Merge "Allow for failure in allocation for MapperVts" into rvc-dev
diff --git a/audio/6.0/IStreamOutEventCallback.hal b/audio/6.0/IStreamOutEventCallback.hal
index de17d73..9c88713 100644
--- a/audio/6.0/IStreamOutEventCallback.hal
+++ b/audio/6.0/IStreamOutEventCallback.hal
@@ -25,11 +25,116 @@
/**
* Codec format changed.
*
+ * onCodecFormatChanged returns an AudioMetadata object in read-only ByteString format.
+ * It represents the most recent codec format decoded by a HW audio decoder.
+ *
+ * Codec format is an optional message from HW audio decoders. It serves to
+ * notify the application about the codec format and audio objects contained
+ * within the compressed audio stream for control, informational,
+ * and display purposes.
+ *
+ * audioMetadata ByteString is convertible to an AudioMetadata object through
+ * both a C++ and a C API present in Metadata.h [1], or through a Java API present
+ * in AudioMetadata.java [2].
+ *
+ * The ByteString format is a stable format used for parcelling (marshalling) across
+ * JNI, AIDL, and HIDL interfaces. The test for R compatibility for native marshalling
+ * 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 6.0) defined keys are as follows [2]:
+ * "bitrate", int32
+ * "channel-mask", int32
+ * "mime", string
+ * "sample-rate", int32
+ * "bit-width", int32
+ * "has-atmos", int32
+ * "audio-encoding", int32
+ *
+ * Parceling Format:
+ * All values are native endian order. [1]
+ *
+ * using type_size_t = uint32_t;
+ * using index_size_t = uint32_t;
+ * using datum_size_t = uint32_t;
+ *
+ * Permitted type indexes are
+ * TYPE_NONE = 0, // Reserved
+ * TYPE_INT32 = 1,
+ * TYPE_INT64 = 2,
+ * TYPE_FLOAT = 3,
+ * TYPE_DOUBLE = 4,
+ * TYPE_STRING = 5,
+ * TYPE_DATA = 6, // A data table of <String, Datum>
+ *
+ * Datum = {
+ * (type_size_t) Type (the type index from type_as_value<T>.)
+ * (datum_size_t) Size (size of the Payload)
+ * (byte string) Payload<Type>
+ * }
+ *
+ * The data is specified in native endian order.
+ * Since the size of the Payload is always present, unknown types may be skipped.
+ *
+ * Payload<Fixed-size Primitive_Value>
+ * [ sizeof(Primitive_Value) in raw bytes ]
+ *
+ * Example of Payload<Int32> of 123:
+ * Payload<Int32>
+ * [ value of 123 ] = 0x7b 0x00 0x00 0x00 123
+ *
+ * Payload<String>
+ * [ (index_size_t) length, not including zero terminator.]
+ * [ (length) raw bytes ]
+ *
+ * Example of Payload<String> of std::string("hi"):
+ * [ (index_size_t) length ] = 0x02 0x00 0x00 0x00 2 strlen("hi")
+ * [ raw bytes "hi" ] = 0x68 0x69 "hi"
+ *
+ * Payload<Data>
+ * [ (index_size_t) entries ]
+ * [ raw bytes (entry 1) Key (Payload<String>)
+ * Value (Datum)
+ * ... (until #entries) ]
+ *
+ * Example of Payload<Data> of {{"hello", "world"},
+ * {"value", (int32_t)1000}};
+ * [ (index_size_t) #entries ] = 0x02 0x00 0x00 0x00 2 entries
+ * Key (Payload<String>)
+ * [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("hello")
+ * [ raw bytes "hello" ] = 0x68 0x65 0x6c 0x6c 0x6f "hello"
+ * Value (Datum)
+ * [ (type_size_t) type ] = 0x05 0x00 0x00 0x00 5 (TYPE_STRING)
+ * [ (datum_size_t) size ] = 0x09 0x00 0x00 0x00 sizeof(index_size_t) +
+ * strlen("world")
+ * Payload<String>
+ * [ (index_size_t) length ] = 0x05 0x00 0x00 0x00 5 strlen("world")
+ * [ raw bytes "world" ] = 0x77 0x6f 0x72 0x6c 0x64 "world"
+ * Key (Payload<String>)
+ * [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("value")
+ * [ raw bytes "value" ] = 0x76 0x61 0x6c 0x75 0x65 "value"
+ * Value (Datum)
+ * [ (type_size_t) type ] = 0x01 0x00 0x00 0x00 1 (TYPE_INT32)
+ * [ (datum_size_t) size ] = 0x04 0x00 0x00 0x00 4 sizeof(int32_t)
+ * Payload<Int32>
+ * [ raw bytes 1000 ] = 0xe8 0x03 0x00 0x00 1000
+ *
+ * The contents of audioMetadata is a Payload<Data>.
+ * An implementation dependent detail is that the Keys are always
+ * stored sorted, so the byte string representation generated is unique.
+ *
+ * Vendor keys are allowed for informational and debugging purposes.
+ * Vendor keys should consist of the vendor company name followed
+ * by a dot; for example, "vendorCompany.someVolume" [2].
+ *
+ * [1] system/media/audio_utils/include/audio_utils/Metadata.h
+ * [2] frameworks/base/media/java/android/media/AudioMetadata.java
+ * [3] system/media/audio_utils/tests/metadata_tests.cpp
+ * [4] cts/tests/tests/media/src/android/media/cts/AudioMetadataTest.java
+ *
* @param audioMetadata is a buffer containing decoded format changes
* reported by codec. The buffer contains data that can be transformed
- * to audio metadata, which is a C++ object based map. See
- * `system/media/audio_utils/include/audio_utils/Metadata.h` for
- * more details.
+ * to audio metadata, which is a C++ object based map.
*/
oneway onCodecFormatChanged(vec<uint8_t> audioMetadata);
};
diff --git a/audio/common/all-versions/default/service/android.hardware.audio.service.rc b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
index 63d2542..f7e1e24 100644
--- a/audio/common/all-versions/default/service/android.hardware.audio.service.rc
+++ b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
@@ -5,5 +5,5 @@
group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock
capabilities BLOCK_SUSPEND
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance
onrestart restart audioserver
diff --git a/audio/common/all-versions/test/utility/include/utility/ValidateXml.h b/audio/common/all-versions/test/utility/include/utility/ValidateXml.h
index ee206f7..274a10b 100644
--- a/audio/common/all-versions/test/utility/include/utility/ValidateXml.h
+++ b/audio/common/all-versions/test/utility/include/utility/ValidateXml.h
@@ -51,8 +51,31 @@
*/
template <bool atLeastOneRequired = true>
::testing::AssertionResult validateXmlMultipleLocations(
- const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr,
- const char* xmlFileName, std::vector<const char*> xmlFileLocations, const char* xsdFilePath);
+ const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr,
+ const char* xmlFileName, const std::vector<std::string>& xmlFileLocations,
+ const char* xsdFilePath);
+template <bool atLeastOneRequired = true>
+::testing::AssertionResult validateXmlMultipleLocations(
+ const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr,
+ const char* xmlFileName, std::initializer_list<const char*> xmlFileLocations,
+ const char* xsdFilePath) {
+ return validateXmlMultipleLocations<atLeastOneRequired>(
+ xmlFileNameExpr, xmlFileLocationsExpr, xsdFilePathExpr, xmlFileName,
+ std::vector<std::string>(xmlFileLocations.begin(), xmlFileLocations.end()),
+ xsdFilePath);
+}
+template <bool atLeastOneRequired = true>
+::testing::AssertionResult validateXmlMultipleLocations(const char* xmlFileNameExpr,
+ const char* xmlFileLocationsExpr,
+ const char* xsdFilePathExpr,
+ const char* xmlFileName,
+ std::vector<const char*> xmlFileLocations,
+ const char* xsdFilePath) {
+ return validateXmlMultipleLocations<atLeastOneRequired>(
+ xmlFileNameExpr, xmlFileLocationsExpr, xsdFilePathExpr, xmlFileName,
+ std::vector<std::string>(xmlFileLocations.begin(), xmlFileLocations.end()),
+ xsdFilePath);
+}
/** ASSERT that all found XML are valid according to an xsd. */
#define ASSERT_VALID_XML_MULTIPLE_LOCATIONS(xmlFileName, xmlFileLocations, xsdFilePath) \
diff --git a/audio/common/all-versions/test/utility/src/ValidateXml.cpp b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
index bdafa82..a866104 100644
--- a/audio/common/all-versions/test/utility/src/ValidateXml.cpp
+++ b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
@@ -131,14 +131,15 @@
template <bool atLeastOneRequired>
::testing::AssertionResult validateXmlMultipleLocations(
- const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr,
- const char* xmlFileName, std::vector<const char*> xmlFileLocations, const char* xsdFilePath) {
+ const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr,
+ const char* xmlFileName, const std::vector<std::string>& xmlFileLocations,
+ const char* xsdFilePath) {
using namespace std::string_literals;
std::vector<std::string> errors;
std::vector<std::string> foundFiles;
- for (const char* location : xmlFileLocations) {
+ for (const auto& location : xmlFileLocations) {
std::string xmlFilePath = location + "/"s + xmlFileName;
if (access(xmlFilePath.c_str(), F_OK) != 0) {
// If the file does not exist ignore this location and fallback on the next one
@@ -166,14 +167,12 @@
: "\nWhere no file might exist.");
}
-template ::testing::AssertionResult validateXmlMultipleLocations<true>(const char*, const char*,
- const char*, const char*,
- std::vector<const char*>,
- const char*);
-template ::testing::AssertionResult validateXmlMultipleLocations<false>(const char*, const char*,
- const char*, const char*,
- std::vector<const char*>,
- const char*);
+template ::testing::AssertionResult validateXmlMultipleLocations<true>(
+ const char*, const char*, const char*, const char*, const std::vector<std::string>&,
+ const char*);
+template ::testing::AssertionResult validateXmlMultipleLocations<false>(
+ const char*, const char*, const char*, const char*, const std::vector<std::string>&,
+ const char*);
} // namespace utility
} // namespace test
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 5d82e7b..d5af335 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -35,6 +35,7 @@
#include <hwbinder/IPCThreadState.h>
#include <android-base/logging.h>
+#include <system/audio_config.h>
#include PATH(android/hardware/audio/FILE_VERSION/IDevice.h)
#include PATH(android/hardware/audio/FILE_VERSION/IDevicesFactory.h)
@@ -135,7 +136,6 @@
////////////////////////// Audio policy configuration ////////////////////////
//////////////////////////////////////////////////////////////////////////////
-static const std::vector<const char*> kConfigLocations = {"/odm/etc", "/vendor/etc", "/system/etc"};
static constexpr char kConfigFileName[] = "audio_policy_configuration.xml";
// Stringify the argument.
@@ -154,8 +154,8 @@
PolicyConfig()
: AudioPolicyConfig(hwModules, availableOutputDevices, availableInputDevices,
defaultOutputDevice) {
- for (const char* location : kConfigLocations) {
- std::string path = std::string(location) + '/' + kConfigFileName;
+ for (const auto& location : android::audio_get_configuration_paths()) {
+ std::string path = location + '/' + kConfigFileName;
if (access(path.c_str(), F_OK) == 0) {
mFilePath = path;
break;
@@ -188,7 +188,7 @@
std::string getError() const {
if (mFilePath.empty()) {
return std::string{"Could not find "} + kConfigFileName +
- " file in: " + testing::PrintToString(kConfigLocations);
+ " file in: " + testing::PrintToString(android::audio_get_configuration_paths());
} else {
return "Invalid config file: " + mFilePath;
}
@@ -304,7 +304,8 @@
"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, kConfigLocations, xsd);
+ EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(kConfigFileName,
+ android::audio_get_configuration_paths(), xsd);
}
class AudioPolicyConfigTest : public AudioHidlTestWithDeviceParameter {
diff --git a/audio/effect/all-versions/vts/functional/ValidateAudioEffectsConfiguration.cpp b/audio/effect/all-versions/vts/functional/ValidateAudioEffectsConfiguration.cpp
index 9c0135b..f251634 100644
--- a/audio/effect/all-versions/vts/functional/ValidateAudioEffectsConfiguration.cpp
+++ b/audio/effect/all-versions/vts/functional/ValidateAudioEffectsConfiguration.cpp
@@ -18,6 +18,7 @@
#include <iterator>
#include <media/EffectsConfig.h>
+#include <system/audio_config.h>
// clang-format off
#include PATH(android/hardware/audio/effect/FILE_VERSION/IEffectsFactory.h)
// clang-format on
@@ -41,13 +42,14 @@
GTEST_SKIP() << "No Effects HAL version " STRINGIFY(CPP_VERSION) " on this device";
}
- std::vector<const char*> locations(std::begin(DEFAULT_LOCATIONS), std::end(DEFAULT_LOCATIONS));
const char* xsd = "/data/local/tmp/audio_effects_conf_" STRINGIFY(CPP_VERSION) ".xsd";
#if MAJOR_VERSION == 2
// In V2, audio effect XML is not required. .conf is still allowed though deprecated
- EXPECT_VALID_XML_MULTIPLE_LOCATIONS(DEFAULT_NAME, locations, xsd);
+ EXPECT_VALID_XML_MULTIPLE_LOCATIONS(DEFAULT_NAME, android::audio_get_configuration_paths(),
+ xsd);
#elif MAJOR_VERSION >= 4
// Starting with V4, audio effect XML is required
- EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(DEFAULT_NAME, locations, xsd);
+ EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(DEFAULT_NAME, android::audio_get_configuration_paths(),
+ xsd);
#endif
}
diff --git a/audio/policy/1.0/vts/functional/ValidateEngineConfiguration.cpp b/audio/policy/1.0/vts/functional/ValidateEngineConfiguration.cpp
index a0aaa6e..5741fa9 100644
--- a/audio/policy/1.0/vts/functional/ValidateEngineConfiguration.cpp
+++ b/audio/policy/1.0/vts/functional/ValidateEngineConfiguration.cpp
@@ -23,7 +23,8 @@
#include <string>
#include "utility/ValidateXml.h"
-static const std::vector<const char*> locations = {"/odm/etc", "/vendor/etc", "/system/etc"};
+#include <system/audio_config.h>
+
static const std::string config = "audio_policy_engine_configuration.xml";
static const std::string schema =
std::string(XSD_DIR) + "/audio_policy_engine_configuration_V1_0.xsd";
@@ -42,7 +43,8 @@
RecordProperty("description",
"Verify that the audio policy engine configuration file "
"is valid according to the schemas");
- EXPECT_VALID_XML_MULTIPLE_LOCATIONS(config.c_str(), locations, schema.c_str());
+ EXPECT_VALID_XML_MULTIPLE_LOCATIONS(config.c_str(), android::audio_get_configuration_paths(),
+ schema.c_str());
}
/**
@@ -52,9 +54,11 @@
*/
static bool deviceUsesConfigurableEngine() {
return android::hardware::audio::common::test::utility::validateXmlMultipleLocations<true>(
- "", "", "", config.c_str(), locations, schema.c_str()) &&
+ "", "", "", config.c_str(), android::audio_get_configuration_paths(),
+ schema.c_str()) &&
android::hardware::audio::common::test::utility::validateXmlMultipleLocations<true>(
- "", "", "", configurableConfig.c_str(), locations, configurableSchemas.c_str());
+ "", "", "", configurableConfig.c_str(), android::audio_get_configuration_paths(),
+ configurableSchemas.c_str());
}
TEST(ValidateConfiguration, audioPolicyEngineConfigurable) {
diff --git a/automotive/audiocontrol/2.0/default/AudioControl.cpp b/automotive/audiocontrol/2.0/default/AudioControl.cpp
index 5bde839..b7c11cd 100644
--- a/automotive/audiocontrol/2.0/default/AudioControl.cpp
+++ b/automotive/audiocontrol/2.0/default/AudioControl.cpp
@@ -69,7 +69,7 @@
}
Return<void> AudioControl::setFadeTowardFront(float value) {
- if (!isValidValue(value)) {
+ if (isValidValue(value)) {
// Just log in this default mock implementation
LOG(INFO) << "Fader set to " << value;
} else {
diff --git a/automotive/can/1.0/default/CanController.cpp b/automotive/can/1.0/default/CanController.cpp
index a2643af..9c0f2c5 100644
--- a/automotive/can/1.0/default/CanController.cpp
+++ b/automotive/can/1.0/default/CanController.cpp
@@ -23,7 +23,7 @@
#include <android-base/logging.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
-#include <filesystem>
+#include <automotive/filesystem>
#include <fstream>
#include <regex>
@@ -31,6 +31,7 @@
using IfId = ICanController::BusConfig::InterfaceId;
using IfIdDisc = ICanController::BusConfig::InterfaceId::hidl_discriminator;
+namespace fs = android::hardware::automotive::filesystem;
namespace fsErrors {
static const std::error_code ok;
@@ -42,10 +43,10 @@
/* In the /sys/devices tree, there are files called "serial", which contain the serial numbers
* for various devices. The exact location inside of this directory is dependent upon the
* hardware we are running on, so we have to start from /sys/devices and work our way down. */
-static const std::filesystem::path kDevPath("/sys/devices/");
+static const fs::path kDevPath("/sys/devices/");
static const std::regex kTtyRe("^tty[A-Z]+[0-9]+$");
-static constexpr auto kOpts = ~(std::filesystem::directory_options::follow_directory_symlink |
- std::filesystem::directory_options::skip_permission_denied);
+static constexpr auto kOpts = ~(fs::directory_options::follow_directory_symlink |
+ fs::directory_options::skip_permission_denied);
/**
* A helper object to associate the interface name and type of a USB to CAN adapter.
@@ -72,16 +73,16 @@
* \param serialPath - Absolute path to a "serial" file for a given device in /sys.
* \return A populated UsbCanIface. On failure, nullopt is returned.
*/
-static std::optional<UsbCanIface> getIfaceName(std::filesystem::path serialPath) {
+static std::optional<UsbCanIface> getIfaceName(fs::path serialPath) {
std::error_code fsStatus;
// Since the path is to a file called "serial", we need to search its parent directory.
- std::filesystem::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus);
+ fs::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus);
if (fsStatus != fsErrors::ok) {
LOG(ERROR) << "Failed to open " << serialPath.parent_path();
return std::nullopt;
}
- for (; fsStatus == fsErrors::ok && fsItr != std::filesystem::recursive_directory_iterator();
+ for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
fsItr.increment(fsStatus)) {
/* We want either a directory called "net" or a directory that looks like tty<something>, so
* skip files. */
@@ -95,7 +96,7 @@
if (currentDir == "net") {
/* This device is a SocketCAN device. The iface name is the only directory under
* net/. Multiple directories under net/ is an error.*/
- std::filesystem::directory_iterator netItr(fsItr->path(), kOpts, fsStatus);
+ fs::directory_iterator netItr(fsItr->path(), kOpts, fsStatus);
if (fsStatus != fsErrors::ok) {
LOG(ERROR) << "Failed to open " << fsItr->path() << " to get net name!";
return std::nullopt;
@@ -111,7 +112,7 @@
LOG(ERROR) << "Failed to verify " << fsItr->path() << " has valid net name!";
return std::nullopt;
}
- if (netItr != std::filesystem::directory_iterator()) {
+ if (netItr != fs::directory_iterator()) {
// There should never be more than one name under net/
LOG(ERROR) << "Found more than one net name in " << fsItr->path() << "!";
return std::nullopt;
@@ -157,13 +158,13 @@
*/
static std::optional<UsbCanIface> findUsbDevice(const hidl_vec<hidl_string>& configSerialnos) {
std::error_code fsStatus;
- std::filesystem::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus);
+ fs::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus);
if (fsStatus != fsErrors::ok) {
LOG(ERROR) << "Failed to open " << kDevPath;
return std::nullopt;
}
- for (; fsStatus == fsErrors::ok && fsItr != std::filesystem::recursive_directory_iterator();
+ for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
fsItr.increment(fsStatus)) {
// We want to find a file called "serial", which is in a directory somewhere. Skip files.
bool isDir = fsItr->is_directory(fsStatus);
@@ -174,7 +175,7 @@
if (!isDir) continue;
auto serialnoPath = fsItr->path() / "serial";
- bool isReg = std::filesystem::is_regular_file(serialnoPath, fsStatus);
+ bool isReg = fs::is_regular_file(serialnoPath, fsStatus);
/* Make sure we have permissions to this directory, ignore enoent, since the file
* "serial" may not exist, which is ok. */
diff --git a/automotive/can/1.0/default/libc++fs/.clang-format b/automotive/can/1.0/default/libc++fs/.clang-format
new file mode 100644
index 0000000..dd59681
--- /dev/null
+++ b/automotive/can/1.0/default/libc++fs/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: LLVM
+
+---
+Language: Cpp
+Standard: Cpp03
+
+AlwaysBreakTemplateDeclarations: true
+PointerAlignment: Left
+
+# Disable formatting options which may break tests.
+SortIncludes: false
+ReflowComments: false
+---
diff --git a/automotive/can/1.0/default/libc++fs/Android.bp b/automotive/can/1.0/default/libc++fs/Android.bp
index 1fe324e..7ab1c28 100644
--- a/automotive/can/1.0/default/libc++fs/Android.bp
+++ b/automotive/can/1.0/default/libc++fs/Android.bp
@@ -19,7 +19,6 @@
cc_defaults {
name: "android.hardware.automotive@libc++fsdefaults",
- host_supported: true,
local_include_dirs: ["include"],
export_include_dirs: ["include"],
cflags: [
@@ -28,33 +27,12 @@
"-Wno-unused-parameter",
],
cppflags: [
- "-std=c++14",
+ "-std=c++17",
"-fexceptions",
"-DLIBCXX_BUILDING_LIBCXXABI",
"-D_LIBCPP_BUILDING_LIBRARY",
],
rtti: true,
- stl: "none",
- target: {
- linux_bionic: {
- enabled: true,
- },
- windows: {
- enabled: true,
- cflags: [
- "-D_LIBCPP_HAS_THREAD_API_WIN32",
- "-D_LIBCXXABI_BUILDING_LIBRARY",
- "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
- "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
- "-UWIN32_LEAN_AND_MEAN",
- ],
- },
- windows_x86: {
- cflags: [
- "-fsjlj-exceptions",
- ],
- },
- },
}
cc_library_static {
diff --git a/automotive/can/1.0/default/libc++fs/include b/automotive/can/1.0/default/libc++fs/include
deleted file mode 120000
index 346e659..0000000
--- a/automotive/can/1.0/default/libc++fs/include
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../external/libcxx/include/
\ No newline at end of file
diff --git a/automotive/can/1.0/default/libc++fs/include/automotive/filesystem b/automotive/can/1.0/default/libc++fs/include/automotive/filesystem
new file mode 100644
index 0000000..660ad09
--- /dev/null
+++ b/automotive/can/1.0/default/libc++fs/include/automotive/filesystem
@@ -0,0 +1,2699 @@
+// -*- C++ -*-
+//===--------------------------- filesystem -------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef _LIBAUTO_FILESYSTEM
+#define _LIBAUTO_FILESYSTEM
+/*
+ filesystem synopsis
+
+ namespace android::hardware::automotive { namespace filesystem {
+
+ class path;
+
+ void swap(path& lhs, path& rhs) noexcept;
+ size_t hash_value(const path& p) noexcept;
+
+ bool operator==(const path& lhs, const path& rhs) noexcept;
+ bool operator!=(const path& lhs, const path& rhs) noexcept;
+ bool operator< (const path& lhs, const path& rhs) noexcept;
+ bool operator<=(const path& lhs, const path& rhs) noexcept;
+ bool operator> (const path& lhs, const path& rhs) noexcept;
+ bool operator>=(const path& lhs, const path& rhs) noexcept;
+
+ path operator/ (const path& lhs, const path& rhs);
+
+ // fs.path.io operators are friends of path.
+ template <class charT, class traits>
+ friend basic_ostream<charT, traits>&
+ operator<<(basic_ostream<charT, traits>& os, const path& p);
+
+ template <class charT, class traits>
+ friend basic_istream<charT, traits>&
+ operator>>(basic_istream<charT, traits>& is, path& p);
+
+ template <class Source>
+ path u8path(const Source& source);
+ template <class InputIterator>
+ path u8path(InputIterator first, InputIterator last);
+
+ class filesystem_error;
+ class directory_entry;
+
+ class directory_iterator;
+
+ // enable directory_iterator range-based for statements
+ directory_iterator begin(directory_iterator iter) noexcept;
+ directory_iterator end(const directory_iterator&) noexcept;
+
+ class recursive_directory_iterator;
+
+ // enable recursive_directory_iterator range-based for statements
+ recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
+ recursive_directory_iterator end(const recursive_directory_iterator&) noexcept;
+
+ class file_status;
+
+ struct space_info
+ {
+ uintmax_t capacity;
+ uintmax_t free;
+ uintmax_t available;
+ };
+
+ enum class file_type;
+ enum class perms;
+ enum class perm_options;
+ enum class copy_options;
+ enum class directory_options;
+
+ typedef chrono::time_point<trivial-clock> file_time_type;
+
+ // operational functions
+
+ path absolute(const path& p);
+ path absolute(const path& p, error_code &ec);
+
+ path canonical(const path& p);
+ path canonical(const path& p, error_code& ec);
+
+ void copy(const path& from, const path& to);
+ void copy(const path& from, const path& to, error_code& ec);
+ void copy(const path& from, const path& to, copy_options options);
+ void copy(const path& from, const path& to, copy_options options,
+ error_code& ec);
+
+ bool copy_file(const path& from, const path& to);
+ bool copy_file(const path& from, const path& to, error_code& ec);
+ bool copy_file(const path& from, const path& to, copy_options option);
+ bool copy_file(const path& from, const path& to, copy_options option,
+ error_code& ec);
+
+ void copy_symlink(const path& existing_symlink, const path& new_symlink);
+ void copy_symlink(const path& existing_symlink, const path& new_symlink,
+ error_code& ec) noexcept;
+
+ bool create_directories(const path& p);
+ bool create_directories(const path& p, error_code& ec);
+
+ bool create_directory(const path& p);
+ bool create_directory(const path& p, error_code& ec) noexcept;
+
+ bool create_directory(const path& p, const path& attributes);
+ bool create_directory(const path& p, const path& attributes,
+ error_code& ec) noexcept;
+
+ void create_directory_symlink(const path& to, const path& new_symlink);
+ void create_directory_symlink(const path& to, const path& new_symlink,
+ error_code& ec) noexcept;
+
+ void create_hard_link(const path& to, const path& new_hard_link);
+ void create_hard_link(const path& to, const path& new_hard_link,
+ error_code& ec) noexcept;
+
+ void create_symlink(const path& to, const path& new_symlink);
+ void create_symlink(const path& to, const path& new_symlink,
+ error_code& ec) noexcept;
+
+ path current_path();
+ path current_path(error_code& ec);
+ void current_path(const path& p);
+ void current_path(const path& p, error_code& ec) noexcept;
+
+ bool exists(file_status s) noexcept;
+ bool exists(const path& p);
+ bool exists(const path& p, error_code& ec) noexcept;
+
+ bool equivalent(const path& p1, const path& p2);
+ bool equivalent(const path& p1, const path& p2, error_code& ec) noexcept;
+
+ uintmax_t file_size(const path& p);
+ uintmax_t file_size(const path& p, error_code& ec) noexcept;
+
+ uintmax_t hard_link_count(const path& p);
+ uintmax_t hard_link_count(const path& p, error_code& ec) noexcept;
+
+ bool is_block_file(file_status s) noexcept;
+ bool is_block_file(const path& p);
+ bool is_block_file(const path& p, error_code& ec) noexcept;
+
+ bool is_character_file(file_status s) noexcept;
+ bool is_character_file(const path& p);
+ bool is_character_file(const path& p, error_code& ec) noexcept;
+
+ bool is_directory(file_status s) noexcept;
+ bool is_directory(const path& p);
+ bool is_directory(const path& p, error_code& ec) noexcept;
+
+ bool is_empty(const path& p);
+ bool is_empty(const path& p, error_code& ec) noexcept;
+
+ bool is_fifo(file_status s) noexcept;
+ bool is_fifo(const path& p);
+ bool is_fifo(const path& p, error_code& ec) noexcept;
+
+ bool is_other(file_status s) noexcept;
+ bool is_other(const path& p);
+ bool is_other(const path& p, error_code& ec) noexcept;
+
+ bool is_regular_file(file_status s) noexcept;
+ bool is_regular_file(const path& p);
+ bool is_regular_file(const path& p, error_code& ec) noexcept;
+
+ bool is_socket(file_status s) noexcept;
+ bool is_socket(const path& p);
+ bool is_socket(const path& p, error_code& ec) noexcept;
+
+ bool is_symlink(file_status s) noexcept;
+ bool is_symlink(const path& p);
+ bool is_symlink(const path& p, error_code& ec) noexcept;
+
+ file_time_type last_write_time(const path& p);
+ file_time_type last_write_time(const path& p, error_code& ec) noexcept;
+ void last_write_time(const path& p, file_time_type new_time);
+ void last_write_time(const path& p, file_time_type new_time,
+ error_code& ec) noexcept;
+
+ void permissions(const path& p, perms prms,
+ perm_options opts=perm_options::replace);
+ void permissions(const path& p, perms prms, error_code& ec) noexcept;
+ void permissions(const path& p, perms prms, perm_options opts,
+ error_code& ec);
+
+ path proximate(const path& p, error_code& ec);
+ path proximate(const path& p, const path& base = current_path());
+ path proximate(const path& p, const path& base, error_code &ec);
+
+ path read_symlink(const path& p);
+ path read_symlink(const path& p, error_code& ec);
+
+ path relative(const path& p, error_code& ec);
+ path relative(const path& p, const path& base=current_path());
+ path relative(const path& p, const path& base, error_code& ec);
+
+ bool remove(const path& p);
+ bool remove(const path& p, error_code& ec) noexcept;
+
+ uintmax_t remove_all(const path& p);
+ uintmax_t remove_all(const path& p, error_code& ec);
+
+ void rename(const path& from, const path& to);
+ void rename(const path& from, const path& to, error_code& ec) noexcept;
+
+ void resize_file(const path& p, uintmax_t size);
+ void resize_file(const path& p, uintmax_t size, error_code& ec) noexcept;
+
+ space_info space(const path& p);
+ space_info space(const path& p, error_code& ec) noexcept;
+
+ file_status status(const path& p);
+ file_status status(const path& p, error_code& ec) noexcept;
+
+ bool status_known(file_status s) noexcept;
+
+ file_status symlink_status(const path& p);
+ file_status symlink_status(const path& p, error_code& ec) noexcept;
+
+ path temp_directory_path();
+ path temp_directory_path(error_code& ec);
+
+ path weakly_canonical(path const& p);
+ path weakly_canonical(path const& p, error_code& ec);
+
+
+} } // namespace android::hardware::automotive::filesystem
+
+*/
+
+#include <__config>
+#include <cstddef>
+#include <cstdlib>
+#include <chrono>
+#include <iterator>
+#include <iosfwd>
+#include <locale>
+#include <memory>
+#include <stack>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <iomanip> // for quoted
+#include <string_view>
+#include <version>
+
+#include <__debug>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#ifndef _LIBCPP_CXX03_LANG
+
+namespace android::hardware::automotive::filesystem {
+using namespace std;
+using namespace std::chrono;
+
+using std::basic_string;
+using std::enable_if;
+using std::error_code;
+using std::false_type;
+
+#ifndef _VSTD
+#define _LIBAUTO_UNDEF_VSTD
+#define _VSTD std
+#endif
+
+#ifdef _VSTD_FS
+#pragma push_macro("_VSTD_FS")
+#else
+#define _LIBAUTO_UNDEF_VSTD_FS
+#endif
+#define _VSTD_FS android::hardware::automotive::filesystem
+
+/* Begin copy of _FilesystemClock from include/chrono */
+struct _FilesystemClock {
+#if !defined(_LIBCPP_HAS_NO_INT128)
+ typedef __int128_t rep;
+ typedef nano period;
+#else
+ typedef long long rep;
+ typedef nano period;
+#endif
+
+ typedef chrono::duration<rep, period> duration;
+ typedef chrono::time_point<_FilesystemClock> time_point;
+
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 const bool is_steady = false;
+
+ _LIBCPP_FUNC_VIS static time_point now() noexcept;
+
+ _LIBCPP_INLINE_VISIBILITY
+ static time_t to_time_t(const time_point& __t) noexcept {
+ typedef chrono::duration<rep> __secs;
+ return time_t(
+ chrono::duration_cast<__secs>(__t.time_since_epoch()).count());
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ static time_point from_time_t(time_t __t) noexcept {
+ typedef chrono::duration<rep> __secs;
+ return time_point(__secs(__t));
+ }
+};
+/* End copy of _FilesystemClock from include/chrono */
+
+typedef chrono::time_point<_FilesystemClock> file_time_type;
+
+struct _LIBCPP_TYPE_VIS space_info {
+ uintmax_t capacity;
+ uintmax_t free;
+ uintmax_t available;
+};
+
+enum class _LIBCPP_ENUM_VIS file_type : signed char {
+ none = 0,
+ not_found = -1,
+ regular = 1,
+ directory = 2,
+ symlink = 3,
+ block = 4,
+ character = 5,
+ fifo = 6,
+ socket = 7,
+ unknown = 8
+};
+
+enum class _LIBCPP_ENUM_VIS perms : unsigned {
+ none = 0,
+
+ owner_read = 0400,
+ owner_write = 0200,
+ owner_exec = 0100,
+ owner_all = 0700,
+
+ group_read = 040,
+ group_write = 020,
+ group_exec = 010,
+ group_all = 070,
+
+ others_read = 04,
+ others_write = 02,
+ others_exec = 01,
+ others_all = 07,
+
+ all = 0777,
+
+ set_uid = 04000,
+ set_gid = 02000,
+ sticky_bit = 01000,
+ mask = 07777,
+ unknown = 0xFFFF,
+};
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perms operator&(perms _LHS, perms _RHS) {
+ return static_cast<perms>(static_cast<unsigned>(_LHS) &
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perms operator|(perms _LHS, perms _RHS) {
+ return static_cast<perms>(static_cast<unsigned>(_LHS) |
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perms operator^(perms _LHS, perms _RHS) {
+ return static_cast<perms>(static_cast<unsigned>(_LHS) ^
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perms operator~(perms _LHS) {
+ return static_cast<perms>(~static_cast<unsigned>(_LHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline perms& operator&=(perms& _LHS, perms _RHS) { return _LHS = _LHS & _RHS; }
+
+_LIBCPP_INLINE_VISIBILITY
+inline perms& operator|=(perms& _LHS, perms _RHS) { return _LHS = _LHS | _RHS; }
+
+_LIBCPP_INLINE_VISIBILITY
+inline perms& operator^=(perms& _LHS, perms _RHS) { return _LHS = _LHS ^ _RHS; }
+
+enum class _LIBCPP_ENUM_VIS perm_options : unsigned char {
+ replace = 1,
+ add = 2,
+ remove = 4,
+ nofollow = 8
+};
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perm_options operator&(perm_options _LHS, perm_options _RHS) {
+ return static_cast<perm_options>(static_cast<unsigned>(_LHS) &
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perm_options operator|(perm_options _LHS, perm_options _RHS) {
+ return static_cast<perm_options>(static_cast<unsigned>(_LHS) |
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perm_options operator^(perm_options _LHS, perm_options _RHS) {
+ return static_cast<perm_options>(static_cast<unsigned>(_LHS) ^
+ static_cast<unsigned>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr perm_options operator~(perm_options _LHS) {
+ return static_cast<perm_options>(~static_cast<unsigned>(_LHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline perm_options& operator&=(perm_options& _LHS, perm_options _RHS) {
+ return _LHS = _LHS & _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline perm_options& operator|=(perm_options& _LHS, perm_options _RHS) {
+ return _LHS = _LHS | _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline perm_options& operator^=(perm_options& _LHS, perm_options _RHS) {
+ return _LHS = _LHS ^ _RHS;
+}
+
+enum class _LIBCPP_ENUM_VIS copy_options : unsigned short {
+ none = 0,
+ skip_existing = 1,
+ overwrite_existing = 2,
+ update_existing = 4,
+ recursive = 8,
+ copy_symlinks = 16,
+ skip_symlinks = 32,
+ directories_only = 64,
+ create_symlinks = 128,
+ create_hard_links = 256,
+ __in_recursive_copy = 512,
+};
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr copy_options operator&(copy_options _LHS, copy_options _RHS) {
+ return static_cast<copy_options>(static_cast<unsigned short>(_LHS) &
+ static_cast<unsigned short>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr copy_options operator|(copy_options _LHS, copy_options _RHS) {
+ return static_cast<copy_options>(static_cast<unsigned short>(_LHS) |
+ static_cast<unsigned short>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr copy_options operator^(copy_options _LHS, copy_options _RHS) {
+ return static_cast<copy_options>(static_cast<unsigned short>(_LHS) ^
+ static_cast<unsigned short>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr copy_options operator~(copy_options _LHS) {
+ return static_cast<copy_options>(~static_cast<unsigned short>(_LHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline copy_options& operator&=(copy_options& _LHS, copy_options _RHS) {
+ return _LHS = _LHS & _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline copy_options& operator|=(copy_options& _LHS, copy_options _RHS) {
+ return _LHS = _LHS | _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline copy_options& operator^=(copy_options& _LHS, copy_options _RHS) {
+ return _LHS = _LHS ^ _RHS;
+}
+
+enum class _LIBCPP_ENUM_VIS directory_options : unsigned char {
+ none = 0,
+ follow_directory_symlink = 1,
+ skip_permission_denied = 2
+};
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr directory_options operator&(directory_options _LHS,
+ directory_options _RHS) {
+ return static_cast<directory_options>(static_cast<unsigned char>(_LHS) &
+ static_cast<unsigned char>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr directory_options operator|(directory_options _LHS,
+ directory_options _RHS) {
+ return static_cast<directory_options>(static_cast<unsigned char>(_LHS) |
+ static_cast<unsigned char>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr directory_options operator^(directory_options _LHS,
+ directory_options _RHS) {
+ return static_cast<directory_options>(static_cast<unsigned char>(_LHS) ^
+ static_cast<unsigned char>(_RHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline constexpr directory_options operator~(directory_options _LHS) {
+ return static_cast<directory_options>(~static_cast<unsigned char>(_LHS));
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline directory_options& operator&=(directory_options& _LHS,
+ directory_options _RHS) {
+ return _LHS = _LHS & _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline directory_options& operator|=(directory_options& _LHS,
+ directory_options _RHS) {
+ return _LHS = _LHS | _RHS;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline directory_options& operator^=(directory_options& _LHS,
+ directory_options _RHS) {
+ return _LHS = _LHS ^ _RHS;
+}
+
+class _LIBCPP_TYPE_VIS file_status {
+public:
+ // constructors
+ _LIBCPP_INLINE_VISIBILITY
+ file_status() noexcept : file_status(file_type::none) {}
+ _LIBCPP_INLINE_VISIBILITY
+ explicit file_status(file_type __ft, perms __prms = perms::unknown) noexcept
+ : __ft_(__ft),
+ __prms_(__prms) {}
+
+ file_status(const file_status&) noexcept = default;
+ file_status(file_status&&) noexcept = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ ~file_status() {}
+
+ file_status& operator=(const file_status&) noexcept = default;
+ file_status& operator=(file_status&&) noexcept = default;
+
+ // observers
+ _LIBCPP_INLINE_VISIBILITY
+ file_type type() const noexcept { return __ft_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ perms permissions() const noexcept { return __prms_; }
+
+ // modifiers
+ _LIBCPP_INLINE_VISIBILITY
+ void type(file_type __ft) noexcept { __ft_ = __ft; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void permissions(perms __p) noexcept { __prms_ = __p; }
+
+private:
+ file_type __ft_;
+ perms __prms_;
+};
+
+class _LIBCPP_TYPE_VIS directory_entry;
+
+template <class _Tp>
+struct __can_convert_char {
+ static const bool value = false;
+};
+template <class _Tp>
+struct __can_convert_char<const _Tp> : public __can_convert_char<_Tp> {};
+template <>
+struct __can_convert_char<char> {
+ static const bool value = true;
+ using __char_type = char;
+};
+template <>
+struct __can_convert_char<wchar_t> {
+ static const bool value = true;
+ using __char_type = wchar_t;
+};
+template <>
+struct __can_convert_char<char16_t> {
+ static const bool value = true;
+ using __char_type = char16_t;
+};
+template <>
+struct __can_convert_char<char32_t> {
+ static const bool value = true;
+ using __char_type = char32_t;
+};
+
+template <class _ECharT>
+typename enable_if<__can_convert_char<_ECharT>::value, bool>::type
+__is_separator(_ECharT __e) {
+ return __e == _ECharT('/');
+}
+
+struct _NullSentinal {};
+
+template <class _Tp>
+using _Void = void;
+
+template <class _Tp, class = void>
+struct __is_pathable_string : public false_type {};
+
+template <class _ECharT, class _Traits, class _Alloc>
+struct __is_pathable_string<
+ basic_string<_ECharT, _Traits, _Alloc>,
+ _Void<typename __can_convert_char<_ECharT>::__char_type> >
+ : public __can_convert_char<_ECharT> {
+ using _Str = basic_string<_ECharT, _Traits, _Alloc>;
+ using _Base = __can_convert_char<_ECharT>;
+ static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); }
+ static _ECharT const* __range_end(_Str const& __s) {
+ return __s.data() + __s.length();
+ }
+ static _ECharT __first_or_null(_Str const& __s) {
+ return __s.empty() ? _ECharT{} : __s[0];
+ }
+};
+
+template <class _ECharT, class _Traits>
+struct __is_pathable_string<
+ basic_string_view<_ECharT, _Traits>,
+ _Void<typename __can_convert_char<_ECharT>::__char_type> >
+ : public __can_convert_char<_ECharT> {
+ using _Str = basic_string_view<_ECharT, _Traits>;
+ using _Base = __can_convert_char<_ECharT>;
+ static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); }
+ static _ECharT const* __range_end(_Str const& __s) {
+ return __s.data() + __s.length();
+ }
+ static _ECharT __first_or_null(_Str const& __s) {
+ return __s.empty() ? _ECharT{} : __s[0];
+ }
+};
+
+template <class _Source, class _DS = typename decay<_Source>::type,
+ class _UnqualPtrType =
+ typename remove_const<typename remove_pointer<_DS>::type>::type,
+ bool _IsCharPtr = is_pointer<_DS>::value&&
+ __can_convert_char<_UnqualPtrType>::value>
+struct __is_pathable_char_array : false_type {};
+
+template <class _Source, class _ECharT, class _UPtr>
+struct __is_pathable_char_array<_Source, _ECharT*, _UPtr, true>
+ : __can_convert_char<typename remove_const<_ECharT>::type> {
+ using _Base = __can_convert_char<typename remove_const<_ECharT>::type>;
+
+ static _ECharT const* __range_begin(const _ECharT* __b) { return __b; }
+ static _ECharT const* __range_end(const _ECharT* __b) {
+ using _Iter = const _ECharT*;
+ const _ECharT __sentinal = _ECharT{};
+ _Iter __e = __b;
+ for (; *__e != __sentinal; ++__e)
+ ;
+ return __e;
+ }
+
+ static _ECharT __first_or_null(const _ECharT* __b) { return *__b; }
+};
+
+template <class _Iter, bool _IsIt = __is_input_iterator<_Iter>::value,
+ class = void>
+struct __is_pathable_iter : false_type {};
+
+template <class _Iter>
+struct __is_pathable_iter<
+ _Iter, true,
+ _Void<typename __can_convert_char<
+ typename iterator_traits<_Iter>::value_type>::__char_type> >
+ : __can_convert_char<typename iterator_traits<_Iter>::value_type> {
+ using _ECharT = typename iterator_traits<_Iter>::value_type;
+ using _Base = __can_convert_char<_ECharT>;
+
+ static _Iter __range_begin(_Iter __b) { return __b; }
+ static _NullSentinal __range_end(_Iter) { return _NullSentinal{}; }
+
+ static _ECharT __first_or_null(_Iter __b) { return *__b; }
+};
+
+template <class _Tp, bool _IsStringT = __is_pathable_string<_Tp>::value,
+ bool _IsCharIterT = __is_pathable_char_array<_Tp>::value,
+ bool _IsIterT = !_IsCharIterT && __is_pathable_iter<_Tp>::value>
+struct __is_pathable : false_type {
+ static_assert(!_IsStringT && !_IsCharIterT && !_IsIterT, "Must all be false");
+};
+
+template <class _Tp>
+struct __is_pathable<_Tp, true, false, false> : __is_pathable_string<_Tp> {};
+
+template <class _Tp>
+struct __is_pathable<_Tp, false, true, false> : __is_pathable_char_array<_Tp> {
+};
+
+template <class _Tp>
+struct __is_pathable<_Tp, false, false, true> : __is_pathable_iter<_Tp> {};
+
+template <class _ECharT>
+struct _PathCVT {
+ static_assert(__can_convert_char<_ECharT>::value,
+ "Char type not convertible");
+
+ typedef __narrow_to_utf8<sizeof(_ECharT) * __CHAR_BIT__> _Narrower;
+
+ static void __append_range(string& __dest, _ECharT const* __b,
+ _ECharT const* __e) {
+ _Narrower()(back_inserter(__dest), __b, __e);
+ }
+
+ template <class _Iter>
+ static void __append_range(string& __dest, _Iter __b, _Iter __e) {
+ static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload");
+ if (__b == __e)
+ return;
+ basic_string<_ECharT> __tmp(__b, __e);
+ _Narrower()(back_inserter(__dest), __tmp.data(),
+ __tmp.data() + __tmp.length());
+ }
+
+ template <class _Iter>
+ static void __append_range(string& __dest, _Iter __b, _NullSentinal) {
+ static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload");
+ const _ECharT __sentinal = _ECharT{};
+ if (*__b == __sentinal)
+ return;
+ basic_string<_ECharT> __tmp;
+ for (; *__b != __sentinal; ++__b)
+ __tmp.push_back(*__b);
+ _Narrower()(back_inserter(__dest), __tmp.data(),
+ __tmp.data() + __tmp.length());
+ }
+
+ template <class _Source>
+ static void __append_source(string& __dest, _Source const& __s) {
+ using _Traits = __is_pathable<_Source>;
+ __append_range(__dest, _Traits::__range_begin(__s),
+ _Traits::__range_end(__s));
+ }
+};
+
+template <>
+struct _PathCVT<char> {
+
+ template <class _Iter>
+ static typename enable_if<__is_exactly_input_iterator<_Iter>::value>::type
+ __append_range(string& __dest, _Iter __b, _Iter __e) {
+ for (; __b != __e; ++__b)
+ __dest.push_back(*__b);
+ }
+
+ template <class _Iter>
+ static typename enable_if<__is_forward_iterator<_Iter>::value>::type
+ __append_range(string& __dest, _Iter __b, _Iter __e) {
+ __dest.__append_forward_unsafe(__b, __e);
+ }
+
+ template <class _Iter>
+ static void __append_range(string& __dest, _Iter __b, _NullSentinal) {
+ const char __sentinal = char{};
+ for (; *__b != __sentinal; ++__b)
+ __dest.push_back(*__b);
+ }
+
+ template <class _Source>
+ static void __append_source(string& __dest, _Source const& __s) {
+ using _Traits = __is_pathable<_Source>;
+ __append_range(__dest, _Traits::__range_begin(__s),
+ _Traits::__range_end(__s));
+ }
+};
+
+class _LIBCPP_TYPE_VIS path {
+ template <class _SourceOrIter, class _Tp = path&>
+ using _EnableIfPathable =
+ typename enable_if<__is_pathable<_SourceOrIter>::value, _Tp>::type;
+
+ template <class _Tp>
+ using _SourceChar = typename __is_pathable<_Tp>::__char_type;
+
+ template <class _Tp>
+ using _SourceCVT = _PathCVT<_SourceChar<_Tp> >;
+
+public:
+ typedef char value_type;
+ typedef basic_string<value_type> string_type;
+ typedef _VSTD::string_view __string_view;
+ static constexpr value_type preferred_separator = '/';
+
+ enum class _LIBCPP_ENUM_VIS format : unsigned char {
+ auto_format,
+ native_format,
+ generic_format
+ };
+
+ // constructors and destructor
+ _LIBCPP_INLINE_VISIBILITY path() noexcept {}
+ _LIBCPP_INLINE_VISIBILITY path(const path& __p) : __pn_(__p.__pn_) {}
+ _LIBCPP_INLINE_VISIBILITY path(path&& __p) noexcept
+ : __pn_(_VSTD::move(__p.__pn_)) {}
+
+ _LIBCPP_INLINE_VISIBILITY
+ path(string_type&& __s, format = format::auto_format) noexcept
+ : __pn_(_VSTD::move(__s)) {}
+
+ template <class _Source, class = _EnableIfPathable<_Source, void> >
+ path(const _Source& __src, format = format::auto_format) {
+ _SourceCVT<_Source>::__append_source(__pn_, __src);
+ }
+
+ template <class _InputIt>
+ path(_InputIt __first, _InputIt __last, format = format::auto_format) {
+ typedef typename iterator_traits<_InputIt>::value_type _ItVal;
+ _PathCVT<_ItVal>::__append_range(__pn_, __first, __last);
+ }
+
+ // TODO Implement locale conversions.
+ template <class _Source, class = _EnableIfPathable<_Source, void> >
+ path(const _Source& __src, const locale& __loc, format = format::auto_format);
+ template <class _InputIt>
+ path(_InputIt __first, _InputIt _last, const locale& __loc,
+ format = format::auto_format);
+
+ _LIBCPP_INLINE_VISIBILITY
+ ~path() = default;
+
+ // assignments
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator=(const path& __p) {
+ __pn_ = __p.__pn_;
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator=(path&& __p) noexcept {
+ __pn_ = _VSTD::move(__p.__pn_);
+ return *this;
+ }
+
+ template <class = void>
+ _LIBCPP_INLINE_VISIBILITY path& operator=(string_type&& __s) noexcept {
+ __pn_ = _VSTD::move(__s);
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& assign(string_type&& __s) noexcept {
+ __pn_ = _VSTD::move(__s);
+ return *this;
+ }
+
+ template <class _Source>
+ _LIBCPP_INLINE_VISIBILITY _EnableIfPathable<_Source>
+ operator=(const _Source& __src) {
+ return this->assign(__src);
+ }
+
+ template <class _Source>
+ _EnableIfPathable<_Source> assign(const _Source& __src) {
+ __pn_.clear();
+ _SourceCVT<_Source>::__append_source(__pn_, __src);
+ return *this;
+ }
+
+ template <class _InputIt>
+ path& assign(_InputIt __first, _InputIt __last) {
+ typedef typename iterator_traits<_InputIt>::value_type _ItVal;
+ __pn_.clear();
+ _PathCVT<_ItVal>::__append_range(__pn_, __first, __last);
+ return *this;
+ }
+
+private:
+ template <class _ECharT>
+ static bool __source_is_absolute(_ECharT __first_or_null) {
+ return __is_separator(__first_or_null);
+ }
+
+public:
+ // appends
+ path& operator/=(const path& __p) {
+ if (__p.is_absolute()) {
+ __pn_ = __p.__pn_;
+ return *this;
+ }
+ if (has_filename())
+ __pn_ += preferred_separator;
+ __pn_ += __p.native();
+ return *this;
+ }
+
+ // FIXME: Use _LIBCPP_DIAGNOSE_WARNING to produce a diagnostic when __src
+ // is known at compile time to be "/' since the user almost certainly intended
+ // to append a separator instead of overwriting the path with "/"
+ template <class _Source>
+ _LIBCPP_INLINE_VISIBILITY _EnableIfPathable<_Source>
+ operator/=(const _Source& __src) {
+ return this->append(__src);
+ }
+
+ template <class _Source>
+ _EnableIfPathable<_Source> append(const _Source& __src) {
+ using _Traits = __is_pathable<_Source>;
+ using _CVT = _PathCVT<_SourceChar<_Source> >;
+ if (__source_is_absolute(_Traits::__first_or_null(__src)))
+ __pn_.clear();
+ else if (has_filename())
+ __pn_ += preferred_separator;
+ _CVT::__append_source(__pn_, __src);
+ return *this;
+ }
+
+ template <class _InputIt>
+ path& append(_InputIt __first, _InputIt __last) {
+ typedef typename iterator_traits<_InputIt>::value_type _ItVal;
+ static_assert(__can_convert_char<_ItVal>::value, "Must convertible");
+ using _CVT = _PathCVT<_ItVal>;
+ if (__first != __last && __source_is_absolute(*__first))
+ __pn_.clear();
+ else if (has_filename())
+ __pn_ += preferred_separator;
+ _CVT::__append_range(__pn_, __first, __last);
+ return *this;
+ }
+
+ // concatenation
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator+=(const path& __x) {
+ __pn_ += __x.__pn_;
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator+=(const string_type& __x) {
+ __pn_ += __x;
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator+=(__string_view __x) {
+ __pn_ += __x;
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator+=(const value_type* __x) {
+ __pn_ += __x;
+ return *this;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& operator+=(value_type __x) {
+ __pn_ += __x;
+ return *this;
+ }
+
+ template <class _ECharT>
+ typename enable_if<__can_convert_char<_ECharT>::value, path&>::type
+ operator+=(_ECharT __x) {
+ basic_string<_ECharT> __tmp;
+ __tmp += __x;
+ _PathCVT<_ECharT>::__append_source(__pn_, __tmp);
+ return *this;
+ }
+
+ template <class _Source>
+ _EnableIfPathable<_Source> operator+=(const _Source& __x) {
+ return this->concat(__x);
+ }
+
+ template <class _Source>
+ _EnableIfPathable<_Source> concat(const _Source& __x) {
+ _SourceCVT<_Source>::__append_source(__pn_, __x);
+ return *this;
+ }
+
+ template <class _InputIt>
+ path& concat(_InputIt __first, _InputIt __last) {
+ typedef typename iterator_traits<_InputIt>::value_type _ItVal;
+ _PathCVT<_ItVal>::__append_range(__pn_, __first, __last);
+ return *this;
+ }
+
+ // modifiers
+ _LIBCPP_INLINE_VISIBILITY
+ void clear() noexcept { __pn_.clear(); }
+
+ path& make_preferred() { return *this; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ path& remove_filename() {
+ auto __fname = __filename();
+ if (!__fname.empty())
+ __pn_.erase(__fname.data() - __pn_.data());
+ return *this;
+ }
+
+ path& replace_filename(const path& __replacement) {
+ remove_filename();
+ return (*this /= __replacement);
+ }
+
+ path& replace_extension(const path& __replacement = path());
+
+ _LIBCPP_INLINE_VISIBILITY
+ void swap(path& __rhs) noexcept { __pn_.swap(__rhs.__pn_); }
+
+ // private helper to allow reserving memory in the path
+ _LIBCPP_INLINE_VISIBILITY
+ void __reserve(size_t __s) { __pn_.reserve(__s); }
+
+ // native format observers
+ _LIBCPP_INLINE_VISIBILITY
+ const string_type& native() const noexcept { return __pn_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ const value_type* c_str() const noexcept { return __pn_.c_str(); }
+
+ _LIBCPP_INLINE_VISIBILITY operator string_type() const { return __pn_; }
+
+ template <class _ECharT, class _Traits = char_traits<_ECharT>,
+ class _Allocator = allocator<_ECharT> >
+ basic_string<_ECharT, _Traits, _Allocator>
+ string(const _Allocator& __a = _Allocator()) const {
+ using _CVT = __widen_from_utf8<sizeof(_ECharT) * __CHAR_BIT__>;
+ using _Str = basic_string<_ECharT, _Traits, _Allocator>;
+ _Str __s(__a);
+ __s.reserve(__pn_.size());
+ _CVT()(back_inserter(__s), __pn_.data(), __pn_.data() + __pn_.size());
+ return __s;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY std::string string() const { return __pn_; }
+ _LIBCPP_INLINE_VISIBILITY std::wstring wstring() const {
+ return string<wchar_t>();
+ }
+ _LIBCPP_INLINE_VISIBILITY std::string u8string() const { return __pn_; }
+ _LIBCPP_INLINE_VISIBILITY std::u16string u16string() const {
+ return string<char16_t>();
+ }
+ _LIBCPP_INLINE_VISIBILITY std::u32string u32string() const {
+ return string<char32_t>();
+ }
+
+ // generic format observers
+ template <class _ECharT, class _Traits = char_traits<_ECharT>,
+ class _Allocator = allocator<_ECharT> >
+ basic_string<_ECharT, _Traits, _Allocator>
+ generic_string(const _Allocator& __a = _Allocator()) const {
+ return string<_ECharT, _Traits, _Allocator>(__a);
+ }
+
+ std::string generic_string() const { return __pn_; }
+ std::wstring generic_wstring() const { return string<wchar_t>(); }
+ std::string generic_u8string() const { return __pn_; }
+ std::u16string generic_u16string() const { return string<char16_t>(); }
+ std::u32string generic_u32string() const { return string<char32_t>(); }
+
+private:
+ int __compare(__string_view) const;
+ __string_view __root_name() const;
+ __string_view __root_directory() const;
+ __string_view __root_path_raw() const;
+ __string_view __relative_path() const;
+ __string_view __parent_path() const;
+ __string_view __filename() const;
+ __string_view __stem() const;
+ __string_view __extension() const;
+
+public:
+ // compare
+ _LIBCPP_INLINE_VISIBILITY int compare(const path& __p) const noexcept {
+ return __compare(__p.__pn_);
+ }
+ _LIBCPP_INLINE_VISIBILITY int compare(const string_type& __s) const {
+ return __compare(__s);
+ }
+ _LIBCPP_INLINE_VISIBILITY int compare(__string_view __s) const {
+ return __compare(__s);
+ }
+ _LIBCPP_INLINE_VISIBILITY int compare(const value_type* __s) const {
+ return __compare(__s);
+ }
+
+ // decomposition
+ _LIBCPP_INLINE_VISIBILITY path root_name() const {
+ return string_type(__root_name());
+ }
+ _LIBCPP_INLINE_VISIBILITY path root_directory() const {
+ return string_type(__root_directory());
+ }
+ _LIBCPP_INLINE_VISIBILITY path root_path() const {
+ return root_name().append(string_type(__root_directory()));
+ }
+ _LIBCPP_INLINE_VISIBILITY path relative_path() const {
+ return string_type(__relative_path());
+ }
+ _LIBCPP_INLINE_VISIBILITY path parent_path() const {
+ return string_type(__parent_path());
+ }
+ _LIBCPP_INLINE_VISIBILITY path filename() const {
+ return string_type(__filename());
+ }
+ _LIBCPP_INLINE_VISIBILITY path stem() const { return string_type(__stem()); }
+ _LIBCPP_INLINE_VISIBILITY path extension() const {
+ return string_type(__extension());
+ }
+
+ // query
+ _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_INLINE_VISIBILITY bool
+ empty() const noexcept {
+ return __pn_.empty();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY bool has_root_name() const {
+ return !__root_name().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_root_directory() const {
+ return !__root_directory().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_root_path() const {
+ return !__root_path_raw().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_relative_path() const {
+ return !__relative_path().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_parent_path() const {
+ return !__parent_path().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_filename() const {
+ return !__filename().empty();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool has_stem() const { return !__stem().empty(); }
+ _LIBCPP_INLINE_VISIBILITY bool has_extension() const {
+ return !__extension().empty();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY bool is_absolute() const {
+ return has_root_directory();
+ }
+ _LIBCPP_INLINE_VISIBILITY bool is_relative() const { return !is_absolute(); }
+
+ // relative paths
+ path lexically_normal() const;
+ path lexically_relative(const path& __base) const;
+
+ _LIBCPP_INLINE_VISIBILITY path lexically_proximate(const path& __base) const {
+ path __result = this->lexically_relative(__base);
+ if (__result.native().empty())
+ return *this;
+ return __result;
+ }
+
+ // iterators
+ class _LIBCPP_TYPE_VIS iterator;
+ typedef iterator const_iterator;
+
+ iterator begin() const;
+ iterator end() const;
+
+ template <class _CharT, class _Traits>
+ _LIBCPP_INLINE_VISIBILITY friend
+ typename enable_if<is_same<_CharT, char>::value &&
+ is_same<_Traits, char_traits<char> >::value,
+ basic_ostream<_CharT, _Traits>&>::type
+ operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) {
+ __os << std::__quoted(__p.native());
+ return __os;
+ }
+
+ template <class _CharT, class _Traits>
+ _LIBCPP_INLINE_VISIBILITY friend
+ typename enable_if<!is_same<_CharT, char>::value ||
+ !is_same<_Traits, char_traits<char> >::value,
+ basic_ostream<_CharT, _Traits>&>::type
+ operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) {
+ __os << std::__quoted(__p.string<_CharT, _Traits>());
+ return __os;
+ }
+
+ template <class _CharT, class _Traits>
+ _LIBCPP_INLINE_VISIBILITY friend basic_istream<_CharT, _Traits>&
+ operator>>(basic_istream<_CharT, _Traits>& __is, path& __p) {
+ basic_string<_CharT, _Traits> __tmp;
+ __is >> __quoted(__tmp);
+ __p = __tmp;
+ return __is;
+ }
+
+ friend _LIBCPP_INLINE_VISIBILITY bool operator==(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) == 0;
+ }
+ friend _LIBCPP_INLINE_VISIBILITY bool operator!=(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) != 0;
+ }
+ friend _LIBCPP_INLINE_VISIBILITY bool operator<(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) < 0;
+ }
+ friend _LIBCPP_INLINE_VISIBILITY bool operator<=(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) <= 0;
+ }
+ friend _LIBCPP_INLINE_VISIBILITY bool operator>(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) > 0;
+ }
+ friend _LIBCPP_INLINE_VISIBILITY bool operator>=(const path& __lhs, const path& __rhs) noexcept {
+ return __lhs.compare(__rhs) >= 0;
+ }
+
+ friend _LIBCPP_INLINE_VISIBILITY path operator/(const path& __lhs,
+ const path& __rhs) {
+ path __result(__lhs);
+ __result /= __rhs;
+ return __result;
+ }
+private:
+ inline _LIBCPP_INLINE_VISIBILITY path&
+ __assign_view(__string_view const& __s) noexcept {
+ __pn_ = string_type(__s);
+ return *this;
+ }
+ string_type __pn_;
+};
+
+inline _LIBCPP_INLINE_VISIBILITY void swap(path& __lhs, path& __rhs) noexcept {
+ __lhs.swap(__rhs);
+}
+
+_LIBCPP_FUNC_VIS
+size_t hash_value(const path& __p) noexcept;
+
+template <class _Source>
+_LIBCPP_INLINE_VISIBILITY
+ typename enable_if<__is_pathable<_Source>::value, path>::type
+ u8path(const _Source& __s) {
+ static_assert(
+ is_same<typename __is_pathable<_Source>::__char_type, char>::value,
+ "u8path(Source const&) requires Source have a character type of type "
+ "'char'");
+ return path(__s);
+}
+
+template <class _InputIt>
+_LIBCPP_INLINE_VISIBILITY
+ typename enable_if<__is_pathable<_InputIt>::value, path>::type
+ u8path(_InputIt __f, _InputIt __l) {
+ static_assert(
+ is_same<typename __is_pathable<_InputIt>::__char_type, char>::value,
+ "u8path(Iter, Iter) requires Iter have a value_type of type 'char'");
+ return path(__f, __l);
+}
+
+class _LIBCPP_TYPE_VIS path::iterator {
+public:
+ enum _ParserState : unsigned char {
+ _Singular,
+ _BeforeBegin,
+ _InRootName,
+ _InRootDir,
+ _InFilenames,
+ _InTrailingSep,
+ _AtEnd
+ };
+
+public:
+ typedef bidirectional_iterator_tag iterator_category;
+
+ typedef path value_type;
+ typedef std::ptrdiff_t difference_type;
+ typedef const path* pointer;
+ typedef const path& reference;
+
+ typedef void
+ __stashing_iterator_tag; // See reverse_iterator and __is_stashing_iterator
+
+public:
+ _LIBCPP_INLINE_VISIBILITY
+ iterator()
+ : __stashed_elem_(), __path_ptr_(nullptr), __entry_(),
+ __state_(_Singular) {}
+
+ iterator(const iterator&) = default;
+ ~iterator() = default;
+
+ iterator& operator=(const iterator&) = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ reference operator*() const { return __stashed_elem_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ pointer operator->() const { return &__stashed_elem_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ iterator& operator++() {
+ _LIBCPP_ASSERT(__state_ != _Singular,
+ "attempting to increment a singular iterator");
+ _LIBCPP_ASSERT(__state_ != _AtEnd,
+ "attempting to increment the end iterator");
+ return __increment();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ iterator operator++(int) {
+ iterator __it(*this);
+ this->operator++();
+ return __it;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ iterator& operator--() {
+ _LIBCPP_ASSERT(__state_ != _Singular,
+ "attempting to decrement a singular iterator");
+ _LIBCPP_ASSERT(__entry_.data() != __path_ptr_->native().data(),
+ "attempting to decrement the begin iterator");
+ return __decrement();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ iterator operator--(int) {
+ iterator __it(*this);
+ this->operator--();
+ return __it;
+ }
+
+private:
+ friend class path;
+
+ inline _LIBCPP_INLINE_VISIBILITY friend bool operator==(const iterator&,
+ const iterator&);
+
+ iterator& __increment();
+ iterator& __decrement();
+
+ path __stashed_elem_;
+ const path* __path_ptr_;
+ path::__string_view __entry_;
+ _ParserState __state_;
+};
+
+inline _LIBCPP_INLINE_VISIBILITY bool operator==(const path::iterator& __lhs,
+ const path::iterator& __rhs) {
+ return __lhs.__path_ptr_ == __rhs.__path_ptr_ &&
+ __lhs.__entry_.data() == __rhs.__entry_.data();
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool operator!=(const path::iterator& __lhs,
+ const path::iterator& __rhs) {
+ return !(__lhs == __rhs);
+}
+
+class _LIBCPP_EXCEPTION_ABI filesystem_error : public system_error {
+public:
+ _LIBCPP_INLINE_VISIBILITY
+ filesystem_error(const string& __what, error_code __ec)
+ : system_error(__ec, __what),
+ __storage_(make_shared<_Storage>(path(), path())) {
+ __create_what(0);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ filesystem_error(const string& __what, const path& __p1, error_code __ec)
+ : system_error(__ec, __what),
+ __storage_(make_shared<_Storage>(__p1, path())) {
+ __create_what(1);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ filesystem_error(const string& __what, const path& __p1, const path& __p2,
+ error_code __ec)
+ : system_error(__ec, __what),
+ __storage_(make_shared<_Storage>(__p1, __p2)) {
+ __create_what(2);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ const path& path1() const noexcept { return __storage_->__p1_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ const path& path2() const noexcept { return __storage_->__p2_; }
+
+ ~filesystem_error() override; // key function
+
+ _LIBCPP_INLINE_VISIBILITY
+ const char* what() const noexcept override {
+ return __storage_->__what_.c_str();
+ }
+
+ _LIBCPP_FUNC_VIS
+ void __create_what(int __num_paths);
+
+private:
+ struct _Storage {
+ _LIBCPP_INLINE_VISIBILITY
+ _Storage(const path& __p1, const path& __p2) : __p1_(__p1), __p2_(__p2) {}
+
+ path __p1_;
+ path __p2_;
+ string __what_;
+ };
+ shared_ptr<_Storage> __storage_;
+};
+
+template <class... _Args>
+_LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY
+#ifndef _LIBCPP_NO_EXCEPTIONS
+ void
+ __throw_filesystem_error(_Args&&... __args) {
+ throw filesystem_error(std::forward<_Args>(__args)...);
+}
+#else
+ void
+ __throw_filesystem_error(_Args&&...) {
+ _VSTD::abort();
+}
+#endif
+
+// operational functions
+
+_LIBCPP_FUNC_VIS
+path __absolute(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+path __canonical(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __copy(const path& __from, const path& __to, copy_options __opt,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __copy_file(const path& __from, const path& __to, copy_options __opt,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __copy_symlink(const path& __existing_symlink, const path& __new_symlink,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __create_directories(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __create_directory(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __create_directory(const path& p, const path& attributes,
+ error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __create_directory_symlink(const path& __to, const path& __new_symlink,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __create_hard_link(const path& __to, const path& __new_hard_link,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __create_symlink(const path& __to, const path& __new_symlink,
+ error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+path __current_path(error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __current_path(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __equivalent(const path&, const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+uintmax_t __file_size(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+uintmax_t __hard_link_count(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __fs_is_empty(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+file_time_type __last_write_time(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __last_write_time(const path& p, file_time_type new_time,
+ error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __permissions(const path&, perms, perm_options, error_code* = nullptr);
+_LIBCPP_FUNC_VIS
+path __read_symlink(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+bool __remove(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+uintmax_t __remove_all(const path& p, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __rename(const path& from, const path& to, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+void __resize_file(const path& p, uintmax_t size, error_code* ec = nullptr);
+_LIBCPP_FUNC_VIS
+space_info __space(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+file_status __status(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+file_status __symlink_status(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+path __system_complete(const path&, error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+path __temp_directory_path(error_code* __ec = nullptr);
+_LIBCPP_FUNC_VIS
+path __weakly_canonical(path const& __p, error_code* __ec = nullptr);
+
+inline _LIBCPP_INLINE_VISIBILITY path current_path() {
+ return __current_path();
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path current_path(error_code& __ec) {
+ return __current_path(&__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void current_path(const path& __p) {
+ __current_path(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void current_path(const path& __p,
+ error_code& __ec) noexcept {
+ __current_path(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path absolute(const path& __p) {
+ return __absolute(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path absolute(const path& __p,
+ error_code& __ec) {
+ return __absolute(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path canonical(const path& __p) {
+ return __canonical(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path canonical(const path& __p,
+ error_code& __ec) {
+ return __canonical(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from,
+ const path& __to) {
+ __copy(__from, __to, copy_options::none);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to,
+ error_code& __ec) {
+ __copy(__from, __to, copy_options::none, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to,
+ copy_options __opt) {
+ __copy(__from, __to, __opt);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to,
+ copy_options __opt,
+ error_code& __ec) {
+ __copy(__from, __to, __opt, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool copy_file(const path& __from,
+ const path& __to) {
+ return __copy_file(__from, __to, copy_options::none);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+copy_file(const path& __from, const path& __to, error_code& __ec) {
+ return __copy_file(__from, __to, copy_options::none, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+copy_file(const path& __from, const path& __to, copy_options __opt) {
+ return __copy_file(__from, __to, __opt);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool copy_file(const path& __from,
+ const path& __to,
+ copy_options __opt,
+ error_code& __ec) {
+ return __copy_file(__from, __to, __opt, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void copy_symlink(const path& __existing,
+ const path& __new) {
+ __copy_symlink(__existing, __new);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+copy_symlink(const path& __ext, const path& __new, error_code& __ec) noexcept {
+ __copy_symlink(__ext, __new, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool create_directories(const path& __p) {
+ return __create_directories(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool create_directories(const path& __p,
+ error_code& __ec) {
+ return __create_directories(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool create_directory(const path& __p) {
+ return __create_directory(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+create_directory(const path& __p, error_code& __ec) noexcept {
+ return __create_directory(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool create_directory(const path& __p,
+ const path& __attrs) {
+ return __create_directory(__p, __attrs);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+create_directory(const path& __p, const path& __attrs,
+ error_code& __ec) noexcept {
+ return __create_directory(__p, __attrs, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+create_directory_symlink(const path& __to, const path& __new) {
+ __create_directory_symlink(__to, __new);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+create_directory_symlink(const path& __to, const path& __new,
+ error_code& __ec) noexcept {
+ __create_directory_symlink(__to, __new, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void create_hard_link(const path& __to,
+ const path& __new) {
+ __create_hard_link(__to, __new);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+create_hard_link(const path& __to, const path& __new,
+ error_code& __ec) noexcept {
+ __create_hard_link(__to, __new, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void create_symlink(const path& __to,
+ const path& __new) {
+ __create_symlink(__to, __new);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+create_symlink(const path& __to, const path& __new, error_code& __ec) noexcept {
+ return __create_symlink(__to, __new, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool status_known(file_status __s) noexcept {
+ return __s.type() != file_type::none;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool exists(file_status __s) noexcept {
+ return status_known(__s) && __s.type() != file_type::not_found;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool exists(const path& __p) {
+ return exists(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool exists(const path& __p,
+ error_code& __ec) noexcept {
+ auto __s = __status(__p, &__ec);
+ if (status_known(__s))
+ __ec.clear();
+ return exists(__s);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool equivalent(const path& __p1,
+ const path& __p2) {
+ return __equivalent(__p1, __p2);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+equivalent(const path& __p1, const path& __p2, error_code& __ec) noexcept {
+ return __equivalent(__p1, __p2, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t file_size(const path& __p) {
+ return __file_size(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t
+file_size(const path& __p, error_code& __ec) noexcept {
+ return __file_size(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t hard_link_count(const path& __p) {
+ return __hard_link_count(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t
+hard_link_count(const path& __p, error_code& __ec) noexcept {
+ return __hard_link_count(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(file_status __s) noexcept {
+ return __s.type() == file_type::block;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(const path& __p) {
+ return is_block_file(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(const path& __p,
+ error_code& __ec) noexcept {
+ return is_block_file(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+is_character_file(file_status __s) noexcept {
+ return __s.type() == file_type::character;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_character_file(const path& __p) {
+ return is_character_file(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+is_character_file(const path& __p, error_code& __ec) noexcept {
+ return is_character_file(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_directory(file_status __s) noexcept {
+ return __s.type() == file_type::directory;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_directory(const path& __p) {
+ return is_directory(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_directory(const path& __p,
+ error_code& __ec) noexcept {
+ return is_directory(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_empty(const path& __p) {
+ return __fs_is_empty(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_empty(const path& __p,
+ error_code& __ec) {
+ return __fs_is_empty(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(file_status __s) noexcept {
+ return __s.type() == file_type::fifo;
+}
+inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(const path& __p) {
+ return is_fifo(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(const path& __p,
+ error_code& __ec) noexcept {
+ return is_fifo(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+is_regular_file(file_status __s) noexcept {
+ return __s.type() == file_type::regular;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_regular_file(const path& __p) {
+ return is_regular_file(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+is_regular_file(const path& __p, error_code& __ec) noexcept {
+ return is_regular_file(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_socket(file_status __s) noexcept {
+ return __s.type() == file_type::socket;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_socket(const path& __p) {
+ return is_socket(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_socket(const path& __p,
+ error_code& __ec) noexcept {
+ return is_socket(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(file_status __s) noexcept {
+ return __s.type() == file_type::symlink;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(const path& __p) {
+ return is_symlink(__symlink_status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(const path& __p,
+ error_code& __ec) noexcept {
+ return is_symlink(__symlink_status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_other(file_status __s) noexcept {
+ return exists(__s) && !is_regular_file(__s) && !is_directory(__s) &&
+ !is_symlink(__s);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_other(const path& __p) {
+ return is_other(__status(__p));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool is_other(const path& __p,
+ error_code& __ec) noexcept {
+ return is_other(__status(__p, &__ec));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_time_type
+last_write_time(const path& __p) {
+ return __last_write_time(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_time_type
+last_write_time(const path& __p, error_code& __ec) noexcept {
+ return __last_write_time(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void last_write_time(const path& __p,
+ file_time_type __t) {
+ __last_write_time(__p, __t);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+last_write_time(const path& __p, file_time_type __t,
+ error_code& __ec) noexcept {
+ __last_write_time(__p, __t, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+permissions(const path& __p, perms __prms,
+ perm_options __opts = perm_options::replace) {
+ __permissions(__p, __prms, __opts);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void permissions(const path& __p, perms __prms,
+ error_code& __ec) noexcept {
+ __permissions(__p, __prms, perm_options::replace, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void permissions(const path& __p, perms __prms,
+ perm_options __opts,
+ error_code& __ec) {
+ __permissions(__p, __prms, __opts, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path proximate(const path& __p,
+ const path& __base,
+ error_code& __ec) {
+ path __tmp = __weakly_canonical(__p, &__ec);
+ if (__ec)
+ return {};
+ path __tmp_base = __weakly_canonical(__base, &__ec);
+ if (__ec)
+ return {};
+ return __tmp.lexically_proximate(__tmp_base);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path proximate(const path& __p,
+ error_code& __ec) {
+ return proximate(__p, current_path(), __ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path
+proximate(const path& __p, const path& __base = current_path()) {
+ return __weakly_canonical(__p).lexically_proximate(
+ __weakly_canonical(__base));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path read_symlink(const path& __p) {
+ return __read_symlink(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path read_symlink(const path& __p,
+ error_code& __ec) {
+ return __read_symlink(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path relative(const path& __p,
+ const path& __base,
+ error_code& __ec) {
+ path __tmp = __weakly_canonical(__p, &__ec);
+ if (__ec)
+ return path();
+ path __tmpbase = __weakly_canonical(__base, &__ec);
+ if (__ec)
+ return path();
+ return __tmp.lexically_relative(__tmpbase);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path relative(const path& __p,
+ error_code& __ec) {
+ return relative(__p, current_path(), __ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path
+relative(const path& __p, const path& __base = current_path()) {
+ return __weakly_canonical(__p).lexically_relative(__weakly_canonical(__base));
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool remove(const path& __p) {
+ return __remove(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool remove(const path& __p,
+ error_code& __ec) noexcept {
+ return __remove(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t remove_all(const path& __p) {
+ return __remove_all(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY uintmax_t remove_all(const path& __p,
+ error_code& __ec) {
+ return __remove_all(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void rename(const path& __from,
+ const path& __to) {
+ return __rename(__from, __to);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+rename(const path& __from, const path& __to, error_code& __ec) noexcept {
+ return __rename(__from, __to, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void resize_file(const path& __p,
+ uintmax_t __ns) {
+ return __resize_file(__p, __ns);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY void
+resize_file(const path& __p, uintmax_t __ns, error_code& __ec) noexcept {
+ return __resize_file(__p, __ns, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY space_info space(const path& __p) {
+ return __space(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY space_info space(const path& __p,
+ error_code& __ec) noexcept {
+ return __space(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_status status(const path& __p) {
+ return __status(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_status status(const path& __p,
+ error_code& __ec) noexcept {
+ return __status(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_status symlink_status(const path& __p) {
+ return __symlink_status(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY file_status
+symlink_status(const path& __p, error_code& __ec) noexcept {
+ return __symlink_status(__p, &__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path temp_directory_path() {
+ return __temp_directory_path();
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path temp_directory_path(error_code& __ec) {
+ return __temp_directory_path(&__ec);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path weakly_canonical(path const& __p) {
+ return __weakly_canonical(__p);
+}
+
+inline _LIBCPP_INLINE_VISIBILITY path weakly_canonical(path const& __p,
+ error_code& __ec) {
+ return __weakly_canonical(__p, &__ec);
+}
+
+class directory_iterator;
+class recursive_directory_iterator;
+class __dir_stream;
+
+class directory_entry {
+ typedef _VSTD_FS::path _Path;
+
+public:
+ // constructors and destructors
+ directory_entry() noexcept = default;
+ directory_entry(directory_entry const&) = default;
+ directory_entry(directory_entry&&) noexcept = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ explicit directory_entry(_Path const& __p) : __p_(__p) {
+ error_code __ec;
+ __refresh(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ directory_entry(_Path const& __p, error_code& __ec) : __p_(__p) {
+ __refresh(&__ec);
+ }
+
+ ~directory_entry() {}
+
+ directory_entry& operator=(directory_entry const&) = default;
+ directory_entry& operator=(directory_entry&&) noexcept = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ void assign(_Path const& __p) {
+ __p_ = __p;
+ error_code __ec;
+ __refresh(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void assign(_Path const& __p, error_code& __ec) {
+ __p_ = __p;
+ __refresh(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void replace_filename(_Path const& __p) {
+ __p_.replace_filename(__p);
+ error_code __ec;
+ __refresh(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void replace_filename(_Path const& __p, error_code& __ec) {
+ __p_ = __p_.parent_path() / __p;
+ __refresh(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void refresh() { __refresh(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void refresh(error_code& __ec) noexcept { __refresh(&__ec); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ _Path const& path() const noexcept { return __p_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ operator const _Path&() const noexcept { return __p_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool exists() const { return _VSTD_FS::exists(file_status{__get_ft()}); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool exists(error_code& __ec) const noexcept {
+ return _VSTD_FS::exists(file_status{__get_ft(&__ec)});
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_block_file() const { return __get_ft() == file_type::block; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_block_file(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::block;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_character_file() const { return __get_ft() == file_type::character; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_character_file(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::character;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_directory() const { return __get_ft() == file_type::directory; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_directory(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::directory;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_fifo() const { return __get_ft() == file_type::fifo; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_fifo(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::fifo;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_other() const { return _VSTD_FS::is_other(file_status{__get_ft()}); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_other(error_code& __ec) const noexcept {
+ return _VSTD_FS::is_other(file_status{__get_ft(&__ec)});
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_regular_file() const { return __get_ft() == file_type::regular; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_regular_file(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::regular;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_socket() const { return __get_ft() == file_type::socket; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_socket(error_code& __ec) const noexcept {
+ return __get_ft(&__ec) == file_type::socket;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_symlink() const { return __get_sym_ft() == file_type::symlink; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool is_symlink(error_code& __ec) const noexcept {
+ return __get_sym_ft(&__ec) == file_type::symlink;
+ }
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t file_size() const { return __get_size(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t file_size(error_code& __ec) const noexcept {
+ return __get_size(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t hard_link_count() const { return __get_nlink(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t hard_link_count(error_code& __ec) const noexcept {
+ return __get_nlink(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_time_type last_write_time() const { return __get_write_time(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_time_type last_write_time(error_code& __ec) const noexcept {
+ return __get_write_time(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status status() const { return __get_status(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status status(error_code& __ec) const noexcept {
+ return __get_status(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status symlink_status() const { return __get_symlink_status(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status symlink_status(error_code& __ec) const noexcept {
+ return __get_symlink_status(&__ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator<(directory_entry const& __rhs) const noexcept {
+ return __p_ < __rhs.__p_;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator==(directory_entry const& __rhs) const noexcept {
+ return __p_ == __rhs.__p_;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator!=(directory_entry const& __rhs) const noexcept {
+ return __p_ != __rhs.__p_;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator<=(directory_entry const& __rhs) const noexcept {
+ return __p_ <= __rhs.__p_;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator>(directory_entry const& __rhs) const noexcept {
+ return __p_ > __rhs.__p_;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool operator>=(directory_entry const& __rhs) const noexcept {
+ return __p_ >= __rhs.__p_;
+ }
+
+private:
+ friend class directory_iterator;
+ friend class recursive_directory_iterator;
+ friend class __dir_stream;
+
+ enum _CacheType : unsigned char {
+ _Empty,
+ _IterSymlink,
+ _IterNonSymlink,
+ _RefreshSymlink,
+ _RefreshSymlinkUnresolved,
+ _RefreshNonSymlink
+ };
+
+ struct __cached_data {
+ uintmax_t __size_;
+ uintmax_t __nlink_;
+ file_time_type __write_time_;
+ perms __sym_perms_;
+ perms __non_sym_perms_;
+ file_type __type_;
+ _CacheType __cache_type_;
+
+ _LIBCPP_INLINE_VISIBILITY
+ __cached_data() noexcept { __reset(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void __reset() {
+ __cache_type_ = _Empty;
+ __type_ = file_type::none;
+ __sym_perms_ = __non_sym_perms_ = perms::unknown;
+ __size_ = __nlink_ = uintmax_t(-1);
+ __write_time_ = file_time_type::min();
+ }
+ };
+
+ _LIBCPP_INLINE_VISIBILITY
+ static __cached_data __create_iter_result(file_type __ft) {
+ __cached_data __data;
+ __data.__type_ = __ft;
+ __data.__cache_type_ = [&]() {
+ switch (__ft) {
+ case file_type::none:
+ return _Empty;
+ case file_type::symlink:
+ return _IterSymlink;
+ default:
+ return _IterNonSymlink;
+ }
+ }();
+ return __data;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void __assign_iter_entry(_Path&& __p, __cached_data __dt) {
+ __p_ = std::move(__p);
+ __data_ = __dt;
+ }
+
+ _LIBCPP_FUNC_VIS
+ error_code __do_refresh() noexcept;
+
+ _LIBCPP_INLINE_VISIBILITY
+ static bool __is_dne_error(error_code const& __ec) {
+ if (!__ec)
+ return true;
+ switch (static_cast<errc>(__ec.value())) {
+ case errc::no_such_file_or_directory:
+ case errc::not_a_directory:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void __handle_error(const char* __msg, error_code* __dest_ec,
+ error_code const& __ec, bool __allow_dne = false) const {
+ if (__dest_ec) {
+ *__dest_ec = __ec;
+ return;
+ }
+ if (__ec && (!__allow_dne || !__is_dne_error(__ec)))
+ __throw_filesystem_error(__msg, __p_, __ec);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void __refresh(error_code* __ec = nullptr) {
+ __handle_error("in directory_entry::refresh", __ec, __do_refresh(),
+ /*allow_dne*/ true);
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_type __get_sym_ft(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ return __symlink_status(__p_, __ec).type();
+ case _IterSymlink:
+ case _RefreshSymlink:
+ case _RefreshSymlinkUnresolved:
+ if (__ec)
+ __ec->clear();
+ return file_type::symlink;
+ case _IterNonSymlink:
+ case _RefreshNonSymlink:
+ file_status __st(__data_.__type_);
+ if (__ec && !_VSTD_FS::exists(__st))
+ *__ec = make_error_code(errc::no_such_file_or_directory);
+ else if (__ec)
+ __ec->clear();
+ return __data_.__type_;
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_type __get_ft(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterSymlink:
+ case _RefreshSymlinkUnresolved:
+ return __status(__p_, __ec).type();
+ case _IterNonSymlink:
+ case _RefreshNonSymlink:
+ case _RefreshSymlink: {
+ file_status __st(__data_.__type_);
+ if (__ec && !_VSTD_FS::exists(__st))
+ *__ec = make_error_code(errc::no_such_file_or_directory);
+ else if (__ec)
+ __ec->clear();
+ return __data_.__type_;
+ }
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status __get_status(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterNonSymlink:
+ case _IterSymlink:
+ case _RefreshSymlinkUnresolved:
+ return __status(__p_, __ec);
+ case _RefreshNonSymlink:
+ case _RefreshSymlink:
+ return file_status(__get_ft(__ec), __data_.__non_sym_perms_);
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_status __get_symlink_status(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterNonSymlink:
+ case _IterSymlink:
+ return __symlink_status(__p_, __ec);
+ case _RefreshNonSymlink:
+ return file_status(__get_sym_ft(__ec), __data_.__non_sym_perms_);
+ case _RefreshSymlink:
+ case _RefreshSymlinkUnresolved:
+ return file_status(__get_sym_ft(__ec), __data_.__sym_perms_);
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t __get_size(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterNonSymlink:
+ case _IterSymlink:
+ case _RefreshSymlinkUnresolved:
+ return _VSTD_FS::__file_size(__p_, __ec);
+ case _RefreshSymlink:
+ case _RefreshNonSymlink: {
+ error_code __m_ec;
+ file_status __st(__get_ft(&__m_ec));
+ __handle_error("in directory_entry::file_size", __ec, __m_ec);
+ if (_VSTD_FS::exists(__st) && !_VSTD_FS::is_regular_file(__st)) {
+ errc __err_kind = _VSTD_FS::is_directory(__st) ? errc::is_a_directory
+ : errc::not_supported;
+ __handle_error("in directory_entry::file_size", __ec,
+ make_error_code(__err_kind));
+ }
+ return __data_.__size_;
+ }
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ uintmax_t __get_nlink(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterNonSymlink:
+ case _IterSymlink:
+ case _RefreshSymlinkUnresolved:
+ return _VSTD_FS::__hard_link_count(__p_, __ec);
+ case _RefreshSymlink:
+ case _RefreshNonSymlink: {
+ error_code __m_ec;
+ (void)__get_ft(&__m_ec);
+ __handle_error("in directory_entry::hard_link_count", __ec, __m_ec);
+ return __data_.__nlink_;
+ }
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ file_time_type __get_write_time(error_code* __ec = nullptr) const {
+ switch (__data_.__cache_type_) {
+ case _Empty:
+ case _IterNonSymlink:
+ case _IterSymlink:
+ case _RefreshSymlinkUnresolved:
+ return _VSTD_FS::__last_write_time(__p_, __ec);
+ case _RefreshSymlink:
+ case _RefreshNonSymlink: {
+ error_code __m_ec;
+ file_status __st(__get_ft(&__m_ec));
+ __handle_error("in directory_entry::last_write_time", __ec, __m_ec);
+ if (_VSTD_FS::exists(__st) &&
+ __data_.__write_time_ == file_time_type::min())
+ __handle_error("in directory_entry::last_write_time", __ec,
+ make_error_code(errc::value_too_large));
+ return __data_.__write_time_;
+ }
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+private:
+ _Path __p_;
+ __cached_data __data_;
+};
+
+class __dir_element_proxy {
+public:
+ inline _LIBCPP_INLINE_VISIBILITY directory_entry operator*() {
+ return _VSTD::move(__elem_);
+ }
+
+private:
+ friend class directory_iterator;
+ friend class recursive_directory_iterator;
+ explicit __dir_element_proxy(directory_entry const& __e) : __elem_(__e) {}
+ __dir_element_proxy(__dir_element_proxy&& __o)
+ : __elem_(_VSTD::move(__o.__elem_)) {}
+ directory_entry __elem_;
+};
+
+class directory_iterator {
+public:
+ typedef directory_entry value_type;
+ typedef ptrdiff_t difference_type;
+ typedef value_type const* pointer;
+ typedef value_type const& reference;
+ typedef input_iterator_tag iterator_category;
+
+public:
+ //ctor & dtor
+ directory_iterator() noexcept {}
+
+ explicit directory_iterator(const path& __p)
+ : directory_iterator(__p, nullptr) {}
+
+ directory_iterator(const path& __p, directory_options __opts)
+ : directory_iterator(__p, nullptr, __opts) {}
+
+ directory_iterator(const path& __p, error_code& __ec)
+ : directory_iterator(__p, &__ec) {}
+
+ directory_iterator(const path& __p, directory_options __opts,
+ error_code& __ec)
+ : directory_iterator(__p, &__ec, __opts) {}
+
+ directory_iterator(const directory_iterator&) = default;
+ directory_iterator(directory_iterator&&) = default;
+ directory_iterator& operator=(const directory_iterator&) = default;
+
+ directory_iterator& operator=(directory_iterator&& __o) noexcept {
+ // non-default implementation provided to support self-move assign.
+ if (this != &__o) {
+ __imp_ = _VSTD::move(__o.__imp_);
+ }
+ return *this;
+ }
+
+ ~directory_iterator() = default;
+
+ const directory_entry& operator*() const {
+ _LIBCPP_ASSERT(__imp_, "The end iterator cannot be dereferenced");
+ return __dereference();
+ }
+
+ const directory_entry* operator->() const { return &**this; }
+
+ directory_iterator& operator++() { return __increment(); }
+
+ __dir_element_proxy operator++(int) {
+ __dir_element_proxy __p(**this);
+ __increment();
+ return __p;
+ }
+
+ directory_iterator& increment(error_code& __ec) { return __increment(&__ec); }
+
+private:
+ inline _LIBCPP_INLINE_VISIBILITY friend bool
+ operator==(const directory_iterator& __lhs,
+ const directory_iterator& __rhs) noexcept;
+
+ // construct the dir_stream
+ _LIBCPP_FUNC_VIS
+ directory_iterator(const path&, error_code*,
+ directory_options = directory_options::none);
+
+ _LIBCPP_FUNC_VIS
+ directory_iterator& __increment(error_code* __ec = nullptr);
+
+ _LIBCPP_FUNC_VIS
+ const directory_entry& __dereference() const;
+
+private:
+ shared_ptr<__dir_stream> __imp_;
+};
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+operator==(const directory_iterator& __lhs,
+ const directory_iterator& __rhs) noexcept {
+ return __lhs.__imp_ == __rhs.__imp_;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+operator!=(const directory_iterator& __lhs,
+ const directory_iterator& __rhs) noexcept {
+ return !(__lhs == __rhs);
+}
+
+// enable directory_iterator range-based for statements
+inline _LIBCPP_INLINE_VISIBILITY directory_iterator
+begin(directory_iterator __iter) noexcept {
+ return __iter;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY directory_iterator
+end(const directory_iterator&) noexcept {
+ return directory_iterator();
+}
+
+class recursive_directory_iterator {
+public:
+ using value_type = directory_entry;
+ using difference_type = std::ptrdiff_t;
+ using pointer = directory_entry const*;
+ using reference = directory_entry const&;
+ using iterator_category = std::input_iterator_tag;
+
+public:
+ // constructors and destructor
+ _LIBCPP_INLINE_VISIBILITY
+ recursive_directory_iterator() noexcept : __rec_(false) {}
+
+ _LIBCPP_INLINE_VISIBILITY
+ explicit recursive_directory_iterator(
+ const path& __p, directory_options __xoptions = directory_options::none)
+ : recursive_directory_iterator(__p, __xoptions, nullptr) {}
+
+ _LIBCPP_INLINE_VISIBILITY
+ recursive_directory_iterator(const path& __p, directory_options __xoptions,
+ error_code& __ec)
+ : recursive_directory_iterator(__p, __xoptions, &__ec) {}
+
+ _LIBCPP_INLINE_VISIBILITY
+ recursive_directory_iterator(const path& __p, error_code& __ec)
+ : recursive_directory_iterator(__p, directory_options::none, &__ec) {}
+
+ recursive_directory_iterator(const recursive_directory_iterator&) = default;
+ recursive_directory_iterator(recursive_directory_iterator&&) = default;
+
+ recursive_directory_iterator&
+ operator=(const recursive_directory_iterator&) = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ recursive_directory_iterator&
+ operator=(recursive_directory_iterator&& __o) noexcept {
+ // non-default implementation provided to support self-move assign.
+ if (this != &__o) {
+ __imp_ = _VSTD::move(__o.__imp_);
+ __rec_ = __o.__rec_;
+ }
+ return *this;
+ }
+
+ ~recursive_directory_iterator() = default;
+
+ _LIBCPP_INLINE_VISIBILITY
+ const directory_entry& operator*() const { return __dereference(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ const directory_entry* operator->() const { return &__dereference(); }
+
+ recursive_directory_iterator& operator++() { return __increment(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ __dir_element_proxy operator++(int) {
+ __dir_element_proxy __p(**this);
+ __increment();
+ return __p;
+ }
+
+ _LIBCPP_INLINE_VISIBILITY
+ recursive_directory_iterator& increment(error_code& __ec) {
+ return __increment(&__ec);
+ }
+
+ _LIBCPP_FUNC_VIS directory_options options() const;
+ _LIBCPP_FUNC_VIS int depth() const;
+
+ _LIBCPP_INLINE_VISIBILITY
+ void pop() { __pop(); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void pop(error_code& __ec) { __pop(&__ec); }
+
+ _LIBCPP_INLINE_VISIBILITY
+ bool recursion_pending() const { return __rec_; }
+
+ _LIBCPP_INLINE_VISIBILITY
+ void disable_recursion_pending() { __rec_ = false; }
+
+private:
+ recursive_directory_iterator(const path& __p, directory_options __opt,
+ error_code* __ec);
+
+ _LIBCPP_FUNC_VIS
+ const directory_entry& __dereference() const;
+
+ _LIBCPP_FUNC_VIS
+ bool __try_recursion(error_code* __ec);
+
+ _LIBCPP_FUNC_VIS
+ void __advance(error_code* __ec = nullptr);
+
+ _LIBCPP_FUNC_VIS
+ recursive_directory_iterator& __increment(error_code* __ec = nullptr);
+
+ _LIBCPP_FUNC_VIS
+ void __pop(error_code* __ec = nullptr);
+
+ inline _LIBCPP_INLINE_VISIBILITY friend bool
+ operator==(const recursive_directory_iterator&,
+ const recursive_directory_iterator&) noexcept;
+
+ struct __shared_imp;
+ shared_ptr<__shared_imp> __imp_;
+ bool __rec_;
+}; // class recursive_directory_iterator
+
+inline _LIBCPP_INLINE_VISIBILITY bool
+operator==(const recursive_directory_iterator& __lhs,
+ const recursive_directory_iterator& __rhs) noexcept {
+ return __lhs.__imp_ == __rhs.__imp_;
+}
+
+_LIBCPP_INLINE_VISIBILITY
+inline bool operator!=(const recursive_directory_iterator& __lhs,
+ const recursive_directory_iterator& __rhs) noexcept {
+ return !(__lhs == __rhs);
+}
+// enable recursive_directory_iterator range-based for statements
+inline _LIBCPP_INLINE_VISIBILITY recursive_directory_iterator
+begin(recursive_directory_iterator __iter) noexcept {
+ return __iter;
+}
+
+inline _LIBCPP_INLINE_VISIBILITY recursive_directory_iterator
+end(const recursive_directory_iterator&) noexcept {
+ return recursive_directory_iterator();
+}
+
+} // namespace android::hardware::automotive::filesystem
+#ifdef _LIBAUTO_UNDEF_VSTD
+#undef _VSTD
+#undef _LIBAUTO_UNDEF_VSTD
+#endif
+
+#ifndef _LIBAUTO_UNDEF_VSTD_FS
+#pragma pop_macro("_VSTD_FS")
+#else
+#undef _VSTD
+#undef _LIBAUTO_UNDEF_VSTD_FS
+#endif
+
+#endif // !_LIBCPP_CXX03_LANG
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBAUTO_FILESYSTEM
diff --git a/automotive/can/1.0/default/libc++fs/src b/automotive/can/1.0/default/libc++fs/src
deleted file mode 120000
index 7abb4ba..0000000
--- a/automotive/can/1.0/default/libc++fs/src
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../external/libcxx/src/
\ No newline at end of file
diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp b/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp
new file mode 100644
index 0000000..624538b
--- /dev/null
+++ b/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp
@@ -0,0 +1,397 @@
+//===------------------ directory_iterator.cpp ----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/* clang-format off */
+#include "automotive/filesystem"
+#include <__config>
+#if defined(_LIBCPP_WIN32API)
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#else
+#include <dirent.h>
+#endif
+#include <errno.h>
+
+#include "filesystem_common.h"
+
+namespace android::hardware::automotive::filesystem {
+
+namespace detail {
+namespace {
+
+#if !defined(_LIBCPP_WIN32API)
+template <class DirEntT, class = decltype(DirEntT::d_type)>
+static file_type get_file_type(DirEntT* ent, int) {
+ switch (ent->d_type) {
+ case DT_BLK:
+ return file_type::block;
+ case DT_CHR:
+ return file_type::character;
+ case DT_DIR:
+ return file_type::directory;
+ case DT_FIFO:
+ return file_type::fifo;
+ case DT_LNK:
+ return file_type::symlink;
+ case DT_REG:
+ return file_type::regular;
+ case DT_SOCK:
+ return file_type::socket;
+ // Unlike in lstat, hitting "unknown" here simply means that the underlying
+ // filesystem doesn't support d_type. Report is as 'none' so we correctly
+ // set the cache to empty.
+ case DT_UNKNOWN:
+ break;
+ }
+ return file_type::none;
+}
+
+template <class DirEntT>
+static file_type get_file_type(DirEntT* ent, long) {
+ return file_type::none;
+}
+
+static pair<string_view, file_type> posix_readdir(DIR* dir_stream,
+ error_code& ec) {
+ struct dirent* dir_entry_ptr = nullptr;
+ errno = 0; // zero errno in order to detect errors
+ ec.clear();
+ if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
+ if (errno)
+ ec = capture_errno();
+ return {};
+ } else {
+ return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
+ }
+}
+#else
+
+static file_type get_file_type(const WIN32_FIND_DATA& data) {
+ //auto attrs = data.dwFileAttributes;
+ // FIXME(EricWF)
+ return file_type::unknown;
+}
+static uintmax_t get_file_size(const WIN32_FIND_DATA& data) {
+ return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow;
+}
+static file_time_type get_write_time(const WIN32_FIND_DATA& data) {
+ ULARGE_INTEGER tmp;
+ FILETIME& time = data.ftLastWriteTime;
+ tmp.u.LowPart = time.dwLowDateTime;
+ tmp.u.HighPart = time.dwHighDateTime;
+ return file_time_type(file_time_type::duration(time.QuadPart));
+}
+
+#endif
+
+} // namespace
+} // namespace detail
+
+using detail::ErrorHandler;
+
+#if defined(_LIBCPP_WIN32API)
+class __dir_stream {
+public:
+ __dir_stream() = delete;
+ __dir_stream& operator=(const __dir_stream&) = delete;
+
+ __dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_),
+ __root_(move(__ds.__root_)),
+ __entry_(move(__ds.__entry_)) {
+ __ds.__stream_ = INVALID_HANDLE_VALUE;
+ }
+
+ __dir_stream(const path& root, directory_options opts, error_code& ec)
+ : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
+ __stream_ = ::FindFirstFileEx(root.c_str(), &__data_);
+ if (__stream_ == INVALID_HANDLE_VALUE) {
+ ec = error_code(::GetLastError(), generic_category());
+ const bool ignore_permission_denied =
+ bool(opts & directory_options::skip_permission_denied);
+ if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
+ ec.clear();
+ return;
+ }
+ }
+
+ ~__dir_stream() noexcept {
+ if (__stream_ == INVALID_HANDLE_VALUE)
+ return;
+ close();
+ }
+
+ bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
+
+ bool advance(error_code& ec) {
+ while (::FindNextFile(__stream_, &__data_)) {
+ if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
+ continue;
+ // FIXME: Cache more of this
+ //directory_entry::__cached_data cdata;
+ //cdata.__type_ = get_file_type(__data_);
+ //cdata.__size_ = get_file_size(__data_);
+ //cdata.__write_time_ = get_write_time(__data_);
+ __entry_.__assign_iter_entry(
+ __root_ / __data_.cFileName,
+ directory_entry::__create_iter_result(get_file_type(__data)));
+ return true;
+ }
+ ec = error_code(::GetLastError(), generic_category());
+ close();
+ return false;
+ }
+
+private:
+ error_code close() noexcept {
+ error_code ec;
+ if (!::FindClose(__stream_))
+ ec = error_code(::GetLastError(), generic_category());
+ __stream_ = INVALID_HANDLE_VALUE;
+ return ec;
+ }
+
+ HANDLE __stream_{INVALID_HANDLE_VALUE};
+ WIN32_FIND_DATA __data_;
+
+public:
+ path __root_;
+ directory_entry __entry_;
+};
+#else
+class __dir_stream {
+public:
+ __dir_stream() = delete;
+ __dir_stream& operator=(const __dir_stream&) = delete;
+
+ __dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_),
+ __root_(move(other.__root_)),
+ __entry_(move(other.__entry_)) {
+ other.__stream_ = nullptr;
+ }
+
+ __dir_stream(const path& root, directory_options opts, error_code& ec)
+ : __stream_(nullptr), __root_(root) {
+ if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
+ ec = detail::capture_errno();
+ const bool allow_eacess =
+ bool(opts & directory_options::skip_permission_denied);
+ if (allow_eacess && ec.value() == EACCES)
+ ec.clear();
+ return;
+ }
+ advance(ec);
+ }
+
+ ~__dir_stream() noexcept {
+ if (__stream_)
+ close();
+ }
+
+ bool good() const noexcept { return __stream_ != nullptr; }
+
+ bool advance(error_code& ec) {
+ while (true) {
+ auto str_type_pair = detail::posix_readdir(__stream_, ec);
+ auto& str = str_type_pair.first;
+ if (str == "." || str == "..") {
+ continue;
+ } else if (ec || str.empty()) {
+ close();
+ return false;
+ } else {
+ __entry_.__assign_iter_entry(
+ __root_ / str,
+ directory_entry::__create_iter_result(str_type_pair.second));
+ return true;
+ }
+ }
+ }
+
+private:
+ error_code close() noexcept {
+ error_code m_ec;
+ if (::closedir(__stream_) == -1)
+ m_ec = detail::capture_errno();
+ __stream_ = nullptr;
+ return m_ec;
+ }
+
+ DIR* __stream_{nullptr};
+
+public:
+ path __root_;
+ directory_entry __entry_;
+};
+#endif
+
+// directory_iterator
+
+directory_iterator::directory_iterator(const path& p, error_code* ec,
+ directory_options opts) {
+ ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
+
+ error_code m_ec;
+ __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
+ if (ec)
+ *ec = m_ec;
+ if (!__imp_->good()) {
+ __imp_.reset();
+ if (m_ec)
+ err.report(m_ec);
+ }
+}
+
+directory_iterator& directory_iterator::__increment(error_code* ec) {
+ _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
+ ErrorHandler<void> err("directory_iterator::operator++()", ec);
+
+ error_code m_ec;
+ if (!__imp_->advance(m_ec)) {
+ path root = move(__imp_->__root_);
+ __imp_.reset();
+ if (m_ec)
+ err.report(m_ec, "at root \"%s\"", root);
+ }
+ return *this;
+}
+
+directory_entry const& directory_iterator::__dereference() const {
+ _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
+ return __imp_->__entry_;
+}
+
+// recursive_directory_iterator
+
+struct recursive_directory_iterator::__shared_imp {
+ stack<__dir_stream> __stack_;
+ directory_options __options_;
+};
+
+recursive_directory_iterator::recursive_directory_iterator(
+ const path& p, directory_options opt, error_code* ec)
+ : __imp_(nullptr), __rec_(true) {
+ ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
+
+ error_code m_ec;
+ __dir_stream new_s(p, opt, m_ec);
+ if (m_ec)
+ err.report(m_ec);
+ if (m_ec || !new_s.good())
+ return;
+
+ __imp_ = make_shared<__shared_imp>();
+ __imp_->__options_ = opt;
+ __imp_->__stack_.push(move(new_s));
+}
+
+void recursive_directory_iterator::__pop(error_code* ec) {
+ _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
+ if (ec)
+ ec->clear();
+ __imp_->__stack_.pop();
+ if (__imp_->__stack_.size() == 0)
+ __imp_.reset();
+ else
+ __advance(ec);
+}
+
+directory_options recursive_directory_iterator::options() const {
+ return __imp_->__options_;
+}
+
+int recursive_directory_iterator::depth() const {
+ return __imp_->__stack_.size() - 1;
+}
+
+const directory_entry& recursive_directory_iterator::__dereference() const {
+ return __imp_->__stack_.top().__entry_;
+}
+
+recursive_directory_iterator&
+recursive_directory_iterator::__increment(error_code* ec) {
+ if (ec)
+ ec->clear();
+ if (recursion_pending()) {
+ if (__try_recursion(ec) || (ec && *ec))
+ return *this;
+ }
+ __rec_ = true;
+ __advance(ec);
+ return *this;
+}
+
+void recursive_directory_iterator::__advance(error_code* ec) {
+ ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
+
+ const directory_iterator end_it;
+ auto& stack = __imp_->__stack_;
+ error_code m_ec;
+ while (stack.size() > 0) {
+ if (stack.top().advance(m_ec))
+ return;
+ if (m_ec)
+ break;
+ stack.pop();
+ }
+
+ if (m_ec) {
+ path root = move(stack.top().__root_);
+ __imp_.reset();
+ err.report(m_ec, "at root \"%s\"", root);
+ } else {
+ __imp_.reset();
+ }
+}
+
+bool recursive_directory_iterator::__try_recursion(error_code* ec) {
+ ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
+
+ bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
+
+ auto& curr_it = __imp_->__stack_.top();
+
+ bool skip_rec = false;
+ error_code m_ec;
+ if (!rec_sym) {
+ file_status st(curr_it.__entry_.__get_sym_ft(&m_ec));
+ if (m_ec && status_known(st))
+ m_ec.clear();
+ if (m_ec || is_symlink(st) || !is_directory(st))
+ skip_rec = true;
+ } else {
+ file_status st(curr_it.__entry_.__get_ft(&m_ec));
+ if (m_ec && status_known(st))
+ m_ec.clear();
+ if (m_ec || !is_directory(st))
+ skip_rec = true;
+ }
+
+ if (!skip_rec) {
+ __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
+ if (new_it.good()) {
+ __imp_->__stack_.push(move(new_it));
+ return true;
+ }
+ }
+ if (m_ec) {
+ const bool allow_eacess =
+ bool(__imp_->__options_ & directory_options::skip_permission_denied);
+ if (m_ec.value() == EACCES && allow_eacess) {
+ if (ec)
+ ec->clear();
+ } else {
+ path at_ent = move(curr_it.__entry_.__p_);
+ __imp_.reset();
+ err.report(m_ec, "attempting recursion into \"%s\"", at_ent);
+ }
+ }
+ return false;
+}
+
+} // namespace android::hardware::automotive::filesystem
+/* clang-format on */
diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h b/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h
new file mode 100644
index 0000000..4f44661
--- /dev/null
+++ b/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h
@@ -0,0 +1,441 @@
+//===----------------------------------------------------------------------===////
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===////
+/* clang-format off */
+#ifndef AUTO_FILESYSTEM_COMMON_H
+#define AUTO_FILESYSTEM_COMMON_H
+
+#include "automotive/filesystem"
+#include <array>
+#include <chrono>
+#include <cstdlib>
+#include <climits>
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/time.h> // for ::utimes as used in __last_write_time
+#include <fcntl.h> /* values for fchmodat */
+
+#if !defined(__APPLE__)
+// We can use the presence of UTIME_OMIT to detect platforms that provide
+// utimensat.
+#if defined(UTIME_OMIT)
+#define _LIBCPP_USE_UTIMENSAT
+#endif
+#endif
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+namespace android::hardware::automotive::filesystem {
+using namespace std::chrono;
+
+using std::error_code;
+using std::is_floating_point;
+using std::micro;
+using std::nano;
+using std::ratio;
+
+namespace detail {
+namespace {
+
+static string format_string_imp(const char* msg, ...) {
+ // we might need a second shot at this, so pre-emptivly make a copy
+ struct GuardVAList {
+ va_list& target;
+ bool active = true;
+ GuardVAList(va_list& target) : target(target), active(true) {}
+ void clear() {
+ if (active)
+ va_end(target);
+ active = false;
+ }
+ ~GuardVAList() {
+ if (active)
+ va_end(target);
+ }
+ };
+ va_list args;
+ va_start(args, msg);
+ GuardVAList args_guard(args);
+
+ va_list args_cp;
+ va_copy(args_cp, args);
+ GuardVAList args_copy_guard(args_cp);
+
+ std::string result;
+
+ array<char, 256> local_buff;
+ size_t size_with_null = local_buff.size();
+ auto ret = ::vsnprintf(local_buff.data(), size_with_null, msg, args_cp);
+
+ args_copy_guard.clear();
+
+ // handle empty expansion
+ if (ret == 0)
+ return result;
+ if (static_cast<size_t>(ret) < size_with_null) {
+ result.assign(local_buff.data(), static_cast<size_t>(ret));
+ return result;
+ }
+
+ // we did not provide a long enough buffer on our first attempt. The
+ // return value is the number of bytes (excluding the null byte) that are
+ // needed for formatting.
+ size_with_null = static_cast<size_t>(ret) + 1;
+ result.__resize_default_init(size_with_null - 1);
+ ret = ::vsnprintf(&result[0], size_with_null, msg, args);
+ _LIBCPP_ASSERT(static_cast<size_t>(ret) == (size_with_null - 1), "TODO");
+
+ return result;
+}
+
+const char* unwrap(string const& s) { return s.c_str(); }
+const char* unwrap(path const& p) { return p.native().c_str(); }
+template <class Arg>
+Arg const& unwrap(Arg const& a) {
+ static_assert(!is_class<Arg>::value, "cannot pass class here");
+ return a;
+}
+
+template <class... Args>
+string format_string(const char* fmt, Args const&... args) {
+ return format_string_imp(fmt, unwrap(args)...);
+}
+
+error_code capture_errno() {
+ _LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
+ return error_code(errno, generic_category());
+}
+
+template <class T>
+T error_value();
+template <>
+_LIBCPP_CONSTEXPR_AFTER_CXX11 void error_value<void>() {}
+template <>
+bool error_value<bool>() {
+ return false;
+}
+template <>
+uintmax_t error_value<uintmax_t>() {
+ return uintmax_t(-1);
+}
+template <>
+_LIBCPP_CONSTEXPR_AFTER_CXX11 file_time_type error_value<file_time_type>() {
+ return file_time_type::min();
+}
+template <>
+path error_value<path>() {
+ return {};
+}
+
+template <class T>
+struct ErrorHandler {
+ const char* func_name;
+ error_code* ec = nullptr;
+ const path* p1 = nullptr;
+ const path* p2 = nullptr;
+
+ ErrorHandler(const char* fname, error_code* ec, const path* p1 = nullptr,
+ const path* p2 = nullptr)
+ : func_name(fname), ec(ec), p1(p1), p2(p2) {
+ if (ec)
+ ec->clear();
+ }
+
+ T report(const error_code& m_ec) const {
+ if (ec) {
+ *ec = m_ec;
+ return error_value<T>();
+ }
+ string what = string("in ") + func_name;
+ switch (bool(p1) + bool(p2)) {
+ case 0:
+ __throw_filesystem_error(what, m_ec);
+ case 1:
+ __throw_filesystem_error(what, *p1, m_ec);
+ case 2:
+ __throw_filesystem_error(what, *p1, *p2, m_ec);
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ template <class... Args>
+ T report(const error_code& m_ec, const char* msg, Args const&... args) const {
+ if (ec) {
+ *ec = m_ec;
+ return error_value<T>();
+ }
+ string what =
+ string("in ") + func_name + ": " + format_string(msg, args...);
+ switch (bool(p1) + bool(p2)) {
+ case 0:
+ __throw_filesystem_error(what, m_ec);
+ case 1:
+ __throw_filesystem_error(what, *p1, m_ec);
+ case 2:
+ __throw_filesystem_error(what, *p1, *p2, m_ec);
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ T report(errc const& err) const { return report(make_error_code(err)); }
+
+ template <class... Args>
+ T report(errc const& err, const char* msg, Args const&... args) const {
+ return report(make_error_code(err), msg, args...);
+ }
+
+private:
+ ErrorHandler(ErrorHandler const&) = delete;
+ ErrorHandler& operator=(ErrorHandler const&) = delete;
+};
+
+using chrono::duration;
+using chrono::duration_cast;
+
+using TimeSpec = struct ::timespec;
+using StatT = struct ::stat;
+
+template <class FileTimeT, class TimeT,
+ bool IsFloat = is_floating_point<typename FileTimeT::rep>::value>
+struct time_util_base {
+ using rep = typename FileTimeT::rep;
+ using fs_duration = typename FileTimeT::duration;
+ using fs_seconds = duration<rep>;
+ using fs_nanoseconds = duration<rep, nano>;
+ using fs_microseconds = duration<rep, micro>;
+
+ static constexpr rep max_seconds =
+ duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
+
+ static constexpr rep max_nsec =
+ duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
+ fs_seconds(max_seconds))
+ .count();
+
+ static constexpr rep min_seconds =
+ duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
+
+ static constexpr rep min_nsec_timespec =
+ duration_cast<fs_nanoseconds>(
+ (FileTimeT::duration::min() - fs_seconds(min_seconds)) +
+ fs_seconds(1))
+ .count();
+
+private:
+#if _LIBCPP_STD_VER > 11 && !defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR)
+ static constexpr fs_duration get_min_nsecs() {
+ return duration_cast<fs_duration>(
+ fs_nanoseconds(min_nsec_timespec) -
+ duration_cast<fs_nanoseconds>(fs_seconds(1)));
+ }
+ // Static assert that these values properly round trip.
+ static_assert(fs_seconds(min_seconds) + get_min_nsecs() ==
+ FileTimeT::duration::min(),
+ "value doesn't roundtrip");
+
+ static constexpr bool check_range() {
+ // This kinda sucks, but it's what happens when we don't have __int128_t.
+ if (sizeof(TimeT) == sizeof(rep)) {
+ typedef duration<long long, ratio<3600 * 24 * 365> > Years;
+ return duration_cast<Years>(fs_seconds(max_seconds)) > Years(250) &&
+ duration_cast<Years>(fs_seconds(min_seconds)) < Years(-250);
+ }
+ return max_seconds >= numeric_limits<TimeT>::max() &&
+ min_seconds <= numeric_limits<TimeT>::min();
+ }
+ static_assert(check_range(), "the representable range is unacceptable small");
+#endif
+};
+
+template <class FileTimeT, class TimeT>
+struct time_util_base<FileTimeT, TimeT, true> {
+ using rep = typename FileTimeT::rep;
+ using fs_duration = typename FileTimeT::duration;
+ using fs_seconds = duration<rep>;
+ using fs_nanoseconds = duration<rep, nano>;
+ using fs_microseconds = duration<rep, micro>;
+
+ static const rep max_seconds;
+ static const rep max_nsec;
+ static const rep min_seconds;
+ static const rep min_nsec_timespec;
+};
+
+template <class FileTimeT, class TimeT>
+const typename FileTimeT::rep
+ time_util_base<FileTimeT, TimeT, true>::max_seconds =
+ duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
+
+template <class FileTimeT, class TimeT>
+const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::max_nsec =
+ duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
+ fs_seconds(max_seconds))
+ .count();
+
+template <class FileTimeT, class TimeT>
+const typename FileTimeT::rep
+ time_util_base<FileTimeT, TimeT, true>::min_seconds =
+ duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
+
+template <class FileTimeT, class TimeT>
+const typename FileTimeT::rep
+ time_util_base<FileTimeT, TimeT, true>::min_nsec_timespec =
+ duration_cast<fs_nanoseconds>((FileTimeT::duration::min() -
+ fs_seconds(min_seconds)) +
+ fs_seconds(1))
+ .count();
+
+template <class FileTimeT, class TimeT, class TimeSpecT>
+struct time_util : time_util_base<FileTimeT, TimeT> {
+ using Base = time_util_base<FileTimeT, TimeT>;
+ using Base::max_nsec;
+ using Base::max_seconds;
+ using Base::min_nsec_timespec;
+ using Base::min_seconds;
+
+ using typename Base::fs_duration;
+ using typename Base::fs_microseconds;
+ using typename Base::fs_nanoseconds;
+ using typename Base::fs_seconds;
+
+public:
+ template <class CType, class ChronoType>
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out,
+ ChronoType time) {
+ using Lim = numeric_limits<CType>;
+ if (time > Lim::max() || time < Lim::min())
+ return false;
+ *out = static_cast<CType>(time);
+ return true;
+ }
+
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) {
+ if (tm.tv_sec >= 0) {
+ return tm.tv_sec < max_seconds ||
+ (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec);
+ } else if (tm.tv_sec == (min_seconds - 1)) {
+ return tm.tv_nsec >= min_nsec_timespec;
+ } else {
+ return tm.tv_sec >= min_seconds;
+ }
+ }
+
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) {
+ auto secs = duration_cast<fs_seconds>(tm.time_since_epoch());
+ auto nsecs = duration_cast<fs_nanoseconds>(tm.time_since_epoch() - secs);
+ if (nsecs.count() < 0) {
+ secs = secs + fs_seconds(1);
+ nsecs = nsecs + fs_seconds(1);
+ }
+ using TLim = numeric_limits<TimeT>;
+ if (secs.count() >= 0)
+ return secs.count() <= TLim::max();
+ return secs.count() >= TLim::min();
+ }
+
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT
+ convert_from_timespec(TimeSpecT tm) {
+ if (tm.tv_sec >= 0 || tm.tv_nsec == 0) {
+ return FileTimeT(fs_seconds(tm.tv_sec) +
+ duration_cast<fs_duration>(fs_nanoseconds(tm.tv_nsec)));
+ } else { // tm.tv_sec < 0
+ auto adj_subsec = duration_cast<fs_duration>(fs_seconds(1) -
+ fs_nanoseconds(tm.tv_nsec));
+ auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec;
+ return FileTimeT(Dur);
+ }
+ }
+
+ template <class SubSecT>
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool
+ set_times_checked(TimeT* sec_out, SubSecT* subsec_out, FileTimeT tp) {
+ auto dur = tp.time_since_epoch();
+ auto sec_dur = duration_cast<fs_seconds>(dur);
+ auto subsec_dur = duration_cast<fs_nanoseconds>(dur - sec_dur);
+ // The tv_nsec and tv_usec fields must not be negative so adjust accordingly
+ if (subsec_dur.count() < 0) {
+ if (sec_dur.count() > min_seconds) {
+ sec_dur = sec_dur - fs_seconds(1);
+ subsec_dur = subsec_dur + fs_seconds(1);
+ } else {
+ subsec_dur = fs_nanoseconds::zero();
+ }
+ }
+ return checked_set(sec_out, sec_dur.count()) &&
+ checked_set(subsec_out, subsec_dur.count());
+ }
+ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool convert_to_timespec(TimeSpecT& dest,
+ FileTimeT tp) {
+ if (!is_representable(tp))
+ return false;
+ return set_times_checked(&dest.tv_sec, &dest.tv_nsec, tp);
+ }
+};
+
+using fs_time = time_util<file_time_type, time_t, TimeSpec>;
+
+#if defined(__APPLE__)
+TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
+TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
+#else
+TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
+TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
+#endif
+
+// allow the utimes implementation to compile even it we're not going
+// to use it.
+
+bool posix_utimes(const path& p, std::array<TimeSpec, 2> const& TS,
+ error_code& ec) {
+ using namespace chrono;
+ auto Convert = [](long nsec) {
+ using int_type = decltype(std::declval< ::timeval>().tv_usec);
+ auto dur = duration_cast<microseconds>(nanoseconds(nsec)).count();
+ return static_cast<int_type>(dur);
+ };
+ struct ::timeval ConvertedTS[2] = {{TS[0].tv_sec, Convert(TS[0].tv_nsec)},
+ {TS[1].tv_sec, Convert(TS[1].tv_nsec)}};
+ if (::utimes(p.c_str(), ConvertedTS) == -1) {
+ ec = capture_errno();
+ return true;
+ }
+ return false;
+}
+
+#if defined(_LIBCPP_USE_UTIMENSAT)
+bool posix_utimensat(const path& p, std::array<TimeSpec, 2> const& TS,
+ error_code& ec) {
+ if (::utimensat(AT_FDCWD, p.c_str(), TS.data(), 0) == -1) {
+ ec = capture_errno();
+ return true;
+ }
+ return false;
+}
+#endif
+
+bool set_file_times(const path& p, std::array<TimeSpec, 2> const& TS,
+ error_code& ec) {
+#if !defined(_LIBCPP_USE_UTIMENSAT)
+ return posix_utimes(p, TS, ec);
+#else
+ return posix_utimensat(p, TS, ec);
+#endif
+}
+
+} // namespace
+} // end namespace detail
+
+} // namespace android::hardware::automotive::filesystem
+
+#endif // AUTO_FILESYSTEM_COMMON_H
+/* clang-format on */
diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp b/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp
new file mode 100644
index 0000000..404c0bd
--- /dev/null
+++ b/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp
@@ -0,0 +1,1773 @@
+//===--------------------- filesystem/ops.cpp -----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/* clang-format off */
+#include "automotive/filesystem"
+#include <array>
+#include <iterator>
+#include <fstream>
+#include <random> /* for unique_path */
+#include <string_view>
+#include <type_traits>
+#include <vector>
+#include <cstdlib>
+#include <climits>
+
+#include "filesystem_common.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <time.h>
+#include <fcntl.h> /* values for fchmodat */
+
+#if defined(__linux__)
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
+#include <sys/sendfile.h>
+#define _LIBCPP_USE_SENDFILE
+#endif
+#elif defined(__APPLE__) || __has_include(<copyfile.h>)
+#include <copyfile.h>
+#define _LIBCPP_USE_COPYFILE
+#endif
+
+#if !defined(__APPLE__)
+#define _LIBCPP_USE_CLOCK_GETTIME
+#endif
+
+#if !defined(CLOCK_REALTIME) || !defined(_LIBCPP_USE_CLOCK_GETTIME)
+#include <sys/time.h> // for gettimeofday and timeval
+#endif // !defined(CLOCK_REALTIME)
+
+#if defined(_LIBCPP_COMPILER_GCC)
+#if _GNUC_VER < 500
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+#endif
+
+namespace android::hardware::automotive::filesystem {
+
+#ifdef _VSTD_FS
+#pragma push_macro("_VSTD_FS")
+#else
+#define _LIBAUTO_UNDEF_VSTD_FS
+#endif
+#define _VSTD_FS android::hardware::automotive::filesystem
+
+namespace {
+namespace parser {
+
+using string_view_t = path::__string_view;
+using string_view_pair = pair<string_view_t, string_view_t>;
+using PosPtr = path::value_type const*;
+
+struct PathParser {
+ enum ParserState : unsigned char {
+ // Zero is a special sentinel value used by default constructed iterators.
+ PS_BeforeBegin = path::iterator::_BeforeBegin,
+ PS_InRootName = path::iterator::_InRootName,
+ PS_InRootDir = path::iterator::_InRootDir,
+ PS_InFilenames = path::iterator::_InFilenames,
+ PS_InTrailingSep = path::iterator::_InTrailingSep,
+ PS_AtEnd = path::iterator::_AtEnd
+ };
+
+ const string_view_t Path;
+ string_view_t RawEntry;
+ ParserState State;
+
+private:
+ PathParser(string_view_t P, ParserState State) noexcept : Path(P),
+ State(State) {}
+
+public:
+ PathParser(string_view_t P, string_view_t E, unsigned char S)
+ : Path(P), RawEntry(E), State(static_cast<ParserState>(S)) {
+ // S cannot be '0' or PS_BeforeBegin.
+ }
+
+ static PathParser CreateBegin(string_view_t P) noexcept {
+ PathParser PP(P, PS_BeforeBegin);
+ PP.increment();
+ return PP;
+ }
+
+ static PathParser CreateEnd(string_view_t P) noexcept {
+ PathParser PP(P, PS_AtEnd);
+ return PP;
+ }
+
+ PosPtr peek() const noexcept {
+ auto TkEnd = getNextTokenStartPos();
+ auto End = getAfterBack();
+ return TkEnd == End ? nullptr : TkEnd;
+ }
+
+ void increment() noexcept {
+ const PosPtr End = getAfterBack();
+ const PosPtr Start = getNextTokenStartPos();
+ if (Start == End)
+ return makeState(PS_AtEnd);
+
+ switch (State) {
+ case PS_BeforeBegin: {
+ PosPtr TkEnd = consumeSeparator(Start, End);
+ if (TkEnd)
+ return makeState(PS_InRootDir, Start, TkEnd);
+ else
+ return makeState(PS_InFilenames, Start, consumeName(Start, End));
+ }
+ case PS_InRootDir:
+ return makeState(PS_InFilenames, Start, consumeName(Start, End));
+
+ case PS_InFilenames: {
+ PosPtr SepEnd = consumeSeparator(Start, End);
+ if (SepEnd != End) {
+ PosPtr TkEnd = consumeName(SepEnd, End);
+ if (TkEnd)
+ return makeState(PS_InFilenames, SepEnd, TkEnd);
+ }
+ return makeState(PS_InTrailingSep, Start, SepEnd);
+ }
+
+ case PS_InTrailingSep:
+ return makeState(PS_AtEnd);
+
+ case PS_InRootName:
+ case PS_AtEnd:
+ _LIBCPP_UNREACHABLE();
+ }
+ }
+
+ void decrement() noexcept {
+ const PosPtr REnd = getBeforeFront();
+ const PosPtr RStart = getCurrentTokenStartPos() - 1;
+ if (RStart == REnd) // we're decrementing the begin
+ return makeState(PS_BeforeBegin);
+
+ switch (State) {
+ case PS_AtEnd: {
+ // Try to consume a trailing separator or root directory first.
+ if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) {
+ if (SepEnd == REnd)
+ return makeState(PS_InRootDir, Path.data(), RStart + 1);
+ return makeState(PS_InTrailingSep, SepEnd + 1, RStart + 1);
+ } else {
+ PosPtr TkStart = consumeName(RStart, REnd);
+ return makeState(PS_InFilenames, TkStart + 1, RStart + 1);
+ }
+ }
+ case PS_InTrailingSep:
+ return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1,
+ RStart + 1);
+ case PS_InFilenames: {
+ PosPtr SepEnd = consumeSeparator(RStart, REnd);
+ if (SepEnd == REnd)
+ return makeState(PS_InRootDir, Path.data(), RStart + 1);
+ PosPtr TkEnd = consumeName(SepEnd, REnd);
+ return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1);
+ }
+ case PS_InRootDir:
+ // return makeState(PS_InRootName, Path.data(), RStart + 1);
+ case PS_InRootName:
+ case PS_BeforeBegin:
+ _LIBCPP_UNREACHABLE();
+ }
+ }
+
+ /// \brief Return a view with the "preferred representation" of the current
+ /// element. For example trailing separators are represented as a '.'
+ string_view_t operator*() const noexcept {
+ switch (State) {
+ case PS_BeforeBegin:
+ case PS_AtEnd:
+ return "";
+ case PS_InRootDir:
+ return "/";
+ case PS_InTrailingSep:
+ return "";
+ case PS_InRootName:
+ case PS_InFilenames:
+ return RawEntry;
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ explicit operator bool() const noexcept {
+ return State != PS_BeforeBegin && State != PS_AtEnd;
+ }
+
+ PathParser& operator++() noexcept {
+ increment();
+ return *this;
+ }
+
+ PathParser& operator--() noexcept {
+ decrement();
+ return *this;
+ }
+
+ bool atEnd() const noexcept {
+ return State == PS_AtEnd;
+ }
+
+ bool inRootDir() const noexcept {
+ return State == PS_InRootDir;
+ }
+
+ bool inRootName() const noexcept {
+ return State == PS_InRootName;
+ }
+
+ bool inRootPath() const noexcept {
+ return inRootName() || inRootDir();
+ }
+
+private:
+ void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept {
+ State = NewState;
+ RawEntry = string_view_t(Start, End - Start);
+ }
+ void makeState(ParserState NewState) noexcept {
+ State = NewState;
+ RawEntry = {};
+ }
+
+ PosPtr getAfterBack() const noexcept { return Path.data() + Path.size(); }
+
+ PosPtr getBeforeFront() const noexcept { return Path.data() - 1; }
+
+ /// \brief Return a pointer to the first character after the currently
+ /// lexed element.
+ PosPtr getNextTokenStartPos() const noexcept {
+ switch (State) {
+ case PS_BeforeBegin:
+ return Path.data();
+ case PS_InRootName:
+ case PS_InRootDir:
+ case PS_InFilenames:
+ return &RawEntry.back() + 1;
+ case PS_InTrailingSep:
+ case PS_AtEnd:
+ return getAfterBack();
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ /// \brief Return a pointer to the first character in the currently lexed
+ /// element.
+ PosPtr getCurrentTokenStartPos() const noexcept {
+ switch (State) {
+ case PS_BeforeBegin:
+ case PS_InRootName:
+ return &Path.front();
+ case PS_InRootDir:
+ case PS_InFilenames:
+ case PS_InTrailingSep:
+ return &RawEntry.front();
+ case PS_AtEnd:
+ return &Path.back() + 1;
+ }
+ _LIBCPP_UNREACHABLE();
+ }
+
+ PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept {
+ if (P == End || *P != '/')
+ return nullptr;
+ const int Inc = P < End ? 1 : -1;
+ P += Inc;
+ while (P != End && *P == '/')
+ P += Inc;
+ return P;
+ }
+
+ PosPtr consumeName(PosPtr P, PosPtr End) const noexcept {
+ if (P == End || *P == '/')
+ return nullptr;
+ const int Inc = P < End ? 1 : -1;
+ P += Inc;
+ while (P != End && *P != '/')
+ P += Inc;
+ return P;
+ }
+};
+
+string_view_pair separate_filename(string_view_t const& s) {
+ if (s == "." || s == ".." || s.empty())
+ return string_view_pair{s, ""};
+ auto pos = s.find_last_of('.');
+ if (pos == string_view_t::npos || pos == 0)
+ return string_view_pair{s, string_view_t{}};
+ return string_view_pair{s.substr(0, pos), s.substr(pos)};
+}
+
+string_view_t createView(PosPtr S, PosPtr E) noexcept {
+ return {S, static_cast<size_t>(E - S) + 1};
+}
+
+} // namespace parser
+} // namespace
+
+// POSIX HELPERS
+
+namespace detail {
+namespace {
+
+using value_type = path::value_type;
+using string_type = path::string_type;
+
+struct FileDescriptor {
+ const path& name;
+ int fd = -1;
+ StatT m_stat;
+ file_status m_status;
+
+ template <class... Args>
+ static FileDescriptor create(const path* p, error_code& ec, Args... args) {
+ ec.clear();
+ int fd;
+ if ((fd = ::open(p->c_str(), args...)) == -1) {
+ ec = capture_errno();
+ return FileDescriptor{p};
+ }
+ return FileDescriptor(p, fd);
+ }
+
+ template <class... Args>
+ static FileDescriptor create_with_status(const path* p, error_code& ec,
+ Args... args) {
+ FileDescriptor fd = create(p, ec, args...);
+ if (!ec)
+ fd.refresh_status(ec);
+
+ return fd;
+ }
+
+ file_status get_status() const { return m_status; }
+ StatT const& get_stat() const { return m_stat; }
+
+ bool status_known() const { return _VSTD_FS::status_known(m_status); }
+
+ file_status refresh_status(error_code& ec);
+
+ void close() noexcept {
+ if (fd != -1)
+ ::close(fd);
+ fd = -1;
+ }
+
+ FileDescriptor(FileDescriptor&& other)
+ : name(other.name), fd(other.fd), m_stat(other.m_stat),
+ m_status(other.m_status) {
+ other.fd = -1;
+ other.m_status = file_status{};
+ }
+
+ ~FileDescriptor() { close(); }
+
+ FileDescriptor(FileDescriptor const&) = delete;
+ FileDescriptor& operator=(FileDescriptor const&) = delete;
+
+private:
+ explicit FileDescriptor(const path* p, int fd = -1) : name(*p), fd(fd) {}
+};
+
+perms posix_get_perms(const StatT& st) noexcept {
+ return static_cast<perms>(st.st_mode) & perms::mask;
+}
+
+::mode_t posix_convert_perms(perms prms) {
+ return static_cast< ::mode_t>(prms & perms::mask);
+}
+
+file_status create_file_status(error_code& m_ec, path const& p,
+ const StatT& path_stat, error_code* ec) {
+ if (ec)
+ *ec = m_ec;
+ if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) {
+ return file_status(file_type::not_found);
+ } else if (m_ec) {
+ ErrorHandler<void> err("posix_stat", ec, &p);
+ err.report(m_ec, "failed to determine attributes for the specified path");
+ return file_status(file_type::none);
+ }
+ // else
+
+ file_status fs_tmp;
+ auto const mode = path_stat.st_mode;
+ if (S_ISLNK(mode))
+ fs_tmp.type(file_type::symlink);
+ else if (S_ISREG(mode))
+ fs_tmp.type(file_type::regular);
+ else if (S_ISDIR(mode))
+ fs_tmp.type(file_type::directory);
+ else if (S_ISBLK(mode))
+ fs_tmp.type(file_type::block);
+ else if (S_ISCHR(mode))
+ fs_tmp.type(file_type::character);
+ else if (S_ISFIFO(mode))
+ fs_tmp.type(file_type::fifo);
+ else if (S_ISSOCK(mode))
+ fs_tmp.type(file_type::socket);
+ else
+ fs_tmp.type(file_type::unknown);
+
+ fs_tmp.permissions(detail::posix_get_perms(path_stat));
+ return fs_tmp;
+}
+
+file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) {
+ error_code m_ec;
+ if (::stat(p.c_str(), &path_stat) == -1)
+ m_ec = detail::capture_errno();
+ return create_file_status(m_ec, p, path_stat, ec);
+}
+
+file_status posix_stat(path const& p, error_code* ec) {
+ StatT path_stat;
+ return posix_stat(p, path_stat, ec);
+}
+
+file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) {
+ error_code m_ec;
+ if (::lstat(p.c_str(), &path_stat) == -1)
+ m_ec = detail::capture_errno();
+ return create_file_status(m_ec, p, path_stat, ec);
+}
+
+file_status posix_lstat(path const& p, error_code* ec) {
+ StatT path_stat;
+ return posix_lstat(p, path_stat, ec);
+}
+
+bool posix_ftruncate(const FileDescriptor& fd, size_t to_size, error_code& ec) {
+ if (::ftruncate(fd.fd, to_size) == -1) {
+ ec = capture_errno();
+ return true;
+ }
+ ec.clear();
+ return false;
+}
+
+bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) {
+ if (::fchmod(fd.fd, st.st_mode) == -1) {
+ ec = capture_errno();
+ return true;
+ }
+ ec.clear();
+ return false;
+}
+
+bool stat_equivalent(const StatT& st1, const StatT& st2) {
+ return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
+}
+
+file_status FileDescriptor::refresh_status(error_code& ec) {
+ // FD must be open and good.
+ m_status = file_status{};
+ m_stat = {};
+ error_code m_ec;
+ if (::fstat(fd, &m_stat) == -1)
+ m_ec = capture_errno();
+ m_status = create_file_status(m_ec, name, m_stat, &ec);
+ return m_status;
+}
+} // namespace
+} // end namespace detail
+
+using detail::capture_errno;
+using detail::ErrorHandler;
+using detail::StatT;
+using detail::TimeSpec;
+using parser::createView;
+using parser::PathParser;
+using parser::string_view_t;
+
+const bool _FilesystemClock::is_steady;
+
+_FilesystemClock::time_point _FilesystemClock::now() noexcept {
+ typedef chrono::duration<rep> __secs;
+#if defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
+ typedef chrono::duration<rep, nano> __nsecs;
+ struct timespec tp;
+ if (0 != clock_gettime(CLOCK_REALTIME, &tp))
+ __throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed");
+ return time_point(__secs(tp.tv_sec) +
+ chrono::duration_cast<duration>(__nsecs(tp.tv_nsec)));
+#else
+ typedef chrono::duration<rep, micro> __microsecs;
+ timeval tv;
+ gettimeofday(&tv, 0);
+ return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec));
+#endif // _LIBCPP_USE_CLOCK_GETTIME && CLOCK_REALTIME
+}
+
+filesystem_error::~filesystem_error() {}
+
+void filesystem_error::__create_what(int __num_paths) {
+ const char* derived_what = system_error::what();
+ __storage_->__what_ = [&]() -> string {
+ const char* p1 = path1().native().empty() ? "\"\"" : path1().c_str();
+ const char* p2 = path2().native().empty() ? "\"\"" : path2().c_str();
+ switch (__num_paths) {
+ default:
+ return detail::format_string("filesystem error: %s", derived_what);
+ case 1:
+ return detail::format_string("filesystem error: %s [%s]", derived_what,
+ p1);
+ case 2:
+ return detail::format_string("filesystem error: %s [%s] [%s]",
+ derived_what, p1, p2);
+ }
+ }();
+}
+
+static path __do_absolute(const path& p, path* cwd, error_code* ec) {
+ if (ec)
+ ec->clear();
+ if (p.is_absolute())
+ return p;
+ *cwd = __current_path(ec);
+ if (ec && *ec)
+ return {};
+ return (*cwd) / p;
+}
+
+path __absolute(const path& p, error_code* ec) {
+ path cwd;
+ return __do_absolute(p, &cwd, ec);
+}
+
+path __canonical(path const& orig_p, error_code* ec) {
+ path cwd;
+ ErrorHandler<path> err("canonical", ec, &orig_p, &cwd);
+
+ path p = __do_absolute(orig_p, &cwd, ec);
+ char buff[PATH_MAX + 1];
+ char* ret;
+ if ((ret = ::realpath(p.c_str(), buff)) == nullptr)
+ return err.report(capture_errno());
+ return {ret};
+}
+
+void __copy(const path& from, const path& to, copy_options options,
+ error_code* ec) {
+ ErrorHandler<void> err("copy", ec, &from, &to);
+
+ const bool sym_status = bool(
+ options & (copy_options::create_symlinks | copy_options::skip_symlinks));
+
+ const bool sym_status2 = bool(options & copy_options::copy_symlinks);
+
+ error_code m_ec1;
+ StatT f_st = {};
+ const file_status f = sym_status || sym_status2
+ ? detail::posix_lstat(from, f_st, &m_ec1)
+ : detail::posix_stat(from, f_st, &m_ec1);
+ if (m_ec1)
+ return err.report(m_ec1);
+
+ StatT t_st = {};
+ const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec1)
+ : detail::posix_stat(to, t_st, &m_ec1);
+
+ if (not status_known(t))
+ return err.report(m_ec1);
+
+ if (!exists(f) || is_other(f) || is_other(t) ||
+ (is_directory(f) && is_regular_file(t)) ||
+ detail::stat_equivalent(f_st, t_st)) {
+ return err.report(errc::function_not_supported);
+ }
+
+ if (ec)
+ ec->clear();
+
+ if (is_symlink(f)) {
+ if (bool(copy_options::skip_symlinks & options)) {
+ // do nothing
+ } else if (not exists(t)) {
+ __copy_symlink(from, to, ec);
+ } else {
+ return err.report(errc::file_exists);
+ }
+ return;
+ } else if (is_regular_file(f)) {
+ if (bool(copy_options::directories_only & options)) {
+ // do nothing
+ } else if (bool(copy_options::create_symlinks & options)) {
+ __create_symlink(from, to, ec);
+ } else if (bool(copy_options::create_hard_links & options)) {
+ __create_hard_link(from, to, ec);
+ } else if (is_directory(t)) {
+ __copy_file(from, to / from.filename(), options, ec);
+ } else {
+ __copy_file(from, to, options, ec);
+ }
+ return;
+ } else if (is_directory(f) && bool(copy_options::create_symlinks & options)) {
+ return err.report(errc::is_a_directory);
+ } else if (is_directory(f) && (bool(copy_options::recursive & options) ||
+ copy_options::none == options)) {
+
+ if (!exists(t)) {
+ // create directory to with attributes from 'from'.
+ __create_directory(to, from, ec);
+ if (ec && *ec) {
+ return;
+ }
+ }
+ directory_iterator it =
+ ec ? directory_iterator(from, *ec) : directory_iterator(from);
+ if (ec && *ec) {
+ return;
+ }
+ error_code m_ec2;
+ for (; it != directory_iterator(); it.increment(m_ec2)) {
+ if (m_ec2) {
+ return err.report(m_ec2);
+ }
+ __copy(it->path(), to / it->path().filename(),
+ options | copy_options::__in_recursive_copy, ec);
+ if (ec && *ec) {
+ return;
+ }
+ }
+ }
+}
+
+namespace detail {
+namespace {
+
+#ifdef _LIBCPP_USE_SENDFILE
+bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd,
+ error_code& ec) {
+
+ size_t count = read_fd.get_stat().st_size;
+ do {
+ ssize_t res;
+ if ((res = ::sendfile(write_fd.fd, read_fd.fd, nullptr, count)) == -1) {
+ ec = capture_errno();
+ return false;
+ }
+ count -= res;
+ } while (count > 0);
+
+ ec.clear();
+
+ return true;
+}
+#elif defined(_LIBCPP_USE_COPYFILE)
+bool copy_file_impl_copyfile(FileDescriptor& read_fd, FileDescriptor& write_fd,
+ error_code& ec) {
+ struct CopyFileState {
+ copyfile_state_t state;
+ CopyFileState() { state = copyfile_state_alloc(); }
+ ~CopyFileState() { copyfile_state_free(state); }
+
+ private:
+ CopyFileState(CopyFileState const&) = delete;
+ CopyFileState& operator=(CopyFileState const&) = delete;
+ };
+
+ CopyFileState cfs;
+ if (fcopyfile(read_fd.fd, write_fd.fd, cfs.state, COPYFILE_DATA) < 0) {
+ ec = capture_errno();
+ return false;
+ }
+
+ ec.clear();
+ return true;
+}
+#endif
+
+// Note: This function isn't guarded by ifdef's even though it may be unused
+// in order to assure it still compiles.
+__attribute__((unused)) bool copy_file_impl_default(FileDescriptor& read_fd,
+ FileDescriptor& write_fd,
+ error_code& ec) {
+ ifstream in;
+ in.__open(read_fd.fd, ios::binary);
+ if (!in.is_open()) {
+ // This assumes that __open didn't reset the error code.
+ ec = capture_errno();
+ return false;
+ }
+ ofstream out;
+ out.__open(write_fd.fd, ios::binary);
+ if (!out.is_open()) {
+ ec = capture_errno();
+ return false;
+ }
+
+ if (in.good() && out.good()) {
+ using InIt = istreambuf_iterator<char>;
+ using OutIt = ostreambuf_iterator<char>;
+ InIt bin(in);
+ InIt ein;
+ OutIt bout(out);
+ copy(bin, ein, bout);
+ }
+ if (out.fail() || in.fail()) {
+ ec = make_error_code(errc::io_error);
+ return false;
+ }
+
+ ec.clear();
+ return true;
+}
+
+bool copy_file_impl(FileDescriptor& from, FileDescriptor& to, error_code& ec) {
+#if defined(_LIBCPP_USE_SENDFILE)
+ return copy_file_impl_sendfile(from, to, ec);
+#elif defined(_LIBCPP_USE_COPYFILE)
+ return copy_file_impl_copyfile(from, to, ec);
+#else
+ return copy_file_impl_default(from, to, ec);
+#endif
+}
+
+} // namespace
+} // namespace detail
+
+bool __copy_file(const path& from, const path& to, copy_options options,
+ error_code* ec) {
+ using detail::FileDescriptor;
+ ErrorHandler<bool> err("copy_file", ec, &to, &from);
+
+ error_code m_ec;
+ FileDescriptor from_fd =
+ FileDescriptor::create_with_status(&from, m_ec, O_RDONLY | O_NONBLOCK);
+ if (m_ec)
+ return err.report(m_ec);
+
+ auto from_st = from_fd.get_status();
+ StatT const& from_stat = from_fd.get_stat();
+ if (!is_regular_file(from_st)) {
+ if (not m_ec)
+ m_ec = make_error_code(errc::not_supported);
+ return err.report(m_ec);
+ }
+
+ const bool skip_existing = bool(copy_options::skip_existing & options);
+ const bool update_existing = bool(copy_options::update_existing & options);
+ const bool overwrite_existing =
+ bool(copy_options::overwrite_existing & options);
+
+ StatT to_stat_path;
+ file_status to_st = detail::posix_stat(to, to_stat_path, &m_ec);
+ if (!status_known(to_st))
+ return err.report(m_ec);
+
+ const bool to_exists = exists(to_st);
+ if (to_exists && !is_regular_file(to_st))
+ return err.report(errc::not_supported);
+
+ if (to_exists && detail::stat_equivalent(from_stat, to_stat_path))
+ return err.report(errc::file_exists);
+
+ if (to_exists && skip_existing)
+ return false;
+
+ bool ShouldCopy = [&]() {
+ if (to_exists && update_existing) {
+ auto from_time = detail::extract_mtime(from_stat);
+ auto to_time = detail::extract_mtime(to_stat_path);
+ if (from_time.tv_sec < to_time.tv_sec)
+ return false;
+ if (from_time.tv_sec == to_time.tv_sec &&
+ from_time.tv_nsec <= to_time.tv_nsec)
+ return false;
+ return true;
+ }
+ if (!to_exists || overwrite_existing)
+ return true;
+ return err.report(errc::file_exists);
+ }();
+ if (!ShouldCopy)
+ return false;
+
+ // Don't truncate right away. We may not be opening the file we originally
+ // looked at; we'll check this later.
+ int to_open_flags = O_WRONLY;
+ if (!to_exists)
+ to_open_flags |= O_CREAT;
+ FileDescriptor to_fd = FileDescriptor::create_with_status(
+ &to, m_ec, to_open_flags, from_stat.st_mode);
+ if (m_ec)
+ return err.report(m_ec);
+
+ if (to_exists) {
+ // Check that the file we initially stat'ed is equivalent to the one
+ // we opened.
+ // FIXME: report this better.
+ if (!detail::stat_equivalent(to_stat_path, to_fd.get_stat()))
+ return err.report(errc::bad_file_descriptor);
+
+ // Set the permissions and truncate the file we opened.
+ if (detail::posix_fchmod(to_fd, from_stat, m_ec))
+ return err.report(m_ec);
+ if (detail::posix_ftruncate(to_fd, 0, m_ec))
+ return err.report(m_ec);
+ }
+
+ if (!copy_file_impl(from_fd, to_fd, m_ec)) {
+ // FIXME: Remove the dest file if we failed, and it didn't exist previously.
+ return err.report(m_ec);
+ }
+
+ return true;
+}
+
+void __copy_symlink(const path& existing_symlink, const path& new_symlink,
+ error_code* ec) {
+ const path real_path(__read_symlink(existing_symlink, ec));
+ if (ec && *ec) {
+ return;
+ }
+ // NOTE: proposal says you should detect if you should call
+ // create_symlink or create_directory_symlink. I don't think this
+ // is needed with POSIX
+ __create_symlink(real_path, new_symlink, ec);
+}
+
+bool __create_directories(const path& p, error_code* ec) {
+ ErrorHandler<bool> err("create_directories", ec, &p);
+
+ error_code m_ec;
+ auto const st = detail::posix_stat(p, &m_ec);
+ if (!status_known(st))
+ return err.report(m_ec);
+ else if (is_directory(st))
+ return false;
+ else if (exists(st))
+ return err.report(errc::file_exists);
+
+ const path parent = p.parent_path();
+ if (!parent.empty()) {
+ const file_status parent_st = status(parent, m_ec);
+ if (not status_known(parent_st))
+ return err.report(m_ec);
+ if (not exists(parent_st)) {
+ __create_directories(parent, ec);
+ if (ec && *ec) {
+ return false;
+ }
+ }
+ }
+ return __create_directory(p, ec);
+}
+
+bool __create_directory(const path& p, error_code* ec) {
+ ErrorHandler<bool> err("create_directory", ec, &p);
+
+ if (::mkdir(p.c_str(), static_cast<int>(perms::all)) == 0)
+ return true;
+ if (errno != EEXIST)
+ err.report(capture_errno());
+ return false;
+}
+
+bool __create_directory(path const& p, path const& attributes, error_code* ec) {
+ ErrorHandler<bool> err("create_directory", ec, &p, &attributes);
+
+ StatT attr_stat;
+ error_code mec;
+ auto st = detail::posix_stat(attributes, attr_stat, &mec);
+ if (!status_known(st))
+ return err.report(mec);
+ if (!is_directory(st))
+ return err.report(errc::not_a_directory,
+ "the specified attribute path is invalid");
+
+ if (::mkdir(p.c_str(), attr_stat.st_mode) == 0)
+ return true;
+ if (errno != EEXIST)
+ err.report(capture_errno());
+ return false;
+}
+
+void __create_directory_symlink(path const& from, path const& to,
+ error_code* ec) {
+ ErrorHandler<void> err("create_directory_symlink", ec, &from, &to);
+ if (::symlink(from.c_str(), to.c_str()) != 0)
+ return err.report(capture_errno());
+}
+
+void __create_hard_link(const path& from, const path& to, error_code* ec) {
+ ErrorHandler<void> err("create_hard_link", ec, &from, &to);
+ if (::link(from.c_str(), to.c_str()) == -1)
+ return err.report(capture_errno());
+}
+
+void __create_symlink(path const& from, path const& to, error_code* ec) {
+ ErrorHandler<void> err("create_symlink", ec, &from, &to);
+ if (::symlink(from.c_str(), to.c_str()) == -1)
+ return err.report(capture_errno());
+}
+
+path __current_path(error_code* ec) {
+ ErrorHandler<path> err("current_path", ec);
+
+ auto size = ::pathconf(".", _PC_PATH_MAX);
+ _LIBCPP_ASSERT(size >= 0, "pathconf returned a 0 as max size");
+
+ auto buff = unique_ptr<char[]>(new char[size + 1]);
+ char* ret;
+ if ((ret = ::getcwd(buff.get(), static_cast<size_t>(size))) == nullptr)
+ return err.report(capture_errno(), "call to getcwd failed");
+
+ return {buff.get()};
+}
+
+void __current_path(const path& p, error_code* ec) {
+ ErrorHandler<void> err("current_path", ec, &p);
+ if (::chdir(p.c_str()) == -1)
+ err.report(capture_errno());
+}
+
+bool __equivalent(const path& p1, const path& p2, error_code* ec) {
+ ErrorHandler<bool> err("equivalent", ec, &p1, &p2);
+
+ error_code ec1, ec2;
+ StatT st1 = {}, st2 = {};
+ auto s1 = detail::posix_stat(p1.native(), st1, &ec1);
+ if (!exists(s1))
+ return err.report(errc::not_supported);
+ auto s2 = detail::posix_stat(p2.native(), st2, &ec2);
+ if (!exists(s2))
+ return err.report(errc::not_supported);
+
+ return detail::stat_equivalent(st1, st2);
+}
+
+uintmax_t __file_size(const path& p, error_code* ec) {
+ ErrorHandler<uintmax_t> err("file_size", ec, &p);
+
+ error_code m_ec;
+ StatT st;
+ file_status fst = detail::posix_stat(p, st, &m_ec);
+ if (!exists(fst) || !is_regular_file(fst)) {
+ errc error_kind =
+ is_directory(fst) ? errc::is_a_directory : errc::not_supported;
+ if (!m_ec)
+ m_ec = make_error_code(error_kind);
+ return err.report(m_ec);
+ }
+ // is_regular_file(p) == true
+ return static_cast<uintmax_t>(st.st_size);
+}
+
+uintmax_t __hard_link_count(const path& p, error_code* ec) {
+ ErrorHandler<uintmax_t> err("hard_link_count", ec, &p);
+
+ error_code m_ec;
+ StatT st;
+ detail::posix_stat(p, st, &m_ec);
+ if (m_ec)
+ return err.report(m_ec);
+ return static_cast<uintmax_t>(st.st_nlink);
+}
+
+bool __fs_is_empty(const path& p, error_code* ec) {
+ ErrorHandler<bool> err("is_empty", ec, &p);
+
+ error_code m_ec;
+ StatT pst;
+ auto st = detail::posix_stat(p, pst, &m_ec);
+ if (m_ec)
+ return err.report(m_ec);
+ else if (!is_directory(st) && !is_regular_file(st))
+ return err.report(errc::not_supported);
+ else if (is_directory(st)) {
+ auto it = ec ? directory_iterator(p, *ec) : directory_iterator(p);
+ if (ec && *ec)
+ return false;
+ return it == directory_iterator{};
+ } else if (is_regular_file(st))
+ return static_cast<uintmax_t>(pst.st_size) == 0;
+
+ _LIBCPP_UNREACHABLE();
+}
+
+static file_time_type __extract_last_write_time(const path& p, const StatT& st,
+ error_code* ec) {
+ using detail::fs_time;
+ ErrorHandler<file_time_type> err("last_write_time", ec, &p);
+
+ auto ts = detail::extract_mtime(st);
+ if (!fs_time::is_representable(ts))
+ return err.report(errc::value_too_large);
+
+ return fs_time::convert_from_timespec(ts);
+}
+
+file_time_type __last_write_time(const path& p, error_code* ec) {
+ using namespace chrono;
+ ErrorHandler<file_time_type> err("last_write_time", ec, &p);
+
+ error_code m_ec;
+ StatT st;
+ detail::posix_stat(p, st, &m_ec);
+ if (m_ec)
+ return err.report(m_ec);
+ return __extract_last_write_time(p, st, ec);
+}
+
+void __last_write_time(const path& p, file_time_type new_time, error_code* ec) {
+ using detail::fs_time;
+ ErrorHandler<void> err("last_write_time", ec, &p);
+
+ error_code m_ec;
+ array<TimeSpec, 2> tbuf;
+#if !defined(_LIBCPP_USE_UTIMENSAT)
+ // This implementation has a race condition between determining the
+ // last access time and attempting to set it to the same value using
+ // ::utimes
+ StatT st;
+ file_status fst = detail::posix_stat(p, st, &m_ec);
+ if (m_ec)
+ return err.report(m_ec);
+ tbuf[0] = detail::extract_atime(st);
+#else
+ tbuf[0].tv_sec = 0;
+ tbuf[0].tv_nsec = UTIME_OMIT;
+#endif
+ if (!fs_time::convert_to_timespec(tbuf[1], new_time))
+ return err.report(errc::value_too_large);
+
+ detail::set_file_times(p, tbuf, m_ec);
+ if (m_ec)
+ return err.report(m_ec);
+}
+
+void __permissions(const path& p, perms prms, perm_options opts,
+ error_code* ec) {
+ ErrorHandler<void> err("permissions", ec, &p);
+
+ auto has_opt = [&](perm_options o) { return bool(o & opts); };
+ const bool resolve_symlinks = !has_opt(perm_options::nofollow);
+ const bool add_perms = has_opt(perm_options::add);
+ const bool remove_perms = has_opt(perm_options::remove);
+ _LIBCPP_ASSERT(
+ (add_perms + remove_perms + has_opt(perm_options::replace)) == 1,
+ "One and only one of the perm_options constants replace, add, or remove "
+ "is present in opts");
+
+ bool set_sym_perms = false;
+ prms &= perms::mask;
+ if (!resolve_symlinks || (add_perms || remove_perms)) {
+ error_code m_ec;
+ file_status st = resolve_symlinks ? detail::posix_stat(p, &m_ec)
+ : detail::posix_lstat(p, &m_ec);
+ set_sym_perms = is_symlink(st);
+ if (m_ec)
+ return err.report(m_ec);
+ _LIBCPP_ASSERT(st.permissions() != perms::unknown,
+ "Permissions unexpectedly unknown");
+ if (add_perms)
+ prms |= st.permissions();
+ else if (remove_perms)
+ prms = st.permissions() & ~prms;
+ }
+ const auto real_perms = detail::posix_convert_perms(prms);
+
+#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD)
+ const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0;
+ if (::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) {
+ return err.report(capture_errno());
+ }
+#else
+ if (set_sym_perms)
+ return err.report(errc::operation_not_supported);
+ if (::chmod(p.c_str(), real_perms) == -1) {
+ return err.report(capture_errno());
+ }
+#endif
+}
+
+path __read_symlink(const path& p, error_code* ec) {
+ ErrorHandler<path> err("read_symlink", ec, &p);
+
+ char buff[PATH_MAX + 1];
+ error_code m_ec;
+ ::ssize_t ret;
+ if ((ret = ::readlink(p.c_str(), buff, PATH_MAX)) == -1) {
+ return err.report(capture_errno());
+ }
+ _LIBCPP_ASSERT(ret <= PATH_MAX, "TODO");
+ _LIBCPP_ASSERT(ret > 0, "TODO");
+ buff[ret] = 0;
+ return {buff};
+}
+
+bool __remove(const path& p, error_code* ec) {
+ ErrorHandler<bool> err("remove", ec, &p);
+ if (::remove(p.c_str()) == -1) {
+ if (errno != ENOENT)
+ err.report(capture_errno());
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+uintmax_t remove_all_impl(path const& p, error_code& ec) {
+ const auto npos = static_cast<uintmax_t>(-1);
+ const file_status st = __symlink_status(p, &ec);
+ if (ec)
+ return npos;
+ uintmax_t count = 1;
+ if (is_directory(st)) {
+ for (directory_iterator it(p, ec); !ec && it != directory_iterator();
+ it.increment(ec)) {
+ auto other_count = remove_all_impl(it->path(), ec);
+ if (ec)
+ return npos;
+ count += other_count;
+ }
+ if (ec)
+ return npos;
+ }
+ if (!__remove(p, &ec))
+ return npos;
+ return count;
+}
+
+} // end namespace
+
+uintmax_t __remove_all(const path& p, error_code* ec) {
+ ErrorHandler<uintmax_t> err("remove_all", ec, &p);
+
+ error_code mec;
+ auto count = remove_all_impl(p, mec);
+ if (mec) {
+ if (mec == errc::no_such_file_or_directory)
+ return 0;
+ return err.report(mec);
+ }
+ return count;
+}
+
+void __rename(const path& from, const path& to, error_code* ec) {
+ ErrorHandler<void> err("rename", ec, &from, &to);
+ if (::rename(from.c_str(), to.c_str()) == -1)
+ err.report(capture_errno());
+}
+
+void __resize_file(const path& p, uintmax_t size, error_code* ec) {
+ ErrorHandler<void> err("resize_file", ec, &p);
+ if (::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1)
+ return err.report(capture_errno());
+}
+
+space_info __space(const path& p, error_code* ec) {
+ ErrorHandler<void> err("space", ec, &p);
+ space_info si;
+ struct statvfs m_svfs = {};
+ if (::statvfs(p.c_str(), &m_svfs) == -1) {
+ err.report(capture_errno());
+ si.capacity = si.free = si.available = static_cast<uintmax_t>(-1);
+ return si;
+ }
+ // Multiply with overflow checking.
+ auto do_mult = [&](uintmax_t& out, uintmax_t other) {
+ out = other * m_svfs.f_frsize;
+ if (other == 0 || out / other != m_svfs.f_frsize)
+ out = static_cast<uintmax_t>(-1);
+ };
+ do_mult(si.capacity, m_svfs.f_blocks);
+ do_mult(si.free, m_svfs.f_bfree);
+ do_mult(si.available, m_svfs.f_bavail);
+ return si;
+}
+
+file_status __status(const path& p, error_code* ec) {
+ return detail::posix_stat(p, ec);
+}
+
+file_status __symlink_status(const path& p, error_code* ec) {
+ return detail::posix_lstat(p, ec);
+}
+
+path __temp_directory_path(error_code* ec) {
+ ErrorHandler<path> err("temp_directory_path", ec);
+
+ const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
+ const char* ret = nullptr;
+
+ for (auto& ep : env_paths)
+ if ((ret = getenv(ep)))
+ break;
+ if (ret == nullptr)
+ ret = "/tmp";
+
+ path p(ret);
+ error_code m_ec;
+ file_status st = detail::posix_stat(p, &m_ec);
+ if (!status_known(st))
+ return err.report(m_ec, "cannot access path \"%s\"", p);
+
+ if (!exists(st) || !is_directory(st))
+ return err.report(errc::not_a_directory, "path \"%s\" is not a directory",
+ p);
+
+ return p;
+}
+
+path __weakly_canonical(const path& p, error_code* ec) {
+ ErrorHandler<path> err("weakly_canonical", ec, &p);
+
+ if (p.empty())
+ return __canonical("", ec);
+
+ path result;
+ path tmp;
+ tmp.__reserve(p.native().size());
+ auto PP = PathParser::CreateEnd(p.native());
+ --PP;
+ vector<string_view_t> DNEParts;
+
+ while (PP.State != PathParser::PS_BeforeBegin) {
+ tmp.assign(createView(p.native().data(), &PP.RawEntry.back()));
+ error_code m_ec;
+ file_status st = __status(tmp, &m_ec);
+ if (!status_known(st)) {
+ return err.report(m_ec);
+ } else if (exists(st)) {
+ result = __canonical(tmp, ec);
+ break;
+ }
+ DNEParts.push_back(*PP);
+ --PP;
+ }
+ if (PP.State == PathParser::PS_BeforeBegin)
+ result = __canonical("", ec);
+ if (ec)
+ ec->clear();
+ if (DNEParts.empty())
+ return result;
+ for (auto It = DNEParts.rbegin(); It != DNEParts.rend(); ++It)
+ result /= *It;
+ return result.lexically_normal();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// path definitions
+///////////////////////////////////////////////////////////////////////////////
+
+constexpr path::value_type path::preferred_separator;
+
+path& path::replace_extension(path const& replacement) {
+ path p = extension();
+ if (not p.empty()) {
+ __pn_.erase(__pn_.size() - p.native().size());
+ }
+ if (!replacement.empty()) {
+ if (replacement.native()[0] != '.') {
+ __pn_ += ".";
+ }
+ __pn_.append(replacement.__pn_);
+ }
+ return *this;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// path.decompose
+
+string_view_t path::__root_name() const {
+ auto PP = PathParser::CreateBegin(__pn_);
+ if (PP.State == PathParser::PS_InRootName)
+ return *PP;
+ return {};
+}
+
+string_view_t path::__root_directory() const {
+ auto PP = PathParser::CreateBegin(__pn_);
+ if (PP.State == PathParser::PS_InRootName)
+ ++PP;
+ if (PP.State == PathParser::PS_InRootDir)
+ return *PP;
+ return {};
+}
+
+string_view_t path::__root_path_raw() const {
+ auto PP = PathParser::CreateBegin(__pn_);
+ if (PP.State == PathParser::PS_InRootName) {
+ auto NextCh = PP.peek();
+ if (NextCh && *NextCh == '/') {
+ ++PP;
+ return createView(__pn_.data(), &PP.RawEntry.back());
+ }
+ return PP.RawEntry;
+ }
+ if (PP.State == PathParser::PS_InRootDir)
+ return *PP;
+ return {};
+}
+
+static bool ConsumeRootName(PathParser *PP) {
+ static_assert(PathParser::PS_BeforeBegin == 1 &&
+ PathParser::PS_InRootName == 2,
+ "Values for enums are incorrect");
+ while (PP->State <= PathParser::PS_InRootName)
+ ++(*PP);
+ return PP->State == PathParser::PS_AtEnd;
+}
+
+static bool ConsumeRootDir(PathParser* PP) {
+ static_assert(PathParser::PS_BeforeBegin == 1 &&
+ PathParser::PS_InRootName == 2 &&
+ PathParser::PS_InRootDir == 3, "Values for enums are incorrect");
+ while (PP->State <= PathParser::PS_InRootDir)
+ ++(*PP);
+ return PP->State == PathParser::PS_AtEnd;
+}
+
+string_view_t path::__relative_path() const {
+ auto PP = PathParser::CreateBegin(__pn_);
+ if (ConsumeRootDir(&PP))
+ return {};
+ return createView(PP.RawEntry.data(), &__pn_.back());
+}
+
+string_view_t path::__parent_path() const {
+ if (empty())
+ return {};
+ // Determine if we have a root path but not a relative path. In that case
+ // return *this.
+ {
+ auto PP = PathParser::CreateBegin(__pn_);
+ if (ConsumeRootDir(&PP))
+ return __pn_;
+ }
+ // Otherwise remove a single element from the end of the path, and return
+ // a string representing that path
+ {
+ auto PP = PathParser::CreateEnd(__pn_);
+ --PP;
+ if (PP.RawEntry.data() == __pn_.data())
+ return {};
+ --PP;
+ return createView(__pn_.data(), &PP.RawEntry.back());
+ }
+}
+
+string_view_t path::__filename() const {
+ if (empty())
+ return {};
+ {
+ PathParser PP = PathParser::CreateBegin(__pn_);
+ if (ConsumeRootDir(&PP))
+ return {};
+ }
+ return *(--PathParser::CreateEnd(__pn_));
+}
+
+string_view_t path::__stem() const {
+ return parser::separate_filename(__filename()).first;
+}
+
+string_view_t path::__extension() const {
+ return parser::separate_filename(__filename()).second;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// path.gen
+
+enum PathPartKind : unsigned char {
+ PK_None,
+ PK_RootSep,
+ PK_Filename,
+ PK_Dot,
+ PK_DotDot,
+ PK_TrailingSep
+};
+
+static PathPartKind ClassifyPathPart(string_view_t Part) {
+ if (Part.empty())
+ return PK_TrailingSep;
+ if (Part == ".")
+ return PK_Dot;
+ if (Part == "..")
+ return PK_DotDot;
+ if (Part == "/")
+ return PK_RootSep;
+ return PK_Filename;
+}
+
+path path::lexically_normal() const {
+ if (__pn_.empty())
+ return *this;
+
+ using PartKindPair = pair<string_view_t, PathPartKind>;
+ vector<PartKindPair> Parts;
+ // Guess as to how many elements the path has to avoid reallocating.
+ Parts.reserve(32);
+
+ // Track the total size of the parts as we collect them. This allows the
+ // resulting path to reserve the correct amount of memory.
+ size_t NewPathSize = 0;
+ auto AddPart = [&](PathPartKind K, string_view_t P) {
+ NewPathSize += P.size();
+ Parts.emplace_back(P, K);
+ };
+ auto LastPartKind = [&]() {
+ if (Parts.empty())
+ return PK_None;
+ return Parts.back().second;
+ };
+
+ bool MaybeNeedTrailingSep = false;
+ // Build a stack containing the remaining elements of the path, popping off
+ // elements which occur before a '..' entry.
+ for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {
+ auto Part = *PP;
+ PathPartKind Kind = ClassifyPathPart(Part);
+ switch (Kind) {
+ case PK_Filename:
+ case PK_RootSep: {
+ // Add all non-dot and non-dot-dot elements to the stack of elements.
+ AddPart(Kind, Part);
+ MaybeNeedTrailingSep = false;
+ break;
+ }
+ case PK_DotDot: {
+ // Only push a ".." element if there are no elements preceding the "..",
+ // or if the preceding element is itself "..".
+ auto LastKind = LastPartKind();
+ if (LastKind == PK_Filename) {
+ NewPathSize -= Parts.back().first.size();
+ Parts.pop_back();
+ } else if (LastKind != PK_RootSep)
+ AddPart(PK_DotDot, "..");
+ MaybeNeedTrailingSep = LastKind == PK_Filename;
+ break;
+ }
+ case PK_Dot:
+ case PK_TrailingSep: {
+ MaybeNeedTrailingSep = true;
+ break;
+ }
+ case PK_None:
+ _LIBCPP_UNREACHABLE();
+ }
+ }
+ // [fs.path.generic]p6.8: If the path is empty, add a dot.
+ if (Parts.empty())
+ return ".";
+
+ // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any
+ // trailing directory-separator.
+ bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;
+
+ path Result;
+ Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);
+ for (auto& PK : Parts)
+ Result /= PK.first;
+
+ if (NeedTrailingSep)
+ Result /= "";
+
+ return Result;
+}
+
+static int DetermineLexicalElementCount(PathParser PP) {
+ int Count = 0;
+ for (; PP; ++PP) {
+ auto Elem = *PP;
+ if (Elem == "..")
+ --Count;
+ else if (Elem != "." && Elem != "")
+ ++Count;
+ }
+ return Count;
+}
+
+path path::lexically_relative(const path& base) const {
+ { // perform root-name/root-directory mismatch checks
+ auto PP = PathParser::CreateBegin(__pn_);
+ auto PPBase = PathParser::CreateBegin(base.__pn_);
+ auto CheckIterMismatchAtBase = [&]() {
+ return PP.State != PPBase.State &&
+ (PP.inRootPath() || PPBase.inRootPath());
+ };
+ if (PP.inRootName() && PPBase.inRootName()) {
+ if (*PP != *PPBase)
+ return {};
+ } else if (CheckIterMismatchAtBase())
+ return {};
+
+ if (PP.inRootPath())
+ ++PP;
+ if (PPBase.inRootPath())
+ ++PPBase;
+ if (CheckIterMismatchAtBase())
+ return {};
+ }
+
+ // Find the first mismatching element
+ auto PP = PathParser::CreateBegin(__pn_);
+ auto PPBase = PathParser::CreateBegin(base.__pn_);
+ while (PP && PPBase && PP.State == PPBase.State && *PP == *PPBase) {
+ ++PP;
+ ++PPBase;
+ }
+
+ // If there is no mismatch, return ".".
+ if (!PP && !PPBase)
+ return ".";
+
+ // Otherwise, determine the number of elements, 'n', which are not dot or
+ // dot-dot minus the number of dot-dot elements.
+ int ElemCount = DetermineLexicalElementCount(PPBase);
+ if (ElemCount < 0)
+ return {};
+
+ // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise
+ if (ElemCount == 0 && (PP.atEnd() || *PP == ""))
+ return ".";
+
+ // return a path constructed with 'n' dot-dot elements, followed by the
+ // elements of '*this' after the mismatch.
+ path Result;
+ // FIXME: Reserve enough room in Result that it won't have to re-allocate.
+ while (ElemCount--)
+ Result /= "..";
+ for (; PP; ++PP)
+ Result /= *PP;
+ return Result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// path.comparisons
+static int CompareRootName(PathParser *LHS, PathParser *RHS) {
+ if (!LHS->inRootName() && !RHS->inRootName())
+ return 0;
+
+ auto GetRootName = [](PathParser *Parser) -> string_view_t {
+ return Parser->inRootName() ? **Parser : "";
+ };
+ int res = GetRootName(LHS).compare(GetRootName(RHS));
+ ConsumeRootName(LHS);
+ ConsumeRootName(RHS);
+ return res;
+}
+
+static int CompareRootDir(PathParser *LHS, PathParser *RHS) {
+ if (!LHS->inRootDir() && RHS->inRootDir())
+ return -1;
+ else if (LHS->inRootDir() && !RHS->inRootDir())
+ return 1;
+ else {
+ ConsumeRootDir(LHS);
+ ConsumeRootDir(RHS);
+ return 0;
+ }
+}
+
+static int CompareRelative(PathParser *LHSPtr, PathParser *RHSPtr) {
+ auto &LHS = *LHSPtr;
+ auto &RHS = *RHSPtr;
+
+ int res;
+ while (LHS && RHS) {
+ if ((res = (*LHS).compare(*RHS)) != 0)
+ return res;
+ ++LHS;
+ ++RHS;
+ }
+ return 0;
+}
+
+static int CompareEndState(PathParser *LHS, PathParser *RHS) {
+ if (LHS->atEnd() && !RHS->atEnd())
+ return -1;
+ else if (!LHS->atEnd() && RHS->atEnd())
+ return 1;
+ return 0;
+}
+
+int path::__compare(string_view_t __s) const {
+ auto LHS = PathParser::CreateBegin(__pn_);
+ auto RHS = PathParser::CreateBegin(__s);
+ int res;
+
+ if ((res = CompareRootName(&LHS, &RHS)) != 0)
+ return res;
+
+ if ((res = CompareRootDir(&LHS, &RHS)) != 0)
+ return res;
+
+ if ((res = CompareRelative(&LHS, &RHS)) != 0)
+ return res;
+
+ return CompareEndState(&LHS, &RHS);
+}
+
+////////////////////////////////////////////////////////////////////////////
+// path.nonmembers
+size_t hash_value(const path& __p) noexcept {
+ auto PP = PathParser::CreateBegin(__p.native());
+ size_t hash_value = 0;
+ hash<string_view_t> hasher;
+ while (PP) {
+ hash_value = __hash_combine(hash_value, hasher(*PP));
+ ++PP;
+ }
+ return hash_value;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// path.itr
+path::iterator path::begin() const {
+ auto PP = PathParser::CreateBegin(__pn_);
+ iterator it;
+ it.__path_ptr_ = this;
+ it.__state_ = static_cast<path::iterator::_ParserState>(PP.State);
+ it.__entry_ = PP.RawEntry;
+ it.__stashed_elem_.__assign_view(*PP);
+ return it;
+}
+
+path::iterator path::end() const {
+ iterator it{};
+ it.__state_ = path::iterator::_AtEnd;
+ it.__path_ptr_ = this;
+ return it;
+}
+
+path::iterator& path::iterator::__increment() {
+ PathParser PP(__path_ptr_->native(), __entry_, __state_);
+ ++PP;
+ __state_ = static_cast<_ParserState>(PP.State);
+ __entry_ = PP.RawEntry;
+ __stashed_elem_.__assign_view(*PP);
+ return *this;
+}
+
+path::iterator& path::iterator::__decrement() {
+ PathParser PP(__path_ptr_->native(), __entry_, __state_);
+ --PP;
+ __state_ = static_cast<_ParserState>(PP.State);
+ __entry_ = PP.RawEntry;
+ __stashed_elem_.__assign_view(*PP);
+ return *this;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// directory entry definitions
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _LIBCPP_WIN32API
+error_code directory_entry::__do_refresh() noexcept {
+ __data_.__reset();
+ error_code failure_ec;
+
+ StatT full_st;
+ file_status st = detail::posix_lstat(__p_, full_st, &failure_ec);
+ if (!status_known(st)) {
+ __data_.__reset();
+ return failure_ec;
+ }
+
+ if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) {
+ __data_.__cache_type_ = directory_entry::_RefreshNonSymlink;
+ __data_.__type_ = st.type();
+ __data_.__non_sym_perms_ = st.permissions();
+ } else { // we have a symlink
+ __data_.__sym_perms_ = st.permissions();
+ // Get the information about the linked entity.
+ // Ignore errors from stat, since we don't want errors regarding symlink
+ // resolution to be reported to the user.
+ error_code ignored_ec;
+ st = detail::posix_stat(__p_, full_st, &ignored_ec);
+
+ __data_.__type_ = st.type();
+ __data_.__non_sym_perms_ = st.permissions();
+
+ // If we failed to resolve the link, then only partially populate the
+ // cache.
+ if (!status_known(st)) {
+ __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved;
+ return error_code{};
+ }
+ // Otherwise, we resolved the link, potentially as not existing.
+ // That's OK.
+ __data_.__cache_type_ = directory_entry::_RefreshSymlink;
+ }
+
+ if (_VSTD_FS::is_regular_file(st))
+ __data_.__size_ = static_cast<uintmax_t>(full_st.st_size);
+
+ if (_VSTD_FS::exists(st)) {
+ __data_.__nlink_ = static_cast<uintmax_t>(full_st.st_nlink);
+
+ // Attempt to extract the mtime, and fail if it's not representable using
+ // file_time_type. For now we ignore the error, as we'll report it when
+ // the value is actually used.
+ error_code ignored_ec;
+ __data_.__write_time_ =
+ __extract_last_write_time(__p_, full_st, &ignored_ec);
+ }
+
+ return failure_ec;
+}
+#else
+error_code directory_entry::__do_refresh() noexcept {
+ __data_.__reset();
+ error_code failure_ec;
+
+ file_status st = _VSTD_FS::symlink_status(__p_, failure_ec);
+ if (!status_known(st)) {
+ __data_.__reset();
+ return failure_ec;
+ }
+
+ if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) {
+ __data_.__cache_type_ = directory_entry::_RefreshNonSymlink;
+ __data_.__type_ = st.type();
+ __data_.__non_sym_perms_ = st.permissions();
+ } else { // we have a symlink
+ __data_.__sym_perms_ = st.permissions();
+ // Get the information about the linked entity.
+ // Ignore errors from stat, since we don't want errors regarding symlink
+ // resolution to be reported to the user.
+ error_code ignored_ec;
+ st = _VSTD_FS::status(__p_, ignored_ec);
+
+ __data_.__type_ = st.type();
+ __data_.__non_sym_perms_ = st.permissions();
+
+ // If we failed to resolve the link, then only partially populate the
+ // cache.
+ if (!status_known(st)) {
+ __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved;
+ return error_code{};
+ }
+ __data_.__cache_type_ = directory_entry::_RefreshSymlink;
+ }
+
+ // FIXME: This is currently broken, and the implementation only a placeholder.
+ // We need to cache last_write_time, file_size, and hard_link_count here before
+ // the implementation actually works.
+
+ return failure_ec;
+}
+#endif
+
+#ifndef _LIBAUTO_UNDEF_VSTD_FS
+#pragma pop_macro("_VSTD_FS")
+#else
+#undef _VSTD
+#undef _LIBAUTO_UNDEF_VSTD_FS
+#endif
+} // namespace android::hardware::automotive::filesystem
+/* clang-format on */
diff --git a/automotive/vehicle/2.0/default/Android.bp b/automotive/vehicle/2.0/default/Android.bp
index e529675..a4fd641 100644
--- a/automotive/vehicle/2.0/default/Android.bp
+++ b/automotive/vehicle/2.0/default/Android.bp
@@ -15,10 +15,12 @@
cc_defaults {
name: "vhal_v2_0_defaults",
shared_libs: [
+ "libbinder_ndk",
"libhidlbase",
"liblog",
"libutils",
"android.hardware.automotive.vehicle@2.0",
+ "carwatchdog_aidl_interface-ndk_platform",
],
cflags: [
"-Wall",
@@ -46,6 +48,7 @@
"common/src/VehiclePropertyStore.cpp",
"common/src/VehicleUtils.cpp",
"common/src/VmsUtils.cpp",
+ "common/src/WatchdogClient.cpp",
],
shared_libs: [
"libbase",
@@ -75,7 +78,10 @@
],
local_include_dirs: ["common/include/vhal_v2_0"],
export_include_dirs: ["impl"],
- whole_static_libs: ["android.hardware.automotive.vehicle@2.0-manager-lib"],
+ whole_static_libs: [
+ "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib",
+ "android.hardware.automotive.vehicle@2.0-manager-lib",
+ ],
shared_libs: [
"libbase",
"libjsoncpp",
@@ -87,6 +93,16 @@
],
}
+// Library used to emulate User HAL behavior through lshal debug requests.
+cc_library_static {
+ name: "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib",
+ vendor: true,
+ defaults: ["vhal_v2_0_defaults"],
+ srcs: [
+ "impl/vhal_v2_0/EmulatedUserHal.cpp",
+ ],
+}
+
cc_test {
name: "android.hardware.automotive.vehicle@2.0-manager-unit-tests",
vendor: true,
@@ -125,6 +141,9 @@
cc_binary {
name: "android.hardware.automotive.vehicle@2.0-service",
defaults: ["vhal_v2_0_defaults"],
+ vintf_fragments: [
+ "android.hardware.automotive.vehicle@2.0-service.xml",
+ ],
init_rc: ["android.hardware.automotive.vehicle@2.0-service.rc"],
vendor: true,
relative_install_path: "hw",
diff --git a/automotive/vehicle/2.0/default/VehicleService.cpp b/automotive/vehicle/2.0/default/VehicleService.cpp
index 127eb98..32e5e70 100644
--- a/automotive/vehicle/2.0/default/VehicleService.cpp
+++ b/automotive/vehicle/2.0/default/VehicleService.cpp
@@ -20,9 +20,12 @@
#include <iostream>
+#include <android/binder_process.h>
+#include <utils/Looper.h>
#include <vhal_v2_0/EmulatedVehicleConnector.h>
#include <vhal_v2_0/EmulatedVehicleHal.h>
#include <vhal_v2_0/VehicleHalManager.h>
+#include <vhal_v2_0/WatchdogClient.h>
using namespace android;
using namespace android::hardware;
@@ -36,7 +39,7 @@
auto service = std::make_unique<VehicleHalManager>(hal.get());
connector->setValuePool(hal->getValuePool());
- configureRpcThreadpool(4, true /* callerWillJoin */);
+ configureRpcThreadpool(4, false /* callerWillJoin */);
ALOGI("Registering as service...");
status_t status = service->registerAsService();
@@ -46,8 +49,22 @@
return 1;
}
+ // Setup a binder thread pool to be a car watchdog client.
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ sp<Looper> looper(Looper::prepare(0 /* opts */));
+ std::shared_ptr<WatchdogClient> watchdogClient =
+ ndk::SharedRefBase::make<WatchdogClient>(looper, service.get());
+ // The current health check is done in the main thread, so it falls short of capturing the real
+ // situation. Checking through HAL binder thread should be considered.
+ if (!watchdogClient->initialize()) {
+ ALOGE("Failed to initialize car watchdog client");
+ return 1;
+ }
ALOGI("Ready");
- joinRpcThreadpool();
+ while (true) {
+ looper->pollAll(-1 /* timeoutMillis */);
+ }
return 1;
}
diff --git a/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml b/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml
new file mode 100644
index 0000000..660b03d
--- /dev/null
+++ b/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml
@@ -0,0 +1,11 @@
+<manifest version="1.0" type="device">
+ <hal format="hidl">
+ <name>android.hardware.automotive.vehicle</name>
+ <transport>hwbinder</transport>
+ <version>2.0</version>
+ <interface>
+ <name>IVehicle</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h
new file mode 100644
index 0000000..578606d
--- /dev/null
+++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_automotive_vehicle_V2_0_WatchdogClient_H_
+#define android_hardware_automotive_vehicle_V2_0_WatchdogClient_H_
+
+#include "VehicleHalManager.h"
+
+#include <aidl/android/automotive/watchdog/BnCarWatchdog.h>
+#include <aidl/android/automotive/watchdog/BnCarWatchdogClient.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+class WatchdogClient : public aidl::android::automotive::watchdog::BnCarWatchdogClient {
+ public:
+ explicit WatchdogClient(const ::android::sp<::android::Looper>& handlerLooper,
+ VehicleHalManager* vhalManager);
+
+ ndk::ScopedAStatus checkIfAlive(
+ int32_t sessionId, aidl::android::automotive::watchdog::TimeoutLength timeout) override;
+ ndk::ScopedAStatus prepareProcessTermination() override;
+
+ bool initialize();
+
+ private:
+ class MessageHandlerImpl : public ::android::MessageHandler {
+ public:
+ explicit MessageHandlerImpl(WatchdogClient* client);
+ void handleMessage(const ::android::Message& message) override;
+
+ private:
+ WatchdogClient* mClient;
+ };
+
+ private:
+ void respondToWatchdog();
+ bool isClientHealthy() const;
+
+ private:
+ ::android::sp<::android::Looper> mHandlerLooper;
+ ::android::sp<MessageHandlerImpl> mMessageHandler;
+ std::shared_ptr<aidl::android::automotive::watchdog::ICarWatchdog> mWatchdogServer;
+ std::shared_ptr<aidl::android::automotive::watchdog::ICarWatchdogClient> mTestClient;
+ VehicleHalManager* mVhalManager;
+ ::android::Mutex mMutex;
+ int mCurrentSessionId GUARDED_BY(mMutex);
+};
+
+} // namespace V2_0
+} // namespace vehicle
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // android_hardware_automotive_vehicle_V2_0_WatchdogClient_H_
diff --git a/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp b/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp
new file mode 100644
index 0000000..c067216
--- /dev/null
+++ b/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "automotive.vehicle@2.0-watchdog"
+
+#include <common/include/vhal_v2_0/WatchdogClient.h>
+
+#include <android/binder_manager.h>
+#include <android/hardware/automotive/vehicle/2.0/types.h>
+
+using aidl::android::automotive::watchdog::ICarWatchdog;
+using aidl::android::automotive::watchdog::TimeoutLength;
+
+namespace {
+
+enum { WHAT_CHECK_ALIVE = 1 };
+
+} // namespace
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+WatchdogClient::WatchdogClient(const sp<Looper>& handlerLooper, VehicleHalManager* vhalManager)
+ : mHandlerLooper(handlerLooper), mVhalManager(vhalManager), mCurrentSessionId(-1) {
+ mMessageHandler = new MessageHandlerImpl(this);
+}
+
+ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength /*timeout*/) {
+ mHandlerLooper->removeMessages(mMessageHandler, WHAT_CHECK_ALIVE);
+ {
+ Mutex::Autolock lock(mMutex);
+ mCurrentSessionId = sessionId;
+ }
+ mHandlerLooper->sendMessage(mMessageHandler, Message(WHAT_CHECK_ALIVE));
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WatchdogClient::prepareProcessTermination() {
+ return ndk::ScopedAStatus::ok();
+}
+
+bool WatchdogClient::initialize() {
+ ndk::SpAIBinder binder(
+ AServiceManager_getService("android.automotive.watchdog.ICarWatchdog/default"));
+ if (binder.get() == nullptr) {
+ ALOGE("Failed to get carwatchdog daemon");
+ return false;
+ }
+ std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder);
+ if (server == nullptr) {
+ ALOGE("Failed to connect to carwatchdog daemon");
+ return false;
+ }
+ mWatchdogServer = server;
+
+ binder = this->asBinder();
+ if (binder.get() == nullptr) {
+ ALOGE("Failed to get car watchdog client binder object");
+ return false;
+ }
+ std::shared_ptr<ICarWatchdogClient> client = ICarWatchdogClient::fromBinder(binder);
+ if (client == nullptr) {
+ ALOGE("Failed to get ICarWatchdogClient from binder");
+ return false;
+ }
+ mTestClient = client;
+ mWatchdogServer->registerClient(client, TimeoutLength::TIMEOUT_NORMAL);
+ ALOGI("Successfully registered the client to car watchdog server");
+ return true;
+}
+
+void WatchdogClient::respondToWatchdog() {
+ if (mWatchdogServer == nullptr) {
+ ALOGW("Cannot respond to car watchdog daemon: car watchdog daemon is not connected");
+ return;
+ }
+ int sessionId;
+ {
+ Mutex::Autolock lock(mMutex);
+ sessionId = mCurrentSessionId;
+ }
+ if (isClientHealthy()) {
+ ndk::ScopedAStatus status = mWatchdogServer->tellClientAlive(mTestClient, sessionId);
+ if (!status.isOk()) {
+ ALOGE("Failed to call tellClientAlive(session id = %d): %d", sessionId,
+ status.getStatus());
+ return;
+ }
+ }
+}
+
+bool WatchdogClient::isClientHealthy() const {
+ // We consider that default vehicle HAL is healthy if we can get PERF_VEHICLE_SPEED value.
+ StatusCode status = StatusCode::TRY_AGAIN;
+ VehiclePropValue propValue = {.prop = (int32_t)VehicleProperty::PERF_VEHICLE_SPEED};
+ while (status == StatusCode::TRY_AGAIN) {
+ mVhalManager->get(propValue,
+ [&propValue, &status](StatusCode s, const VehiclePropValue& v) {
+ status = s;
+ if (s == StatusCode::OK) {
+ propValue = v;
+ }
+ });
+ }
+ return status == StatusCode::OK;
+}
+
+WatchdogClient::MessageHandlerImpl::MessageHandlerImpl(WatchdogClient* client) : mClient(client) {}
+
+void WatchdogClient::MessageHandlerImpl::handleMessage(const Message& message) {
+ switch (message.what) {
+ case WHAT_CHECK_ALIVE:
+ mClient->respondToWatchdog();
+ break;
+ default:
+ ALOGW("Unknown message: %d", message.what);
+ }
+}
+
+} // namespace V2_0
+} // namespace vehicle
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index ea75986..b8a606a 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -79,8 +79,6 @@
constexpr int WHEEL_FRONT_RIGHT = (int)VehicleAreaWheel::RIGHT_FRONT;
constexpr int WHEEL_REAR_LEFT = (int)VehicleAreaWheel::LEFT_REAR;
constexpr int WHEEL_REAR_RIGHT = (int)VehicleAreaWheel::RIGHT_REAR;
-constexpr int INITIAL_USER_INFO = (int)VehicleProperty::INITIAL_USER_INFO;
-constexpr int SWITCH_USER = (int)VehicleProperty::SWITCH_USER;
/**
* This property is used for test purpose to generate fake events. Here is the test package that
@@ -439,6 +437,16 @@
{.config =
{
+ .prop = toInt(VehicleProperty::TIRE_PRESSURE_DISPLAY_UNITS),
+ .access = VehiclePropertyAccess::READ_WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .configArray = {(int)VehicleUnit::KILOPASCAL, (int)VehicleUnit::PSI,
+ (int)VehicleUnit::BAR},
+ },
+ .initialValue = {.int32Values = {toInt(VehicleUnit::PSI)}}},
+
+ {.config =
+ {
.prop = toInt(VehicleProperty::CURRENT_GEAR),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
@@ -1028,6 +1036,14 @@
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
},
},
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION),
+ .access = VehiclePropertyAccess::READ_WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ },
+ },
};
} // impl
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
new file mode 100644
index 0000000..c49fadc
--- /dev/null
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "EmulatedUserHal"
+
+#include <cutils/log.h>
+#include <utils/SystemClock.h>
+
+#include "EmulatedUserHal.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+namespace impl {
+
+constexpr int INITIAL_USER_INFO = static_cast<int>(VehicleProperty::INITIAL_USER_INFO);
+constexpr int SWITCH_USER = static_cast<int>(VehicleProperty::SWITCH_USER);
+
+bool EmulatedUserHal::isSupported(int32_t prop) {
+ switch (prop) {
+ case INITIAL_USER_INFO:
+ case SWITCH_USER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::onSetProperty(
+ const VehiclePropValue& value) {
+ ALOGV("onSetProperty(): %s", toString(value).c_str());
+
+ switch (value.prop) {
+ case INITIAL_USER_INFO:
+ return onSetInitialUserInfoResponse(value);
+ case SWITCH_USER:
+ return onSetSwitchUserResponse(value);
+ default:
+ return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+ << "Unsupported property: " << toString(value);
+ }
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>>
+EmulatedUserHal::onSetInitialUserInfoResponse(const VehiclePropValue& value) {
+ if (value.value.int32Values.size() == 0) {
+ ALOGE("set(INITIAL_USER_INFO): no int32values, ignoring it: %s", toString(value).c_str());
+ return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+ << "no int32values on " << toString(value);
+ }
+
+ if (value.areaId != 0) {
+ ALOGD("set(INITIAL_USER_INFO) called from lshal; storing it: %s", toString(value).c_str());
+ mInitialUserResponseFromCmd.reset(new VehiclePropValue(value));
+ return {};
+ }
+
+ ALOGD("set(INITIAL_USER_INFO) called from Android: %s", toString(value).c_str());
+
+ int32_t requestId = value.value.int32Values[0];
+ if (mInitialUserResponseFromCmd != nullptr) {
+ ALOGI("replying INITIAL_USER_INFO with lshal value: %s",
+ toString(*mInitialUserResponseFromCmd).c_str());
+ return sendUserHalResponse(std::move(mInitialUserResponseFromCmd), requestId);
+ }
+
+ // Returns default response
+ auto updatedValue = std::unique_ptr<VehiclePropValue>(new VehiclePropValue);
+ updatedValue->prop = INITIAL_USER_INFO;
+ updatedValue->timestamp = elapsedRealtimeNano();
+ updatedValue->value.int32Values.resize(2);
+ updatedValue->value.int32Values[0] = requestId;
+ updatedValue->value.int32Values[1] = (int32_t)InitialUserInfoResponseAction::DEFAULT;
+
+ ALOGI("no lshal response; replying with InitialUserInfoResponseAction::DEFAULT: %s",
+ toString(*updatedValue).c_str());
+
+ return updatedValue;
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::onSetSwitchUserResponse(
+ const VehiclePropValue& value) {
+ if (value.value.int32Values.size() == 0) {
+ ALOGE("set(SWITCH_USER): no int32values, ignoring it: %s", toString(value).c_str());
+ return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+ << "no int32values on " << toString(value);
+ }
+
+ if (value.areaId != 0) {
+ ALOGD("set(SWITCH_USER) called from lshal; storing it: %s", toString(value).c_str());
+ mSwitchUserResponseFromCmd.reset(new VehiclePropValue(value));
+ return {};
+ }
+ ALOGD("set(SWITCH_USER) called from Android: %s", toString(value).c_str());
+
+ int32_t requestId = value.value.int32Values[0];
+ if (mSwitchUserResponseFromCmd != nullptr) {
+ ALOGI("replying SWITCH_USER with lshal value: %s",
+ toString(*mSwitchUserResponseFromCmd).c_str());
+ return sendUserHalResponse(std::move(mSwitchUserResponseFromCmd), requestId);
+ }
+
+ // Returns default response
+ auto updatedValue = std::unique_ptr<VehiclePropValue>(new VehiclePropValue);
+ updatedValue->prop = SWITCH_USER;
+ updatedValue->timestamp = elapsedRealtimeNano();
+ updatedValue->value.int32Values.resize(3);
+ updatedValue->value.int32Values[0] = requestId;
+ updatedValue->value.int32Values[1] = (int32_t)SwitchUserMessageType::VEHICLE_RESPONSE;
+ updatedValue->value.int32Values[2] = (int32_t)SwitchUserStatus::SUCCESS;
+
+ ALOGI("no lshal response; replying with VEHICLE_RESPONSE / SUCCESS: %s",
+ toString(*updatedValue).c_str());
+
+ return updatedValue;
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::sendUserHalResponse(
+ std::unique_ptr<VehiclePropValue> response, int32_t requestId) {
+ switch (response->areaId) {
+ case 1:
+ ALOGD("returning response with right request id");
+ response->value.int32Values[0] = requestId;
+ break;
+ case 2:
+ ALOGD("returning response with wrong request id");
+ response->value.int32Values[0] = -requestId;
+ break;
+ case 3:
+ ALOGD("not generating a property change event because of lshal prop: %s",
+ toString(*response).c_str());
+ return android::base::Error(static_cast<int>(StatusCode::NOT_AVAILABLE))
+ << "not generating a property change event because of lshal prop: "
+ << toString(*response);
+ default:
+ ALOGE("invalid action on lshal response: %s", toString(*response).c_str());
+ return android::base::Error(static_cast<int>(StatusCode::INTERNAL_ERROR))
+ << "invalid action on lshal response: " << toString(*response);
+ }
+
+ ALOGD("updating property to: %s", toString(*response).c_str());
+
+ return response;
+}
+
+void EmulatedUserHal::showDumpHelp(int fd) {
+ dprintf(fd, "%s: dumps state used for user management\n", kUserHalDumpOption);
+}
+
+void EmulatedUserHal::dump(int fd, std::string indent) {
+ if (mInitialUserResponseFromCmd != nullptr) {
+ dprintf(fd, "%sInitialUserInfo response: %s\n", indent.c_str(),
+ toString(*mInitialUserResponseFromCmd).c_str());
+ } else {
+ dprintf(fd, "%sNo InitialUserInfo response\n", indent.c_str());
+ }
+ if (mSwitchUserResponseFromCmd != nullptr) {
+ dprintf(fd, "%sSwitchUser response: %s\n", indent.c_str(),
+ toString(*mSwitchUserResponseFromCmd).c_str());
+ } else {
+ dprintf(fd, "%sNo SwitchUser response\n", indent.c_str());
+ }
+}
+
+} // namespace impl
+
+} // namespace V2_0
+} // namespace vehicle
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h
new file mode 100644
index 0000000..b25efcb
--- /dev/null
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
+#define android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
+
+#include <android-base/result.h>
+
+#include <android/hardware/automotive/vehicle/2.0/types.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+namespace impl {
+
+constexpr char kUserHalDumpOption[] = "--user-hal";
+
+/**
+ * Class used to emulate User HAL behavior through lshal debug requests.
+ */
+class EmulatedUserHal {
+ public:
+ EmulatedUserHal() {}
+
+ ~EmulatedUserHal() = default;
+
+ /**
+ * Checks if the emulator can handle the property.
+ */
+ bool isSupported(int32_t prop);
+
+ /**
+ * Lets the emulator handle the property.
+ *
+ * @return updated property and StatusCode
+ */
+ android::base::Result<std::unique_ptr<VehiclePropValue>> onSetProperty(
+ const VehiclePropValue& value);
+
+ /**
+ * Shows the User HAL emulation help.
+ */
+ void showDumpHelp(int fd);
+
+ /**
+ * Dump its contents.
+ */
+ void dump(int fd, std::string indent);
+
+ private:
+ /**
+ * INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change
+ * indicating what the initial user should be.
+ *
+ * During normal circumstances, the emulator will reply right away, passing a response if
+ * InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which
+ * user to boot).
+ *
+ * But during development / testing, the behavior can be changed using lshal dump, which must
+ * use the areaId to indicate what should happen next.
+ *
+ * So, the behavior of set(INITIAL_USER_INFO) is:
+ *
+ * - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called
+ * by lshal).
+ * - else if mInitialUserResponseFromCmd is not set, return a response with the same request id
+ * and InitialUserInfoResponseAction::DEFAULT
+ * - else the behavior is defined by the areaId on mInitialUserResponseFromCmd:
+ * - if it's 1, reply with mInitialUserResponseFromCmd and the right request id
+ * - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can
+ * test this error scenario)
+ * - if it's 3, then don't send a property change (so Android can emulate a timeout)
+ *
+ */
+ android::base::Result<std::unique_ptr<VehiclePropValue>> onSetInitialUserInfoResponse(
+ const VehiclePropValue& value);
+
+ /**
+ * Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage.
+ */
+ android::base::Result<std::unique_ptr<VehiclePropValue>> onSetSwitchUserResponse(
+ const VehiclePropValue& value);
+
+ android::base::Result<std::unique_ptr<VehiclePropValue>> sendUserHalResponse(
+ std::unique_ptr<VehiclePropValue> response, int32_t requestId);
+
+ std::unique_ptr<VehiclePropValue> mInitialUserResponseFromCmd;
+ std::unique_ptr<VehiclePropValue> mSwitchUserResponseFromCmd;
+};
+
+} // namespace impl
+
+} // namespace V2_0
+} // namespace vehicle
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
index ce7dc65..7f9362f 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
@@ -38,9 +38,6 @@
class EmulatedPassthroughConnector : public PassthroughConnector {
public:
bool onDump(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
-
- private:
- void dumpUserHal(int fd, std::string indent);
};
bool EmulatedPassthroughConnector::onDump(const hidl_handle& handle,
@@ -50,12 +47,12 @@
if (options.size() > 0) {
if (options[0] == "--help") {
dprintf(fd, "Emulator-specific usage:\n");
- dprintf(fd, "--user-hal: dumps state used for user management \n");
+ mEmulatedUserHal.showDumpHelp(fd);
dprintf(fd, "\n");
// Include caller's help options
return true;
- } else if (options[0] == "--user-hal") {
- dumpUserHal(fd, "");
+ } else if (options[0] == kUserHalDumpOption) {
+ mEmulatedUserHal.dump(fd, "");
return false;
} else {
@@ -65,27 +62,12 @@
}
dprintf(fd, "Emulator-specific state:\n");
- dumpUserHal(fd, " ");
+ mEmulatedUserHal.dump(fd, " ");
dprintf(fd, "\n");
return true;
}
-void EmulatedPassthroughConnector::dumpUserHal(int fd, std::string indent) {
- if (mInitialUserResponseFromCmd != nullptr) {
- dprintf(fd, "%sInitialUserInfo response: %s\n", indent.c_str(),
- toString(*mInitialUserResponseFromCmd).c_str());
- } else {
- dprintf(fd, "%sNo InitialUserInfo response\n", indent.c_str());
- }
- if (mSwitchUserResponseFromCmd != nullptr) {
- dprintf(fd, "%sSwitchUser response: %s\n", indent.c_str(),
- toString(*mSwitchUserResponseFromCmd).c_str());
- } else {
- dprintf(fd, "%sNo SwitchUser response\n", indent.c_str());
- }
-}
-
PassthroughConnectorPtr makeEmulatedPassthroughConnector() {
return std::make_unique<EmulatedPassthroughConnector>();
}
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp
index bdc5244..02c00c1 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp
@@ -175,9 +175,7 @@
// here, since we never send the control signal back, the value of 'updateStatus' flag
// does not matter here.
auto status = mVehicleClient->setProperty(propValue, updateStatus);
- if (status != StatusCode::OK) {
- return status;
- }
+ return status;
} else if (mHvacPowerProps.count(propValue.prop)) {
auto hvacPowerOn = mPropStore->readValueOrNull(
toInt(VehicleProperty::HVAC_POWER_ON),
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
index 70e39eb..ad5096e 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
@@ -181,6 +181,23 @@
}
StatusCode VehicleHalServer::onSetProperty(const VehiclePropValue& value, bool updateStatus) {
+ if (mEmulatedUserHal.isSupported(value.prop)) {
+ LOG(INFO) << "onSetProperty(): property " << value.prop << " will be handled by UserHal";
+
+ const auto& ret = mEmulatedUserHal.onSetProperty(value);
+ if (!ret.ok()) {
+ LOG(ERROR) << "onSetProperty(): HAL returned error: " << ret.error().message();
+ return StatusCode(ret.error().code());
+ }
+ auto updatedValue = ret.value().get();
+ if (updatedValue != nullptr) {
+ LOG(INFO) << "onSetProperty(): updating property returned by HAL: "
+ << toString(*updatedValue);
+ onPropertyValueFromCar(*updatedValue, updateStatus);
+ }
+ return StatusCode::OK;
+ }
+
// Some properties need to be treated non-trivially
switch (value.prop) {
case kGenerateFakeDataControllingProperty:
@@ -245,10 +262,6 @@
break;
}
break;
- case INITIAL_USER_INFO:
- return onSetInitialUserInfoResponse(value, updateStatus);
- case SWITCH_USER:
- return onSetSwitchUserResponse(value, updateStatus);
default:
break;
}
@@ -262,165 +275,4 @@
return StatusCode::OK;
}
-/**
- * INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change
- * indicating what the initial user should be.
- *
- * During normal circumstances, the emulator will reply right away, passing a response if
- * InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which user
- * to boot).
- *
- * But during development / testing, the behavior can be changed using lshal dump, which must use
- * the areaId to indicate what should happen next.
- *
- * So, the behavior of set(INITIAL_USER_INFO) is:
- *
- * - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called by
- * lshal).
- * - else if mInitialUserResponseFromCmd is not set, return a response with the same request id and
- * InitialUserInfoResponseAction::DEFAULT
- * - else the behavior is defined by the areaId on mInitialUserResponseFromCmd:
- * - if it's 1, reply with mInitialUserResponseFromCmd and the right request id
- * - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can test
- * this error scenario)
- * - if it's 3, then don't send a property change (so Android can emulate a timeout)
- *
- */
-StatusCode VehicleHalServer::onSetInitialUserInfoResponse(const VehiclePropValue& value,
- bool updateStatus) {
- // TODO: LOG calls below might be more suited to be DEBUG, but those are not being logged
- // (even when explicitly calling setprop log.tag. As this class should be using ALOG instead of
- // LOG, it's not worth investigating why...
-
- if (value.value.int32Values.size() == 0) {
- LOG(ERROR) << "set(INITIAL_USER_INFO): no int32values, ignoring it: " << toString(value);
- return StatusCode::INVALID_ARG;
- }
-
- if (value.areaId != 0) {
- LOG(INFO) << "set(INITIAL_USER_INFO) called from lshal; storing it: " << toString(value);
- mInitialUserResponseFromCmd.reset(new VehiclePropValue(value));
- return StatusCode::OK;
- }
- LOG(INFO) << "set(INITIAL_USER_INFO) called from Android: " << toString(value);
-
- int32_t requestId = value.value.int32Values[0];
-
- // Create the update property and set common values
- auto updatedValue = createVehiclePropValue(VehiclePropertyType::MIXED, 0);
- updatedValue->prop = INITIAL_USER_INFO;
- updatedValue->timestamp = elapsedRealtimeNano();
-
- if (mInitialUserResponseFromCmd == nullptr) {
- updatedValue->value.int32Values.resize(2);
- updatedValue->value.int32Values[0] = requestId;
- updatedValue->value.int32Values[1] = (int32_t)InitialUserInfoResponseAction::DEFAULT;
- LOG(INFO) << "no lshal response; returning InitialUserInfoResponseAction::DEFAULT: "
- << toString(*updatedValue);
- onPropertyValueFromCar(*updatedValue, updateStatus);
- return StatusCode::OK;
- }
-
- // mInitialUserResponseFromCmd is used for just one request
- std::unique_ptr<VehiclePropValue> response = std::move(mInitialUserResponseFromCmd);
-
- // TODO(b/150409377): rather than populate the raw values directly, it should use the
- // libraries that convert a InitialUserInfoResponse into a VehiclePropValue)
-
- switch (response->areaId) {
- case 1:
- LOG(INFO) << "returning response with right request id";
- *updatedValue = *response;
- updatedValue->areaId = 0;
- updatedValue->value.int32Values[0] = requestId;
- break;
- case 2:
- LOG(INFO) << "returning response with wrong request id";
- *updatedValue = *response;
- updatedValue->areaId = 0;
- updatedValue->value.int32Values[0] = -requestId;
- break;
- case 3:
- LOG(INFO) << "not generating a property change event because of lshal prop: "
- << toString(*response);
- return StatusCode::OK;
- default:
- LOG(ERROR) << "invalid action on lshal response: " << toString(*response);
- return StatusCode::INTERNAL_ERROR;
- }
-
- LOG(INFO) << "updating property to: " << toString(*updatedValue);
- onPropertyValueFromCar(*updatedValue, updateStatus);
- return StatusCode::OK;
-}
-
-/**
- * Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage.
- */
-StatusCode VehicleHalServer::onSetSwitchUserResponse(const VehiclePropValue& value,
- bool updateStatus) {
- if (value.value.int32Values.size() == 0) {
- LOG(ERROR) << "set(SWITCH_USER): no int32values, ignoring it: " << toString(value);
- return StatusCode::INVALID_ARG;
- }
-
- if (value.areaId != 0) {
- LOG(INFO) << "set(SWITCH_USER) called from lshal; storing it: " << toString(value);
- mSwitchUserResponseFromCmd.reset(new VehiclePropValue(value));
- return StatusCode::OK;
- }
- LOG(INFO) << "set(SWITCH_USER) called from Android: " << toString(value);
-
- int32_t requestId = value.value.int32Values[0];
-
- // Create the update property and set common values
- auto updatedValue = createVehiclePropValue(VehiclePropertyType::MIXED, 0);
- updatedValue->prop = SWITCH_USER;
- updatedValue->timestamp = elapsedRealtimeNano();
-
- if (mSwitchUserResponseFromCmd == nullptr) {
- updatedValue->value.int32Values.resize(3);
- updatedValue->value.int32Values[0] = requestId;
- updatedValue->value.int32Values[1] = (int32_t)SwitchUserMessageType::VEHICLE_RESPONSE;
- updatedValue->value.int32Values[2] = (int32_t)SwitchUserStatus::SUCCESS;
- LOG(INFO) << "no lshal response; returning VEHICLE_RESPONSE / SUCCESS: "
- << toString(*updatedValue);
- onPropertyValueFromCar(*updatedValue, updateStatus);
- return StatusCode::OK;
- }
-
- // mSwitchUserResponseFromCmd is used for just one request
- std::unique_ptr<VehiclePropValue> response = std::move(mSwitchUserResponseFromCmd);
-
- // TODO(b/150409377): move code below to a local function like sendUserHalResponse(),
- // as it's the same for all (like onSetInitialUserInfoResponse)
-
- switch (response->areaId) {
- case 1:
- LOG(INFO) << "returning response with right request id";
- *updatedValue = *response;
- updatedValue->areaId = 0;
- updatedValue->value.int32Values[0] = requestId;
- break;
- case 2:
- LOG(INFO) << "returning response with wrong request id";
- *updatedValue = *response;
- updatedValue->areaId = 0;
- updatedValue->value.int32Values[0] = -requestId;
- break;
- case 3:
- LOG(INFO) << "not generating a property change event because of lshal prop: "
- << toString(*response);
- return StatusCode::OK;
- default:
- LOG(ERROR) << "invalid action on lshal response: " << toString(*response);
- return StatusCode::INTERNAL_ERROR;
- }
-
- LOG(INFO) << "updating property to: " << toString(*updatedValue);
- onPropertyValueFromCar(*updatedValue, updateStatus);
-
- return StatusCode::OK;
-}
-
} // namespace android::hardware::automotive::vehicle::V2_0::impl
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
index 20e094a..2841fbe 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
@@ -19,6 +19,7 @@
#include <vhal_v2_0/VehicleObjectPool.h>
#include <vhal_v2_0/VehicleServer.h>
+#include "EmulatedUserHal.h"
#include "GeneratorHub.h"
namespace android::hardware::automotive::vehicle::V2_0::impl {
@@ -53,15 +54,10 @@
VehiclePropValuePtr createHwInputKeyProp(VehicleHwKeyInputAction action, int32_t keyCode,
int32_t targetDisplay);
- StatusCode onSetInitialUserInfoResponse(const VehiclePropValue& value, bool updateStatus);
- StatusCode onSetSwitchUserResponse(const VehiclePropValue& value, bool updateStatus);
-
// data members
protected:
- // TODO(b/146207078): it might be clearer to move members below to an EmulatedUserHal class
- std::unique_ptr<VehiclePropValue> mInitialUserResponseFromCmd;
- std::unique_ptr<VehiclePropValue> mSwitchUserResponseFromCmd;
+ EmulatedUserHal mEmulatedUserHal;
private:
GeneratorHub mGeneratorHub{
diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal
index 23f9135..733e7dc 100644
--- a/automotive/vehicle/2.0/types.hal
+++ b/automotive/vehicle/2.0/types.hal
@@ -606,8 +606,23 @@
/**
* Tire pressure
*
- * min/max value indicates tire pressure sensor range. Each tire will have a separate min/max
- * value denoted by its areaConfig.areaId.
+ * Each tires is identified by its areaConfig.areaId config and their
+ * minFloatValue/maxFloatValue are used to store OEM recommended pressure
+ * range.
+ * The Min value in the areaConfig data represents the lower bound of
+ * the recommended tire pressure.
+ * The Max value in the areaConfig data represents the upper bound of
+ * the recommended tire pressure.
+ * For example:
+ * The following areaConfig indicates the recommended tire pressure
+ * of left_front tire is from 200.0 KILOPASCAL to 240.0 KILOPASCAL.
+ * .areaConfigs = {
+ * VehicleAreaConfig {
+ * .areaId = VehicleAreaWheel::LEFT_FRONT,
+ * .minFloatValue = 200.0,
+ * .maxFloatValue = 240.0,
+ * }
+ * },
*
* @change_mode VehiclePropertyChangeMode:CONTINUOUS
* @access VehiclePropertyAccess:READ
@@ -786,7 +801,8 @@
/*
* HVAC Properties
*
- * Additional rules for mapping a zoned HVAC property to AreaIDs:
+ * Additional rules for mapping a zoned HVAC property (except
+ * HVAC_MAX_DEFROST_ON) to AreaIDs:
* - Every seat in VehicleAreaSeat that is available in the car, must be
* part of an AreaID in the AreaID array.
*
@@ -919,6 +935,11 @@
* possible. Any parameters modified as a side effect of turning on/off
* the MAX DEFROST parameter shall generate onPropertyEvent() callbacks to
* the VHAL.
+ * The AreaIDs for HVAC_MAX_DEFROST_ON indicate MAX DEFROST can be controlled
+ * in the area.
+ * For example:
+ * areaConfig.areaId = {ROW_1_LEFT | ROW_1_RIGHT} indicates HVAC_MAX_DEFROST_ON
+ * only can be controlled for the front rows.
*
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
@@ -2646,9 +2667,9 @@
* If the request succeeded and Android has 3 users (0, 10, 11), the response would be:
*
* int32[0]: -108 // request id
- * int32[1]: 5 // messageType = SwitchUserMessageType::ANDROID_SWITCH
+ * int32[1]: 5 // messageType = SwitchUserMessageType::ANDROID_POST_SWITCH
* int32[2]: 11 // target user id
- * int32[3]: 11 // target user id flags (none)
+ * int32[3]: 0 // target user id flags (none)
* int32[4]: 11 // current user
* int32[5]: 0 // current user flags (none)
* int32[6]: 3 // number of users
@@ -2664,17 +2685,21 @@
*
* 5.ANDROID_POST_SWITCH
* ---------------------
- * Called by the Android System after a request to switch a user was made
+ * Called by the Android System after a request to switch a user was made.
*
* This property is called after switch requests of any type (i.e., LEGACY_ANDROID_SWITCH,
* ANDROID_SWITCH, or VEHICLE_REQUEST) and can be used to determine if the request succeeded or
* failed:
*
- * 1. When it succeeded, it's called when the Android user is in the boot locked state and the
- * value of the current and target users ids in the response are different. This would be
- * equivalent to receiving an Intent.ACTION_LOCKED_BOOT_COMPLETED in an Android app.
+ * 1. When it succeeded, it's called when the Android user is in the unlocked state and the
+ * value of the current and target users ids in the response are the same. This would be
+ * equivalent to receiving an Intent.ACTION_USER_UNLOCKED in an Android app.
* 2. When it failed it's called right away and the value of the current and target users ids
- * in the response are the same.
+ * in the response are different (as the current user didn't change to the target).
+ * 3. If a new switch request is made before the HAL responded to the previous one or before
+ * the user was unlocked, then the ANDROID_POST_SWITCH request is not made. For example,
+ * the driver could accidentally switch to the wrong user which has lock crentials, then
+ * switch to the right one before entering the credentials.
*
* The HAL can update its internal state once it receives this request, but it doesn't need to
* reply back to the Android System.
@@ -2788,7 +2813,7 @@
*
* To query the association, the Android system gets the property, passing a VehiclePropValue
* containing the types of associations are being queried, as defined by
- * UserIdentificationGetRequest. The HAL must return right away, updating the VehiclePropValue
+ * UserIdentificationGetRequest. The HAL must return right away, returning a VehiclePropValue
* with a UserIdentificationResponse. Notice that user identification should have already
* happened while system is booting up and the VHAL implementation should only return the
* already identified association (like the key FOB used to unlock the car), instead of starting
@@ -2803,45 +2828,50 @@
* For example, to query if the current user (10) is associated with the FOB that unlocked the
* car and a custom mechanism provided by the OEM, the request would be:
*
- * int32[0]: 10 (Android user id)
- * int32[1]: 0 (Android user flags)
- * int32[2]: 2 (number of types queried)
- * int32[3]: 1 (1st type queried, UserIdentificationAssociationType::KEY_FOB)
- * int32[4]: 101 (2nd type queried, UserIdentificationAssociationType::CUSTOM_1)
+ * int32[0]: 42 // request id
+ * int32[1]: 10 (Android user id)
+ * int32[2]: 0 (Android user flags)
+ * int32[3]: 2 (number of types queried)
+ * int32[4]: 1 (1st type queried, UserIdentificationAssociationType::KEY_FOB)
+ * int32[5]: 101 (2nd type queried, UserIdentificationAssociationType::CUSTOM_1)
*
* If the user is associated with the FOB but not with the custom mechanism, the response would
* be:
*
- * int32[9]: 2 (number of associations in the response)
- * int32[1]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB)
- * int32[2]: 2 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER)
- * int32[3]: 101 (2st type: UserIdentificationAssociationType::CUSTOM_1)
- * int32[4]: 4 (2nd value: UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER)
+ * int32[0]: 42 // request id
+ * int32[1]: 2 (number of associations in the response)
+ * int32[2]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB)
+ * int32[3]: 2 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER)
+ * int32[4]: 101 (2st type: UserIdentificationAssociationType::CUSTOM_1)
+ * int32[5]: 4 (2nd value: UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER)
*
* Then to associate the user with the custom mechanism, a set request would be made:
*
- * int32[0]: 10 (Android user id)
- * int32[0]: 0 (Android user flags)
- * int32[1]: 1 (number of associations being set)
- * int32[2]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1)
- * int32[3]: 1 (1st value: UserIdentificationAssociationSETValue::ASSOCIATE_CURRENT_USER)
+ * int32[0]: 42 // request id
+ * int32[1]: 10 (Android user id)
+ * int32[2]: 0 (Android user flags)
+ * int32[3]: 1 (number of associations being set)
+ * int32[4]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1)
+ * int32[5]: 1 (1st value: UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER)
*
* If the request succeeded, the response would be simply:
*
- * int32[0]: 2 (number of associations in the response)
- * int32[1]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1)
- * int32[2]: 1 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER)
+ * int32[0]: 42 // request id
+ * int32[1]: 1 (number of associations in the response)
+ * int32[2]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1)
+ * int32[3]: 1 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER)
*
* Notice that the set request adds associations, but doesn't remove the existing ones. In the
* example above, the end state would be 2 associations (FOB and CUSTOM_1). If we wanted to
* associate the user with just CUSTOM_1 but not FOB, then the request should have been:
*
- * int32[0]: 10 (Android user id)
- * int32[1]: 2 (number of types set)
- * int32[2]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB)
- * int32[3]: 2 (1st value: UserIdentificationAssociationValue::DISASSOCIATE_CURRENT_USER)
- * int32[3]: 101 (2nd type: UserIdentificationAssociationType::CUSTOM_1)
- * int32[5]: 1 (2nd value: UserIdentificationAssociationValue::ASSOCIATE_CURRENT_USER)
+ * int32[0]: 42 // request id
+ * int32[1]: 10 (Android user id)
+ * int32[2]: 2 (number of types set)
+ * int32[3]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB)
+ * int32[4]: 2 (1st value: UserIdentificationAssociationValue::DISASSOCIATE_CURRENT_USER)
+ * int32[5]: 101 (2nd type: UserIdentificationAssociationType::CUSTOM_1)
+ * int32[6]: 1 (2nd value: UserIdentificationAssociationValue::ASSOCIATE_CURRENT_USER)
*
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
@@ -4614,6 +4644,11 @@
*/
struct UserIdentificationGetRequest {
/**
+ * Id of the request being responded.
+ */
+ UserRequestId requestId;
+
+ /**
* Information about the current foreground Android user.
*/
UserInfo userInfo;
@@ -4637,6 +4672,11 @@
*/
struct UserIdentificationSetRequest {
/**
+ * Id of the request being responded.
+ */
+ UserRequestId requestId;
+
+ /**
* Information about the current foreground Android user.
*/
UserInfo userInfo;
@@ -4649,7 +4689,7 @@
/**
* Associations being set.
*/
- vec<UserIdentificationAssociationSetValue> associations;
+ vec<UserIdentificationSetAssociation> associations;
};
/**
@@ -4660,6 +4700,11 @@
*/
struct UserIdentificationResponse {
/**
+ * Id of the request being responded.
+ */
+ UserRequestId requestId;
+
+ /**
* Number of associations being returned.
*/
int32_t numberAssociation;
diff --git a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
index 9fa128d..def59de 100644
--- a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
+++ b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
@@ -4,5 +4,5 @@
capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
user bluetooth
group bluetooth
- writepid /dev/stune/foreground/tasks
+ task_profiles HighPerformance
diff --git a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
index 49f0be3..5c7cbf4 100644
--- a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
+++ b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
@@ -5,5 +5,5 @@
capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
user bluetooth
group bluetooth
- writepid /dev/stune/foreground/tasks
+ task_profiles HighPerformance
diff --git a/bluetooth/a2dp/1.0/vts/functional/Android.bp b/bluetooth/a2dp/1.0/vts/functional/Android.bp
index 5b8410a..df18fcc 100644
--- a/bluetooth/a2dp/1.0/vts/functional/Android.bp
+++ b/bluetooth/a2dp/1.0/vts/functional/Android.bp
@@ -23,5 +23,5 @@
"android.hardware.bluetooth.a2dp@1.0",
"libbluetooth-types",
],
- test_suites: ["general-tests", "vts-core"],
+ test_suites: ["general-tests", "vts"],
}
diff --git a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
index 7c58ef3..30b965d 100644
--- a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
+++ b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
@@ -76,7 +76,11 @@
for (const auto value : ValidMergeStatusValues()) {
EXPECT_TRUE(boot->setSnapshotMergeStatus(value).withDefault(false));
auto status = boot->getSnapshotMergeStatus();
- EXPECT_EQ(status, value);
+ if (value == MergeStatus::SNAPSHOTTED) {
+ EXPECT_TRUE(status == MergeStatus::SNAPSHOTTED || status == MergeStatus::NONE);
+ } else {
+ EXPECT_EQ(status, value);
+ }
}
}
diff --git a/camera/common/1.0/default/CameraModule.cpp b/camera/common/1.0/default/CameraModule.cpp
index 467c121..86f26e4 100644
--- a/camera/common/1.0/default/CameraModule.cpp
+++ b/camera/common/1.0/default/CameraModule.cpp
@@ -194,16 +194,6 @@
}
}
- // Always add a default for the pre-correction active array if the vendor chooses to omit this
- camera_metadata_entry entry = chars.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
- if (entry.count == 0) {
- Vector<int32_t> preCorrectionArray;
- entry = chars.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- preCorrectionArray.appendArray(entry.data.i32, entry.count);
- chars.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, preCorrectionArray);
- derivedCharKeys.push(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
- }
-
// Add those newly added keys to AVAILABLE_CHARACTERISTICS_KEYS
// This has to be done at this end of this function.
if (derivedCharKeys.size() > 0) {
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
index 64cf321..52ade97 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc usb
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
index e8549ed..63ded90 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
@@ -7,4 +7,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
index 2dfac76..953d1af 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
@@ -7,4 +7,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
index 913561b..f7ac9f8 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
index fd4826e..a32dd46 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index 05b8b47..bf5fbfe 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -18,12 +18,13 @@
#include <algorithm>
#include <chrono>
+#include <condition_variable>
+#include <list>
#include <mutex>
#include <regex>
#include <string>
#include <unordered_map>
#include <unordered_set>
-#include <condition_variable>
#include <inttypes.h>
@@ -165,6 +166,26 @@
YUV_REPROCESS,
};
+enum SystemCameraKind {
+ /**
+ * These camera devices are visible to all apps and system components alike
+ */
+ PUBLIC = 0,
+
+ /**
+ * These camera devices are visible only to processes having the
+ * android.permission.SYSTEM_CAMERA permission. They are not exposed to 3P
+ * apps.
+ */
+ SYSTEM_ONLY_CAMERA,
+
+ /**
+ * These camera devices are visible only to HAL clients (that try to connect
+ * on a hwbinder thread).
+ */
+ HIDDEN_SECURE_CAMERA
+};
+
namespace {
// "device@<version>/legacy/<id>"
const char *kDeviceNameRE = "device@([0-9]+\\.[0-9]+)/%s/(.+)";
@@ -851,6 +872,8 @@
static Status isAutoFocusModeAvailable(
CameraParameters &cameraParams, const char *mode) ;
static Status isMonochromeCamera(const camera_metadata_t *staticMeta);
+ static Status getSystemCameraKind(const camera_metadata_t* staticMeta,
+ SystemCameraKind* systemCameraKind);
// Used by switchToOffline where a new result queue is created for offline reqs
void updateInflightResultQueue(std::shared_ptr<ResultMetadataQueue> resultQueue);
@@ -2555,6 +2578,90 @@
}
}
+TEST_P(CameraHidlTest, systemCameraTest) {
+ hidl_vec<hidl_string> cameraDeviceNames = getCameraDeviceNames(mProvider);
+ std::map<std::string, std::list<SystemCameraKind>> hiddenPhysicalIdToLogicalMap;
+ for (const auto& name : cameraDeviceNames) {
+ int deviceVersion = getCameraDeviceVersion(name, mProviderType);
+ switch (deviceVersion) {
+ case CAMERA_DEVICE_API_VERSION_3_6:
+ case CAMERA_DEVICE_API_VERSION_3_5:
+ case CAMERA_DEVICE_API_VERSION_3_4:
+ case CAMERA_DEVICE_API_VERSION_3_3:
+ case CAMERA_DEVICE_API_VERSION_3_2: {
+ ::android::sp<::android::hardware::camera::device::V3_2::ICameraDevice> device3_x;
+ ALOGI("getCameraCharacteristics: Testing camera device %s", name.c_str());
+ Return<void> ret;
+ ret = mProvider->getCameraDeviceInterface_V3_x(
+ name, [&](auto status, const auto& device) {
+ ALOGI("getCameraDeviceInterface_V3_x returns status:%d", (int)status);
+ ASSERT_EQ(Status::OK, status);
+ ASSERT_NE(device, nullptr);
+ device3_x = device;
+ });
+ ASSERT_TRUE(ret.isOk());
+
+ ret = device3_x->getCameraCharacteristics([&](auto status, const auto& chars) {
+ ASSERT_EQ(status, Status::OK);
+ const camera_metadata_t* staticMeta =
+ reinterpret_cast<const camera_metadata_t*>(chars.data());
+ ASSERT_NE(staticMeta, nullptr);
+ Status rc = isLogicalMultiCamera(staticMeta);
+ ASSERT_TRUE(Status::OK == rc || Status::METHOD_NOT_SUPPORTED == rc);
+ if (Status::METHOD_NOT_SUPPORTED == rc) {
+ return;
+ }
+ std::unordered_set<std::string> physicalIds;
+ ASSERT_EQ(Status::OK, getPhysicalCameraIds(staticMeta, &physicalIds));
+ SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC;
+ rc = getSystemCameraKind(staticMeta, &systemCameraKind);
+ ASSERT_EQ(rc, Status::OK);
+ for (auto physicalId : physicalIds) {
+ bool isPublicId = false;
+ for (auto& deviceName : cameraDeviceNames) {
+ std::string publicVersion, publicId;
+ ASSERT_TRUE(::matchDeviceName(deviceName, mProviderType, &publicVersion,
+ &publicId));
+ if (physicalId == publicId) {
+ isPublicId = true;
+ break;
+ }
+ }
+ // For hidden physical cameras, collect their associated logical cameras
+ // and store the system camera kind.
+ if (!isPublicId) {
+ auto it = hiddenPhysicalIdToLogicalMap.find(physicalId);
+ if (it == hiddenPhysicalIdToLogicalMap.end()) {
+ hiddenPhysicalIdToLogicalMap.insert(std::make_pair(
+ physicalId, std::list<SystemCameraKind>(systemCameraKind)));
+ } else {
+ it->second.push_back(systemCameraKind);
+ }
+ }
+ }
+ });
+ ASSERT_TRUE(ret.isOk());
+ } break;
+ case CAMERA_DEVICE_API_VERSION_1_0: {
+ // Not applicable
+ } break;
+ default: {
+ ALOGE("%s: Unsupported device version %d", __func__, deviceVersion);
+ ADD_FAILURE();
+ } break;
+ }
+ }
+
+ // Check that the system camera kind of the logical cameras associated with
+ // each hidden physical camera is the same.
+ for (const auto& it : hiddenPhysicalIdToLogicalMap) {
+ SystemCameraKind neededSystemCameraKind = it.second.front();
+ for (auto foundSystemCamera : it.second) {
+ ASSERT_EQ(neededSystemCameraKind, foundSystemCamera);
+ }
+ }
+}
+
// Verify that the static camera characteristics can be retrieved
// successfully.
TEST_P(CameraHidlTest, getCameraCharacteristics) {
@@ -5671,6 +5778,39 @@
return ret;
}
+Status CameraHidlTest::getSystemCameraKind(const camera_metadata_t* staticMeta,
+ SystemCameraKind* systemCameraKind) {
+ Status ret = Status::OK;
+ if (nullptr == staticMeta || nullptr == systemCameraKind) {
+ return Status::ILLEGAL_ARGUMENT;
+ }
+
+ camera_metadata_ro_entry entry;
+ int rc = find_camera_metadata_ro_entry(staticMeta, ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
+ &entry);
+ if (0 != rc) {
+ return Status::ILLEGAL_ARGUMENT;
+ }
+
+ if (entry.count == 1 &&
+ entry.data.u8[0] == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA) {
+ *systemCameraKind = SystemCameraKind::HIDDEN_SECURE_CAMERA;
+ return ret;
+ }
+
+ // Go through the capabilities and check if it has
+ // ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
+ for (size_t i = 0; i < entry.count; ++i) {
+ uint8_t capability = entry.data.u8[i];
+ if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA) {
+ *systemCameraKind = SystemCameraKind::SYSTEM_ONLY_CAMERA;
+ return ret;
+ }
+ }
+ *systemCameraKind = SystemCameraKind::PUBLIC;
+ return ret;
+}
+
// Check whether this is a monochrome camera using the static camera characteristics.
Status CameraHidlTest::isMonochromeCamera(const camera_metadata_t *staticMeta) {
Status ret = Status::METHOD_NOT_SUPPORTED;
@@ -6391,8 +6531,10 @@
const hidl_vec<hidl_string>& deviceNames) {
const camera_metadata_t* metadata = (camera_metadata_t*)chars.data();
ASSERT_NE(nullptr, metadata);
-
- Status rc = isLogicalMultiCamera(metadata);
+ SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC;
+ Status rc = getSystemCameraKind(metadata, &systemCameraKind);
+ ASSERT_EQ(rc, Status::OK);
+ rc = isLogicalMultiCamera(metadata);
ASSERT_TRUE(Status::OK == rc || Status::METHOD_NOT_SUPPORTED == rc);
if (Status::METHOD_NOT_SUPPORTED == rc) {
return;
@@ -6411,6 +6553,7 @@
ASSERT_NE(physicalId, cameraId);
bool isPublicId = false;
std::string fullPublicId;
+ SystemCameraKind physSystemCameraKind = SystemCameraKind::PUBLIC;
for (auto& deviceName : deviceNames) {
std::string publicVersion, publicId;
ASSERT_TRUE(::matchDeviceName(deviceName, mProviderType, &publicVersion, &publicId));
@@ -6434,9 +6577,16 @@
ret = subDevice->getCameraCharacteristics(
[&](auto status, const auto& chars) {
ASSERT_EQ(Status::OK, status);
- retcode = find_camera_metadata_ro_entry(
- (const camera_metadata_t *)chars.data(),
- ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
+ const camera_metadata_t* staticMeta =
+ reinterpret_cast<const camera_metadata_t*>(chars.data());
+ rc = getSystemCameraKind(staticMeta, &physSystemCameraKind);
+ ASSERT_EQ(rc, Status::OK);
+ // Make sure that the system camera kind of a non-hidden
+ // physical cameras is the same as the logical camera associated
+ // with it.
+ ASSERT_EQ(physSystemCameraKind, systemCameraKind);
+ retcode = find_camera_metadata_ro_entry(staticMeta,
+ ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2);
ASSERT_EQ(hasZoomRatioRange, subCameraHasZoomRatioRange);
});
@@ -6452,17 +6602,16 @@
ASSERT_NE(device3_5, nullptr);
// Check camera characteristics for hidden camera id
- Return<void> ret = device3_5->getPhysicalCameraCharacteristics(physicalId,
- [&](auto status, const auto& chars) {
- verifyCameraCharacteristics(status, chars);
- verifyMonochromeCharacteristics(chars, deviceVersion);
-
- retcode = find_camera_metadata_ro_entry(
- (const camera_metadata_t *)chars.data(),
- ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
- bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2);
- ASSERT_EQ(hasZoomRatioRange, subCameraHasZoomRatioRange);
- });
+ Return<void> ret = device3_5->getPhysicalCameraCharacteristics(
+ physicalId, [&](auto status, const auto& chars) {
+ verifyCameraCharacteristics(status, chars);
+ verifyMonochromeCharacteristics(chars, deviceVersion);
+ retcode =
+ find_camera_metadata_ro_entry((const camera_metadata_t*)chars.data(),
+ ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
+ bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2);
+ ASSERT_EQ(hasZoomRatioRange, subCameraHasZoomRatioRange);
+ });
ASSERT_TRUE(ret.isOk());
// Check calling getCameraDeviceInterface_V3_x() on hidden camera id returns
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
index 107097e..b3b06b2 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc usb
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
index b45158a..7c5e69b 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
@@ -8,4 +8,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
index 955b28e..49bca8f 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
@@ -8,4 +8,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
index c065815..4bd1fb4 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
index 63dd11d..b444325 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/compatibility_matrices/compatibility_matrix.5.xml b/compatibility_matrices/compatibility_matrix.5.xml
index 1b051f5..e772b6f 100644
--- a/compatibility_matrices/compatibility_matrix.5.xml
+++ b/compatibility_matrices/compatibility_matrix.5.xml
@@ -371,6 +371,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.radio</name>
+ <version>1.4</version>
<version>1.5</version>
<interface>
<name>IRadio</name>
diff --git a/current.txt b/current.txt
index 656f5c7..edf96b2 100644
--- a/current.txt
+++ b/current.txt
@@ -1,6 +1,10 @@
# Do not change this file except to add new interfaces. Changing
# pre-existing interfaces will fail VTS and break framework-only OTAs
+# Test HALs
+
+717c17cd380bb48710dff601d1a03351d4ebc28028353d5d60489248f506523c android.hardware.tests.lazy@1.0::ILazy
+
# HALs released in Android O
f219c3b5b8c6cb1d659d4c7328f67246abfe1a8613f469826fd3b9ad090417a2 android.hardware.audio@2.0::IDevice
@@ -588,6 +592,7 @@
cd06a7911b9acd4a653bbf7133888878fbcb3f84be177c7a3f1becaae3d8618f android.hardware.camera.metadata@3.2::types
a05277065c28ebecd58118bd240fb8c55757361e8648c01f7c4dacdb7f2a95dc android.hardware.camera.metadata@3.3::types
9cb3df2bde2c6cd5fd96b7c41555420cacd7e276a556c684af91b7461c86460f android.hardware.gnss@1.0::IGnssCallback
+dd6cd9dba4fde99a1bc3cb1728d82309f509a6e6e1993e5042dfa5ffe4af5442 android.hardware.gnss@2.0::IGnssMeasurementCallback
af334f1fc85c62b343f84b74d0495eed6f495f7fecedb53463db10c202310058 android.hardware.gnss.measurement_corrections@1.0::types
33a6b20c43af00fdfb305df891bc5911c06d9a9130b912759649932e5a4a6e6d android.hardware.gnss.visibility_control@1.0::IGnssVisibilityControlCallback
bceee81ec1b59324abd05932b5620fda5a6589597c9cb3953ba7f3ea02cccd3e android.hardware.camera.provider@2.4::ICameraProvider
@@ -614,6 +619,7 @@
164826a380f4c1700183003f62d7532e367b67381c30ea44f946c0cf00008f85 android.hardware.audio@6.0::IStreamOut
997fdaad7a9d17ee7e01feb7031a753e2365e72ad30b11d950e9183fabdf3844 android.hardware.audio@6.0::IStreamOutCallback
e7ca0db9a1098210f327a9b152fa6afe6bf019c41e5264c64829d04d50c0a526 android.hardware.audio@6.0::IStreamOutEventCallback
+aa2211abd803e03d05ea11c18749db068f785fe026f8d99bce64bd764f63d194 android.hardware.audio@6.0::IStreamOutEventCallback # b/150175043
822369cf4dc16a6f6b9622bcf86cbdc0b692dc82193fc15e967767175cbfdd8f android.hardware.audio@6.0::types
bee662c62d997d8065e2bcb5c1e7a9578931f22ce28fd02c219fdb4d0630abf7 android.hardware.audio.common@6.0::types
525bec6b44f1103869c269a128d51b8dccd73af5340ba863c8886c68357c7faf android.hardware.audio.effect@6.0::IAcousticEchoCancelerEffect
@@ -655,9 +661,6 @@
7d2e77ad86766bbc213fa7377eab739f44cc0866e567e6d33c0e27e7f99e27a8 android.hardware.automotive.sv@1.0::ISurroundViewSession
d34769e55df919739bb46f25ae2e125e9c807733afa94daeca20feadd74de79c android.hardware.automotive.sv@1.0::ISurroundViewStream
affd9c591f48a69773fcf43dc4a716d292cd4bc5ba2be8649276af0aedea435d android.hardware.automotive.sv@1.0::types
-b3caf524c46a47d67e6453a34419e1881942d059e146cda740502670e9a752c3 android.hardware.automotive.vehicle@2.0::IVehicle
-7ce8728b27600e840cacf0a832f6942819fe535f9d3797ae052d5eef5065921c android.hardware.automotive.vehicle@2.0::IVehicleCallback
-6b2564fce1d364baf9ba15a5cb00a8f08f86a5be5387c0ede795328ca536a2c7 android.hardware.automotive.vehicle@2.0::types
140f8f62100ccf9cd282ae3685a0f4ef0a9f971d77dfbc7350ccb4e04cf295ec android.hardware.biometrics.fingerprint@2.2::IBiometricsFingerprint
82cad99f5feb2ea9bcd4579055edf4af8feb9fc602a6e4827ddd727d254d4991 android.hardware.biometrics.fingerprint@2.2::IBiometricsFingerprintClientCallback
ae6315fd42196478ac08441cb489d854118001bca5b9b9fd58af5110952be30e android.hardware.biometrics.fingerprint@2.2::types
diff --git a/gnss/2.0/IGnssMeasurementCallback.hal b/gnss/2.0/IGnssMeasurementCallback.hal
index e055f7a..76c7943 100644
--- a/gnss/2.0/IGnssMeasurementCallback.hal
+++ b/gnss/2.0/IGnssMeasurementCallback.hal
@@ -402,6 +402,8 @@
* Value "C" represents GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
* GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C.
*
+ * value "D" represents BDS B1C D.
+ *
* Value "I" represents GPS L5 I, GLONASS G3 I, GALILEO E5a I, GALILEO E5b I, GALILEO E5a+b I,
* SBAS L5 I, QZSS L5 I, BDS B1 I, BDS B2 I, BDS B3 I.
*
@@ -411,7 +413,7 @@
*
* Value "N" represents GPS L1 codeless, GPS L2 codeless.
*
- * Value "P" represents GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P.
+ * Value "P" represents GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C P.
*
* Value "Q" represents GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q,
* SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q.
@@ -423,7 +425,7 @@
* Value "X" represents GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G3 (I+Q),
* GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b(I+Q),
* GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q),
- * LEX(6) (S+L), BDS B1 (I+Q), BDS B2 (I+Q), BDS B3 (I+Q), IRNSS L5 (B+C).
+ * LEX(6) (S+L), BDS B1 (I+Q), BDS B1C (D+P), BDS B2 (I+Q), BDS B3 (I+Q), IRNSS L5 (B+C).
*
* Value "Y" represents GPS L1Y, GPS L2Y.
*
diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
index 7a00ed2..27b633a 100644
--- a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
+++ b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
@@ -204,7 +204,7 @@
void Test_setActiveConfigWithConstraints(
const IComposerClient::VsyncPeriodChangeConstraints& constraints, bool refreshMiss);
- void sendRefreshFrame(const VsyncPeriodChangeTimeline&);
+ void sendRefreshFrame(const VsyncPeriodChangeTimeline*);
void waitForVsyncPeriodChange(Display display, const VsyncPeriodChangeTimeline& timeline,
int64_t desiredTimeNanos, int64_t oldPeriodNanos,
@@ -294,7 +294,7 @@
display, config, constraints, &timeline));
if (timeline.refreshRequired) {
- sendRefreshFrame(timeline);
+ sendRefreshFrame(&timeline);
}
waitForVsyncPeriodChange(display, timeline, constraints.desiredTimeNanos, 0,
expectedVsyncPeriodNanos);
@@ -350,7 +350,7 @@
}
}
-TEST_P(GraphicsComposerHidlTest, setActiveConfigWithConstraints_SeamlessNotAllowed) {
+TEST_P(GraphicsComposerHidlCommandTest, setActiveConfigWithConstraints_SeamlessNotAllowed) {
VsyncPeriodChangeTimeline timeline;
IComposerClient::VsyncPeriodChangeConstraints constraints;
@@ -365,6 +365,7 @@
display, config2, IComposerClient::IComposerClient::Attribute::CONFIG_GROUP);
if (configGroup1 != configGroup2) {
mComposerClient->setActiveConfig(display, config1);
+ sendRefreshFrame(nullptr);
EXPECT_EQ(Error::SEAMLESS_NOT_ALLOWED,
mComposerClient->setActiveConfigWithConstraints(display, config2,
constraints, &timeline));
@@ -377,11 +378,13 @@
return std::chrono::time_point<std::chrono::steady_clock>(std::chrono::nanoseconds(time));
}
-void GraphicsComposerHidlCommandTest::sendRefreshFrame(const VsyncPeriodChangeTimeline& timeline) {
- // Refresh time should be before newVsyncAppliedTimeNanos
- EXPECT_LT(timeline.refreshTimeNanos, timeline.newVsyncAppliedTimeNanos);
+void GraphicsComposerHidlCommandTest::sendRefreshFrame(const VsyncPeriodChangeTimeline* timeline) {
+ if (timeline != nullptr) {
+ // Refresh time should be before newVsyncAppliedTimeNanos
+ EXPECT_LT(timeline->refreshTimeNanos, timeline->newVsyncAppliedTimeNanos);
- std::this_thread::sleep_until(toTimePoint(timeline.refreshTimeNanos));
+ std::this_thread::sleep_until(toTimePoint(timeline->refreshTimeNanos));
+ }
mWriter->selectDisplay(mPrimaryDisplay);
mComposerClient->setPowerMode(mPrimaryDisplay, V2_1::IComposerClient::PowerMode::ON);
@@ -453,6 +456,7 @@
for (Display display : mComposerCallback->getDisplays()) {
forEachTwoConfigs(display, [&](Config config1, Config config2) {
mComposerClient->setActiveConfig(display, config1);
+ sendRefreshFrame(nullptr);
int32_t vsyncPeriod1 = mComposerClient->getDisplayAttribute_2_4(
display, config1, IComposerClient::IComposerClient::Attribute::VSYNC_PERIOD);
@@ -478,7 +482,7 @@
// callback
std::this_thread::sleep_until(toTimePoint(timeline.refreshTimeNanos) + 100ms);
}
- sendRefreshFrame(timeline);
+ sendRefreshFrame(&timeline);
}
waitForVsyncPeriodChange(display, timeline, constraints.desiredTimeNanos, vsyncPeriod1,
vsyncPeriod2);
@@ -493,7 +497,7 @@
if (newTimelime.has_value()) {
if (timeline.refreshRequired) {
- sendRefreshFrame(newTimelime.value());
+ sendRefreshFrame(&newTimelime.value());
}
waitForVsyncPeriodChange(display, newTimelime.value(), constraints.desiredTimeNanos,
vsyncPeriod1, vsyncPeriod2);
diff --git a/identity/aidl/android/hardware/identity/CipherSuite.aidl b/identity/aidl/android/hardware/identity/CipherSuite.aidl
index 20b02a8..f38134b 100644
--- a/identity/aidl/android/hardware/identity/CipherSuite.aidl
+++ b/identity/aidl/android/hardware/identity/CipherSuite.aidl
@@ -24,13 +24,15 @@
enum CipherSuite {
/**
* Specifies that the cipher suite that will be used to secure communications between the reader
- * is:
+ * and the prover is using the following primitives
*
- * - ECDHE with HKDF-SHA-256 for key agreement.
- * - AES-256 with GCM block mode for authenticated encryption (nonces are incremented by
- * one for every message).
- * - ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
- * man-in-the-middle attacks), signing keys are not ephemeral.
+ * - ECKA-DH (Elliptic Curve Key Agreement Algorithm - Diffie-Hellman, see BSI TR-03111)
+ * - HKDF-SHA-256 (see RFC 5869)
+ * - AES-256-GCM (see NIST SP 800-38D)
+ * - HMAC-SHA-256 (see RFC 2104)
+ *
+ * The exact way these primitives are combined to derive the session key is specified in
+ * section 9.2.1.4 of ISO/IEC 18013-5 (see description of cipher suite '1').
*
* At present this is the only supported cipher suite and it is mandatory for all
* implementations to support it.
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index cc14271..7d14f03 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -48,6 +48,8 @@
* with the reader. The reason for generating the key pair in the secure environment is so that
* the secure environment knows what public key to expect to find in the session transcript.
*
+ * The generated key must be an EC key using the P-256 curve.
+ *
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* will be returned.
*
@@ -61,7 +63,8 @@
* This method may only be called once per instance. If called more than once, STATUS_FAILED
* will be returned.
*
- * @param publicKey contains the reader's ephemeral public key, in uncompressed form.
+ * @param publicKey contains the reader's ephemeral public key, in uncompressed
+ * form (e.g. 0x04 || X || Y).
*/
void setReaderEphemeralPublicKey(in byte[] publicKey);
@@ -82,9 +85,9 @@
* This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
* createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
* multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
- * finishRetrieval().This whole process is called a "credential presentation".
+ * finishRetrieval().
*
- * It is permissible to perform multiple credential presentations using the same instance (e.g.
+ * It is permissible to perform data retrievals multiple times using the same instance (e.g.
* startRetrieval(), then multiple calls of startRetrieveEntryValue(), retrieveEntryValue(),
* then finally finishRetrieval()) but if this is done, the sessionTranscript parameter
* must be identical for each startRetrieval() invocation. If this is not the case, this call
@@ -148,6 +151,8 @@
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
* ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
*
+ * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader
+ *
* The public key corresponding to the key used to made signature, can be found in the
* 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described
* in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element
@@ -220,13 +225,11 @@
*
* It is permissible to keep retrieving values if an access control check fails.
*
- * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+ * @param nameSpace is the namespace of the element, e.g. "org.iso.18013.5.1"
*
- * @param name is the name of the element.
+ * @param name is the name of the element, e.g. "driving_privileges".
*
- * @param entrySize is the size of the entry value, if it's a text string or a byte string.
- * It must be zero if the entry value is an integer or boolean. If this requirement
- * is not met the call fails with STATUS_INVALID_DATA.
+ * @param entrySize is the size of the entry value encoded in CBOR.
*
* @param accessControlProfileIds specifies the set of access control profiles that can
* authorize access to the provisioned element. If an identifier of a profile
@@ -260,14 +263,12 @@
* @param out mac is empty if signingKeyBlob or the sessionTranscript passed to
* startRetrieval() is empty. Otherwise it is a COSE_Mac0 with empty payload
* and the detached content is set to DeviceAuthentication as defined below.
- * The key used for the MAC operation is EMacKey and is derived as follows:
- *
- * KDF(ECDH(SDeviceKey.Priv, EReaderKey.Pub))
- *
- * where SDeviceKey.Priv is the key identified by signingKeyBlob. The KDF
- * and ECDH functions shall be the same as the ciphersuite selected and
- * passed to IIdentityStore.getCredential(). The EMacKey shall be derived
- * using a salt of 0x00.
+ * This code is produced by using the key agreement and key derivation function
+ * from the ciphersuite with the authentication private key and the reader
+ * ephemeral public key to compute a shared message authentication code (MAC)
+ * key, then using the MAC function from the ciphersuite to compute a MAC of
+ * the authenticated data. See section 9.2.3.5 of ISO/IEC 18013-5 for details
+ * of this operation.
*
* DeviceAuthentication = [
* "DeviceAuthentication",
@@ -308,6 +309,34 @@
/**
* Generate a key pair to be used for signing session data and retrieved data items.
*
+ * The generated key must be an EC key using the P-256 curve.
+ *
+ * This method shall return just a single X.509 certificate which is signed by CredentialKey.
+ * When combined with the certificate chain returned at provisioning time by
+ * getAttestationCertificate() on IWritableIdentityCredential (for the credential key), this
+ * forms a chain all the way from the root of trust to the generated key.
+ *
+ * The public part of a signing key is usually included in issuer-signed data and is
+ * used for anti-cloning purposes or as a mechanism for the issuer to attest to data
+ * generated on the device.
+ *
+ * The following non-optional fields for the X.509 certificate shall be set as follows:
+ *
+ * - version: INTEGER 2 (means v3 certificate).
+ *
+ * - serialNumber: INTEGER 1 (fixed value: same on all certs).
+ *
+ * - signature: must be set to ECDSA.
+ *
+ * - subject: CN shall be set to "Android Identity Credential Authentication Key".
+ *
+ * - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the
+ * values returned in HardwareInformation.
+ *
+ * - validity: should be from current time and one year in the future.
+ *
+ * - subjectPublicKeyInfo: must contain attested public key.
+ *
* @param out signingKeyBlob contains an encrypted copy of the newly-generated private
* signing key.
*
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
index 23cb1b7..bd664e8 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -55,8 +55,8 @@
*
* - For each namespase, a set of name/value pairs, each with an associated set of access control
* profile IDs. Names are UTF-8 strings of up to 256 bytes in length (most should be much
- * shorter). Values stored must be encoed as valid CBOR (https://tools.ietf.org/html/rfc7049) and
- * the encoeded size is is limited to at most 512 KiB.
+ * shorter). Values stored must be encoded as CBOR (https://tools.ietf.org/html/rfc7049) and
+ * the encoded size is is limited to at most 512 KiB.
*
* - A set of access control profiles, each with a profile ID and a specification of the
* conditions which satisfy the profile's requirements.
@@ -108,12 +108,13 @@
@VintfStability
interface IIdentityCredentialStore {
/**
- * Success.
+ * Success. This is never returned but included for completeness and for use by code
+ * using these statuses for internal use.
*/
const int STATUS_OK = 0;
/**
- * The operation failed. This is used as a generic catch-all for errors that don't belong
+ * The operation failed. This is used as a generic catch-all for errors that don't belong
* in other categories, including memory/resource allocation failures and I/O errors.
*/
const int STATUS_FAILED = 1;
@@ -124,7 +125,7 @@
const int STATUS_CIPHER_SUITE_NOT_SUPPORTED = 2;
/**
- * The passed data was invalid. This is a generic catch all for errors that don't belong
+ * The passed data was invalid. This is a generic catch all for errors that don't belong
* in other categories related to parameter validation.
*/
const int STATUS_INVALID_DATA = 3;
@@ -186,16 +187,19 @@
HardwareInformation getHardwareInformation();
/**
- * createCredential creates a new Credential. When a Credential is created, two cryptographic
+ * createCredential creates a new credential. When a credential is created, two cryptographic
* keys are created: StorageKey, an AES key used to secure the externalized Credential
- * contents, and CredentialKeyPair, an EC key pair used to authenticate the store to the IA. In
- * addition, all of the Credential data content is imported and a certificate for the
- * CredentialKeyPair and a signature produced with the CredentialKeyPair are created. These
+ * contents, and CredentialKey, an EC key pair used to authenticate the store to the IA.
+ *
+ * CredentialKey must be an EC key using the P-256 curve.
+ *
+ * In addition, all of the Credential data content is imported and a certificate for the
+ * CredentialKey and a signature produced with the CredentialKey are created. These
* latter values may be checked by an issuing authority to verify that the data was imported
* into secure hardware and that it was imported unmodified.
*
* @param docType is an optional name (may be an empty string) that identifies the type of
- * credential being created, e.g. "org.iso.18013-5.2019.mdl" (the doc type of the ISO
+ * credential being created, e.g. "org.iso.18013.5.1.mDL" (the doc type of the ISO
* driving license standard).
*
* @param testCredential indicates if this is a test store. Test credentials must use an
@@ -213,15 +217,8 @@
* Credential.
*
* The cipher suite used to communicate with the remote verifier must also be specified. Currently
- * only a single cipher-suite is supported and the details of this are as follow:
- *
- * - ECDHE with HKDF-SHA-256 for key agreement.
- * - AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
- * for every message).
- * - ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
- * man-in-the-middle attacks), signing keys are not ephemeral.
- *
- * Support for other cipher suites may be added in a future version of this HAL.
+ * only a single cipher-suite is supported. Support for other cipher suites may be added in a
+ * future version of this HAL.
*
* This method fails with STATUS_INVALID_DATA if the passed in credentialData cannot be
* decoded or decrypted.
@@ -233,7 +230,7 @@
* return argument of the same name in finishAddingEntries(), in
* IWritableIdentityCredential.
*
- * @return an IIdentityCredential HIDL interface that provides operations on the Credential.
+ * @return an IIdentityCredential interface that provides operations on the Credential.
*/
IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);
}
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index 483b0c7..07486e6 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -60,12 +60,50 @@
* attestationApplicationId.
*
* - The teeEnforced field in the attestation extension must include
- * Tag::IDENTITY_CREDENTIAL_KEY. This tag indicates that the key is an Identity
- * Credential key (which can only sign/MAC very specific messages) and not an Android
- * Keystore key (which can be used to sign/MAC anything).
+ *
+ * - Tag::IDENTITY_CREDENTIAL_KEY which indicates that the key is an Identity
+ * Credential key (which can only sign/MAC very specific messages) and not an Android
+ * Keystore key (which can be used to sign/MAC anything).
+ *
+ * - Tag::PURPOSE must be set to SIGN
+ *
+ * - Tag::KEY_SIZE must be set to the appropriate key size, in bits (e.g. 256)
+ *
+ * - Tag::ALGORITHM must be set to EC
+ *
+ * - Tag::NO_AUTH_REQUIRED must be set
+ *
+ * - Tag::DIGEST must be set to SHA_2_256
+ *
+ * - Tag::EC_CURVE must be set to P_256
*
* Additional authorizations may be needed in the softwareEnforced and teeEnforced
- * fields - the above is not an exhaustive list.
+ * fields - the above is not an exhaustive list. Specifically, authorizations containing
+ * information about the root of trust, OS version, verified boot state, and so on should
+ * be included.
+ *
+ * Since the chain is required to be generated using Keymaster Attestation, the returned
+ * certificate chain has the following properties:
+ *
+ * - The certificate chain is of at least length three.
+ *
+ * - The root of trust is the same as for Keymaster Attestation. This is usually
+ * a certificate owned by Google but depending on the specific Android device it may
+ * be another certificate.
+ *
+ * As with any user of attestation, the Issuing Authority (as a relying party) wishing
+ * to issue a credential to a device using these APIs, must carefully examine the
+ * returned certificate chain for all of the above (and more). In particular, the Issuing
+ * Authority should check the root of trust, verified boot state, patch level,
+ * application id, etc.
+ *
+ * This all depends on the needs of the Issuing Authority and the kind of credential but
+ * in general an Issuing Authority should never issue a credential to a device without
+ * verified boot enabled, to an unrecognized application, or if it appears the device
+ * hasn't been updated for a long time.
+ *
+ * See https://github.com/google/android-key-attestation for an example of how to
+ * examine attestations generated from Android devices.
*
* @param attestationApplicationId is the DER encoded value to be stored
* in Tag::ATTESTATION_APPLICATION_ID. This schema is described in
@@ -102,10 +140,11 @@
* with STATUS_INVALID_DATA.
*
* @param id a numeric identifier that must be unique within the context of a Credential and may
- * be used to reference the profile. If this is not satisfied the call fails with
+ * be used to reference the profile. This id must be non-negative and less than 32 (allowing
+ * for a total of 32 profiles). If this is not satisfied the call fails with
* STATUS_INVALID_DATA.
*
- * @param readerCertificate if non-empty, specifies a X.509 certificate (or chain of
+ * @param readerCertificate if non-empty, specifies a single X.509 certificate (not a chain of
* certificates) that must be used to authenticate requests (see the readerSignature
* parameter in IIdentityCredential.startRetrieval).
*
@@ -142,7 +181,7 @@
* @param accessControlProfileIds specifies the set of access control profiles that can
* authorize access to the provisioned element.
*
- * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+ * @param nameSpace is the namespace of the element, e.g. "org.iso.18013.5.1"
*
* @param name is the name of the element.
*
diff --git a/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl
index 01d312d..13f0c6d 100644
--- a/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl
+++ b/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -29,7 +29,7 @@
/**
* readerCertificate, if non-empty, specifies a single X.509 certificate (not a chain
* of certificates) that must be used to authenticate requests. For details about how
- * this is done, see the readerSignature paremter of IIdentityCredential.startRetrieval.
+ * this is done, see the readerSignature parameter of IIdentityCredential.startRetrieval.
*/
Certificate readerCertificate;
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
index 89f7f35..52cd496 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -44,6 +44,8 @@
return false;
}
storageKey_ = random.value();
+ startPersonalizationCalled_ = false;
+ firstEntry_ = true;
return true;
}
@@ -105,6 +107,12 @@
ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+ if (startPersonalizationCalled_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
+ }
+
+ startPersonalizationCalled_ = true;
numAccessControlProfileRemaining_ = accessControlProfileCount;
remainingEntryCounts_ = entryCounts;
entryNameSpace_ = "";
@@ -128,6 +136,19 @@
"numAccessControlProfileRemaining_ is 0 and expected non-zero"));
}
+ if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be unique"));
+ }
+ accessControlProfileIds_.insert(id);
+
+ if (id < 0 || id >= 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be non-negative and less than 32"));
+ }
+
// Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
// be zero.
if (!userAuthenticationRequired && timeoutMillis != 0) {
@@ -184,12 +205,20 @@
}
// Handle initial beginEntry() call.
- if (entryNameSpace_ == "") {
+ if (firstEntry_) {
+ firstEntry_ = false;
entryNameSpace_ = nameSpace;
+ allNameSpaces_.insert(nameSpace);
}
// If the namespace changed...
if (nameSpace != entryNameSpace_) {
+ if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Name space cannot be added in interleaving fashion"));
+ }
+
// Then check that all entries in the previous namespace have been added..
if (remainingEntryCounts_[0] != 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -197,6 +226,8 @@
"New namespace but a non-zero number of entries remain to be added"));
}
remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+ remainingEntryCounts_[0] -= 1;
+ allNameSpaces_.insert(nameSpace);
if (signedDataCurrentNamespace_.size() > 0) {
signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
@@ -330,6 +361,18 @@
ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
vector<int8_t>* outCredentialData, vector<int8_t>* outProofOfProvisioningSignature) {
+ if (numAccessControlProfileRemaining_ != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "numAccessControlProfileRemaining_ is not 0 and expected zero"));
+ }
+
+ if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "More entry spaces remain than startPersonalization configured"));
+ }
+
if (signedDataCurrentNamespace_.size() > 0) {
signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
}
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h
index b182862..cb91f7b 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/WritableIdentityCredential.h
@@ -21,9 +21,11 @@
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <cppbor.h>
+#include <set>
namespace aidl::android::hardware::identity {
+using ::std::set;
using ::std::string;
using ::std::vector;
@@ -66,6 +68,8 @@
// This is set in initialize().
vector<uint8_t> storageKey_;
+ bool startPersonalizationCalled_;
+ bool firstEntry_;
// These are set in getAttestationCertificate().
vector<uint8_t> credentialPrivKey_;
@@ -79,6 +83,9 @@
cppbor::Map signedDataNamespaces_;
cppbor::Array signedDataCurrentNamespace_;
+ // This field is initialized in addAccessControlProfile
+ set<int32_t> accessControlProfileIds_;
+
// These fields are initialized during beginAddEntry()
size_t entryRemainingBytes_;
vector<uint8_t> entryAdditionalData_;
@@ -86,6 +93,7 @@
string entryName_;
vector<int32_t> entryAccessControlProfileIds_;
vector<uint8_t> entryBytes_;
+ set<string> allNameSpaces_;
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index ef8beb4..e4780bf 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -4,7 +4,11 @@
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
- srcs: ["VtsHalIdentityTargetTest.cpp"],
+ srcs: [
+ "VtsHalIdentityEndToEndTest.cpp",
+ "VtsIWritableIdentityCredentialTests.cpp",
+ "VtsIdentityTestUtils.cpp",
+ ],
shared_libs: [
"libbinder",
"libcrypto",
diff --git a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
similarity index 69%
rename from identity/aidl/vts/VtsHalIdentityTargetTest.cpp
rename to identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
index ea37fdc..8a4e8a7 100644
--- a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -28,8 +28,11 @@
#include <future>
#include <map>
+#include "VtsIdentityTestUtils.h"
+
namespace android::hardware::identity {
+using std::endl;
using std::map;
using std::optional;
using std::string;
@@ -41,51 +44,6 @@
using ::android::hardware::keymaster::HardwareAuthToken;
-// ---------------------------------------------------------------------------
-// Test Data.
-// ---------------------------------------------------------------------------
-
-struct TestEntryData {
- TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
- : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
-
- TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
- }
- TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
- vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Bstr(value).encode();
- }
- TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Bool(value).encode();
- }
- TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- if (value >= 0) {
- valueCbor = cppbor::Uint(value).encode();
- } else {
- valueCbor = cppbor::Nint(-value).encode();
- }
- }
-
- string nameSpace;
- string name;
- vector<uint8_t> valueCbor;
- vector<int32_t> profileIds;
-};
-
-struct TestProfile {
- uint16_t id;
- vector<uint8_t> readerCertificate;
- bool userAuthenticationRequired;
- uint64_t timeoutMillis;
-};
-
-// ----------------------------------------------------------------
-
class IdentityAidl : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
@@ -108,39 +66,26 @@
TEST_P(IdentityAidl, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
- optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
- ASSERT_TRUE(readerKeyPKCS8);
- optional<vector<uint8_t>> readerPublicKey =
- support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
- optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
- string serialDecimal = "1234";
- string issuer = "Android Open Source Project";
- string subject = "Android IdentityCredential VTS Test";
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
- optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
- readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
+ vector<uint8_t> readerKey;
+ optional<vector<uint8_t>> readerCertificate =
+ test_utils::GenerateReaderCertificate("1234", readerKey);
ASSERT_TRUE(readerCertificate);
// Make the portrait image really big (just shy of 256 KiB) to ensure that
// the chunking code gets exercised.
vector<uint8_t> portraitImage;
- portraitImage.resize(256 * 1024 - 10);
- for (size_t n = 0; n < portraitImage.size(); n++) {
- portraitImage[n] = (uint8_t)n;
- }
+ test_utils::SetImageData(portraitImage);
// Access control profiles:
- const vector<TestProfile> testProfiles = {// Profile 0 (reader authentication)
- {0, readerCertificate.value(), false, 0},
- // Profile 1 (no authentication)
- {1, {}, false, 0}};
+ const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+ {0, readerCertificate.value(), false, 0},
+ // Profile 1 (no authentication)
+ {1, {}, false, 0}};
HardwareAuthToken authToken;
// Here's the actual test data:
- const vector<TestEntryData> testEntries = {
+ const vector<test_utils::TestEntryData> testEntries = {
{"PersonalData", "Last name", string("Turing"), vector<int32_t>{0, 1}},
{"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
{"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}},
@@ -155,67 +100,33 @@
string cborPretty;
sp<IWritableIdentityCredential> writableCredential;
- string docType = "org.iso.18013-5.2019.mdl";
- bool testCredential = true;
- ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &writableCredential)
- .isOk());
- ASSERT_NE(writableCredential, nullptr);
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
string challenge = "attestationChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+ ASSERT_EQ(binder::Status::EX_NONE, attData.result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_OK, attData.result.serviceSpecificErrorCode());
+
// TODO: set it to something random and check it's in the cert chain
- vector<uint8_t> attestationApplicationId = {};
- vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
- vector<Certificate> attestationCertificates;
- ASSERT_TRUE(writableCredential
- ->getAttestationCertificate(attestationApplicationId, attestationChallenge,
- &attestationCertificates)
- .isOk());
- ASSERT_GE(attestationCertificates.size(), 2);
+ ASSERT_GE(attData.attestationCertificate.size(), 2);
ASSERT_TRUE(
writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts)
.isOk());
- vector<SecureAccessControlProfile> returnedSecureProfiles;
- for (const auto& testProfile : testProfiles) {
- SecureAccessControlProfile profile;
- Certificate cert;
- cert.encodedCertificate = testProfile.readerCertificate;
- ASSERT_TRUE(writableCredential
- ->addAccessControlProfile(testProfile.id, cert,
- testProfile.userAuthenticationRequired,
- testProfile.timeoutMillis,
- 0, // secureUserId
- &profile)
- .isOk());
- ASSERT_EQ(testProfile.id, profile.id);
- ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
- ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
- ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
- ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
- returnedSecureProfiles.push_back(profile);
- }
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
// Uses TestEntryData* pointer as key and values are the encrypted blobs. This
// is a little hacky but it works well enough.
- map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
for (const auto& entry : testEntries) {
- vector<vector<uint8_t>> chunks =
- support::chunkVector(entry.valueCbor, hwInfo.dataChunkSize);
-
- ASSERT_TRUE(writableCredential
- ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
- entry.valueCbor.size())
- .isOk());
-
- vector<vector<uint8_t>> encryptedChunks;
- for (const auto& chunk : chunks) {
- vector<uint8_t> encryptedChunk;
- ASSERT_TRUE(writableCredential->addEntryValue(chunk, &encryptedChunk).isOk());
- encryptedChunks.push_back(encryptedChunk);
- }
- encryptedBlobs[&entry] = encryptedChunks;
+ ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
}
vector<uint8_t> credentialData;
@@ -276,8 +187,8 @@
"]",
cborPretty);
- optional<vector<uint8_t>> credentialPubKey =
- support::certificateChainGetTopMostKey(attestationCertificates[0].encodedCertificate);
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
ASSERT_TRUE(credentialPubKey);
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
@@ -347,8 +258,8 @@
.add(cppbor::Semantic(24, itemsRequestBytes))
.encode();
optional<vector<uint8_t>> readerSignature =
- support::coseSignEcDsa(readerKey.value(), {}, // content
- dataToSign, // detached content
+ support::coseSignEcDsa(readerKey, {}, // content
+ dataToSign, // detached content
readerCertificate.value());
ASSERT_TRUE(readerSignature);
@@ -358,7 +269,7 @@
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
ASSERT_TRUE(credential
- ->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+ ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes,
signingKeyBlob, sessionTranscriptBytes,
readerSignature.value(), testEntriesEntryCounts)
.isOk());
@@ -405,6 +316,8 @@
cppbor::Array deviceAuthentication;
deviceAuthentication.add("DeviceAuthentication");
deviceAuthentication.add(sessionTranscript.clone());
+
+ string docType = "org.iso.18013-5.2019.mdl";
deviceAuthentication.add(docType);
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
new file mode 100644
index 0000000..b68fbb5
--- /dev/null
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -0,0 +1,683 @@
+/*
+ * 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 "VtsIWritableIdentityCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+
+#include "VtsIdentityTestUtils.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+class IdentityCredentialTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ }
+
+ sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ vector<uint8_t> attestationChallenge;
+ vector<Certificate> attestationCertificate;
+ vector<uint8_t> attestationApplicationId = {};
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1";
+ vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+ vector<Certificate> attestationCertificate;
+ vector<uint8_t> attestationApplicationId = {};
+
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(test_utils::ValidateAttestationCertificate(attData.attestationCertificate));
+
+ string challenge2 = "NotSoRandomChallenge2";
+ test_utils::AttestationData attData2(writableCredential, challenge2, {});
+ EXPECT_FALSE(attData2.result.isOk()) << attData2.result.exceptionCode() << "; "
+ << attData2.result.exceptionMessage() << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, attData2.result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, attData2.result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // First call should go through
+ const vector<int32_t> entryCounts = {2, 4};
+ result = writableCredential->startPersonalization(5, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ // Call personalization again to check if repeat call is allowed.
+ result = writableCredential->startPersonalization(7, entryCounts);
+
+ // Second call to startPersonalization should have failed.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify minimal number of profile count and entry count
+ const vector<int32_t> entryCounts = {1, 1};
+ writableCredential->startPersonalization(1, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {0};
+ writableCredential->startPersonalization(0, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify minimal number of profile count and entry count
+ const vector<int32_t> entryCounts = {1};
+ writableCredential->startPersonalization(1, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify set a large number of profile count and entry count is ok
+ const vector<int32_t> entryCounts = {3000};
+ writableCredential->startPersonalization(3500, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Enter mismatched entry and profile numbers
+ const vector<int32_t> entryCounts = {5, 6};
+ writableCredential->startPersonalization(5, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate = test_utils::GenerateReaderCertificate("12345");
+ ASSERT_TRUE(readerCertificate);
+
+ const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+ {1, readerCertificate.value(), false, 0},
+ {2, readerCertificate.value(), true, 1},
+ // Profile 4 (no authentication)
+ {4, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ // finishAddingEntries should fail because the number of addAccessControlProfile mismatched with
+ // startPersonalization, and begintest_utils::AddEntry was not called.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {3, 6};
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ const vector<test_utils::TestProfile> testProfiles = {// first profile should go though
+ {1, {}, true, 2},
+ // same id, different
+ // authentication requirement
+ {1, {}, true, 1},
+ // same id, different certificate
+ {1, {}, false, 0}};
+
+ bool expectOk = true;
+ for (const auto& testProfile : testProfiles) {
+ SecureAccessControlProfile profile;
+ Certificate cert;
+ cert.encodedCertificate = testProfile.readerCertificate;
+ result = writableCredential->addAccessControlProfile(
+ testProfile.id, cert, testProfile.userAuthenticationRequired,
+ testProfile.timeoutMillis, 0, &profile);
+
+ if (expectOk) {
+ expectOk = false;
+ // for profile should be allowed though as there are no duplications
+ // yet.
+ ASSERT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage()
+ << "test profile id = " << testProfile.id << endl;
+
+ ASSERT_EQ(testProfile.id, profile.id);
+ ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+ ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+ ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+ ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+ } else {
+ // should not allow duplicate id profiles.
+ ASSERT_FALSE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage()
+ << ". Test profile id = " << testProfile.id
+ << ", timeout=" << testProfile.timeoutMillis << endl;
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA,
+ result.serviceSpecificErrorCode());
+ }
+ }
+}
+
+TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ EXPECT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ const vector<int32_t> entryCounts = {1u};
+ writableCredential->startPersonalization(1, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ const vector<test_utils::TestProfile> testProfiles = {{1, readerCertificate1.value(), true, 1}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty =
+ support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'Name Space' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
+ ASSERT_TRUE(credentialPubKey);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ EXPECT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 = test_utils::GenerateReaderCertificate("1256");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<test_utils::TestProfile> testProfiles = {
+ {1, readerCertificate1.value(), true, 1},
+ {2, readerCertificate2.value(), true, 2},
+ };
+ const vector<int32_t> entryCounts = {1u, 3u, 1u, 1u, 2u};
+ writableCredential->startPersonalization(testProfiles.size(), entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ vector<uint8_t> portraitImage1;
+ test_utils::SetImageData(portraitImage1);
+
+ vector<uint8_t> portraitImage2;
+ test_utils::SetImageData(portraitImage2);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ {"Name Space 1", "Last name", string("Turing"), vector<int32_t>{1, 2}},
+ {"Name Space2", "Home address", string("Maida Vale, London, England"),
+ vector<int32_t>{1}},
+ {"Name Space2", "Work address", string("Maida Vale2, London, England"),
+ vector<int32_t>{2}},
+ {"Name Space2", "Trailer address", string("Maida, London, England"),
+ vector<int32_t>{1}},
+ {"Image", "Portrait image", portraitImage1, vector<int32_t>{1}},
+ {"Image2", "Work image", portraitImage2, vector<int32_t>{1, 2}},
+ {"Name Space3", "xyzw", string("random stuff"), vector<int32_t>{1, 2}},
+ {"Name Space3", "Something", string("Some string"), vector<int32_t>{2}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(),
+ 32, //
+ {"readerCertificate"});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 1,\n"
+ " },\n"
+ " {\n"
+ " 'id' : 2,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 2,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'Name Space 1' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Name Space2' : [\n"
+ " {\n"
+ " 'name' : 'Home address',\n"
+ " 'value' : 'Maida Vale, London, England',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Work address',\n"
+ " 'value' : 'Maida Vale2, London, England',\n"
+ " 'accessControlProfiles' : [2, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Trailer address',\n"
+ " 'value' : 'Maida, London, England',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Image' : [\n"
+ " {\n"
+ " 'name' : 'Portrait image',\n"
+ " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Image2' : [\n"
+ " {\n"
+ " 'name' : 'Work image',\n"
+ " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Name Space3' : [\n"
+ " {\n"
+ " 'name' : 'xyzw',\n"
+ " 'value' : 'random stuff',\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Something',\n"
+ " 'value' : 'Some string',\n"
+ " 'accessControlProfiles' : [2, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
+ ASSERT_TRUE(credentialPubKey);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ const vector<int32_t> entryCounts = {2u, 2u};
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 =
+ test_utils::GenerateReaderCertificate("123456987987987987987987");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+ {1, readerCertificate2.value(), true, 1},
+ {2, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ // test empty name space
+ {"", "t name", string("Turing"), vector<int32_t>{2}},
+ {"", "Birth", string("19120623"), vector<int32_t>{2}},
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ // Enter mismatched entry and profile numbers.
+ // Technically the 2nd name space of "Name Space" occurs intermittently, 2
+ // before "Image" and 2 after image, which is not correct. All of same name
+ // space should occur together. Let's see if this fails.
+ const vector<int32_t> entryCounts = {2u, 1u, 2u};
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 =
+ test_utils::GenerateReaderCertificate("123456987987987987987987");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+ {1, readerCertificate2.value(), true, 1},
+ {2, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ // test empty name space
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+ const test_utils::TestEntryData testEntry2 = {"Image", "Portrait image", string("asdfs"),
+ vector<int32_t>{0, 1}};
+
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, testEntry2, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+
+ // We expect this to fail because the namespace is out of order, all "Name Space"
+ // should have been called together
+ const vector<test_utils::TestEntryData> testEntries3 = {
+ {"Name Space", "First name", string("Alan"), vector<int32_t>{0, 1}},
+ {"Name Space", "Home address", string("Maida Vale, London, England"),
+ vector<int32_t>{0}},
+ };
+
+ for (const auto& entry : testEntries3) {
+ EXPECT_FALSE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, false));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ // should fail because test_utils::AddEntry should have failed earlier.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) {
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {1};
+ Status result = writableCredential->startPersonalization(1, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ SecureAccessControlProfile profile;
+
+ // This should fail because the id is >= 32
+ result = writableCredential->addAccessControlProfile(32, // id
+ {}, // readerCertificate
+ false, // userAuthenticationRequired
+ 0, // timeoutMillis
+ 42, // secureUserId
+ &profile);
+ ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+
+ // This should fail because the id is < 0
+ result = writableCredential->addAccessControlProfile(-1, // id
+ {}, // readerCertificate
+ false, // userAuthenticationRequired
+ 0, // timeoutMillis
+ 42, // secureUserId
+ &profile);
+ ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Identity, IdentityCredentialTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp
new file mode 100644
index 0000000..3aeebc6
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#include "VtsIdentityTestUtils.h"
+
+#include <aidl/Gtest.h>
+#include <map>
+
+namespace android::hardware::identity::test_utils {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+ sp<IIdentityCredentialStore>& credentialStore) {
+ if (credentialStore == nullptr) {
+ return false;
+ }
+
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ Status result = credentialStore->createCredential(docType, testCredential, &writableCredential);
+
+ if (result.isOk() && writableCredential != nullptr) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal) {
+ vector<uint8_t> privKey;
+ return GenerateReaderCertificate(serialDecimal, privKey);
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+ vector<uint8_t>& readerPrivateKey) {
+ optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+ if (!readerKeyPKCS8) {
+ return {};
+ }
+
+ optional<vector<uint8_t>> readerPublicKey =
+ support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+ optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+ if (!readerPublicKey || !readerKey) {
+ return {};
+ }
+
+ readerPrivateKey = readerKey.value();
+
+ string issuer = "Android Open Source Project";
+ string subject = "Android IdentityCredential VTS Test";
+ time_t validityNotBefore = time(nullptr);
+ time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+ return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
+ serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+}
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+ sp<IWritableIdentityCredential>& writableCredential,
+ const vector<TestProfile>& testProfiles) {
+ Status result;
+
+ vector<SecureAccessControlProfile> secureProfiles;
+
+ for (const auto& testProfile : testProfiles) {
+ SecureAccessControlProfile profile;
+ Certificate cert;
+ cert.encodedCertificate = testProfile.readerCertificate;
+ result = writableCredential->addAccessControlProfile(
+ testProfile.id, cert, testProfile.userAuthenticationRequired,
+ testProfile.timeoutMillis, 0, &profile);
+
+ // Don't use assert so all errors can be outputed. Then return
+ // instead of exit even on errors so caller can decide.
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << "test profile id = " << testProfile.id << endl;
+ EXPECT_EQ(testProfile.id, profile.id);
+ EXPECT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+ EXPECT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+ EXPECT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+ EXPECT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+
+ if (!result.isOk() || testProfile.id != profile.id ||
+ testProfile.readerCertificate != profile.readerCertificate.encodedCertificate ||
+ testProfile.userAuthenticationRequired != profile.userAuthenticationRequired ||
+ testProfile.timeoutMillis != profile.timeoutMillis ||
+ support::kAesGcmTagSize + support::kAesGcmIvSize != profile.mac.size()) {
+ return {};
+ }
+
+ secureProfiles.push_back(profile);
+ }
+
+ return secureProfiles;
+}
+
+// Most test expects this function to pass. So we will print out additional
+// value if failed so more debug data can be provided.
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+ int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+ bool expectSuccess) {
+ Status result;
+ vector<vector<uint8_t>> chunks = support::chunkVector(entry.valueCbor, dataChunkSize);
+
+ result = writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+ entry.valueCbor.size());
+
+ if (expectSuccess) {
+ EXPECT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+ << "entry name = " << entry.name << ", name space=" << entry.nameSpace << endl;
+ }
+
+ if (!result.isOk()) {
+ return false;
+ }
+
+ vector<vector<uint8_t>> encryptedChunks;
+ for (const auto& chunk : chunks) {
+ vector<uint8_t> encryptedContent;
+ result = writableCredential->addEntryValue(chunk, &encryptedContent);
+ if (expectSuccess) {
+ EXPECT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+ << "entry name = " << entry.name << ", name space = " << entry.nameSpace
+ << endl;
+
+ EXPECT_GT(encryptedContent.size(), 0u) << "entry name = " << entry.name
+ << ", name space = " << entry.nameSpace << endl;
+ }
+
+ if (!result.isOk() || encryptedContent.size() <= 0u) {
+ return false;
+ }
+
+ encryptedChunks.push_back(encryptedContent);
+ }
+
+ encryptedBlobs[&entry] = encryptedChunks;
+ return true;
+}
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates) {
+ return (inputCertificates.size() >= 2);
+ // TODO: add parsing of the certificate and make sure it is genuine.
+}
+
+void SetImageData(vector<uint8_t>& image) {
+ image.resize(256 * 1024 - 10);
+ for (size_t n = 0; n < image.size(); n++) {
+ image[n] = (uint8_t)n;
+ }
+}
+
+} // namespace android::hardware::identity::test_utils
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/VtsIdentityTestUtils.h
new file mode 100644
index 0000000..043ccd6
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.h
@@ -0,0 +1,118 @@
+/*
+ * 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 VTS_IDENTITY_TEST_UTILS_H
+#define VTS_IDENTITY_TEST_UTILS_H
+
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android::hardware::identity::test_utils {
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::vector;
+
+using ::android::sp;
+using ::android::binder::Status;
+
+struct AttestationData {
+ AttestationData(sp<IWritableIdentityCredential>& writableCredential, string challenge,
+ vector<uint8_t> applicationId)
+ : attestationApplicationId(applicationId) {
+ // ASSERT_NE(writableCredential, nullptr);
+
+ if (!challenge.empty()) {
+ attestationChallenge.assign(challenge.begin(), challenge.end());
+ }
+
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+ }
+
+ AttestationData() {}
+
+ vector<uint8_t> attestationChallenge;
+ vector<uint8_t> attestationApplicationId;
+ vector<Certificate> attestationCertificate;
+ Status result;
+};
+
+struct TestEntryData {
+ TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
+ : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+ TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+ }
+ TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+ vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bstr(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bool(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ if (value >= 0) {
+ valueCbor = cppbor::Uint(value).encode();
+ } else {
+ valueCbor = cppbor::Nint(-value).encode();
+ }
+ }
+
+ string nameSpace;
+ string name;
+ vector<uint8_t> valueCbor;
+ vector<int32_t> profileIds;
+};
+
+struct TestProfile {
+ uint16_t id;
+ vector<uint8_t> readerCertificate;
+ bool userAuthenticationRequired;
+ uint64_t timeoutMillis;
+};
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+ sp<IIdentityCredentialStore>& credentialStore);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+ vector<uint8_t>& readerPrivateKey);
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+ sp<IWritableIdentityCredential>& writableCredential,
+ const vector<TestProfile>& testProfiles);
+
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+ int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+ bool expectSuccess);
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates);
+
+void SetImageData(vector<uint8_t>& image);
+
+} // namespace android::hardware::identity::test_utils
+
+#endif // VTS_IDENTITY_TEST_UTILS_H
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index bf6a5c3..dc49ddc 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -958,12 +958,17 @@
optional<vector<uint8_t>> createEcKeyPair() {
auto ec_key = EC_KEY_Ptr(EC_KEY_new());
auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
- auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
if (ec_key.get() == nullptr || pkey.get() == nullptr) {
LOG(ERROR) << "Memory allocation failed";
return {};
}
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ if (group.get() == nullptr) {
+ LOG(ERROR) << "Error creating EC group by curve name";
+ return {};
+ }
+
if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
LOG(ERROR) << "Error generating key";
diff --git a/keymaster/4.1/support/attestation_record.cpp b/keymaster/4.1/support/attestation_record.cpp
index 63bf854..598b6b5 100644
--- a/keymaster/4.1/support/attestation_record.cpp
+++ b/keymaster/4.1/support/attestation_record.cpp
@@ -296,6 +296,10 @@
std::tuple<ErrorCode, AttestationRecord> parse_attestation_record(const hidl_vec<uint8_t>& cert) {
const uint8_t* p = cert.data();
X509_Ptr x509(d2i_X509(nullptr, &p, cert.size()));
+ if (!x509.get()) {
+ LOG(ERROR) << "Error converting DER";
+ return {ErrorCode::INVALID_ARGUMENT, {}};
+ }
ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */));
if (!oid.get()) {
diff --git a/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp b/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp
index 7ea3275..495de0f 100644
--- a/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp
+++ b/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#define LOG_TAG "keymaster_hidl_hal_test"
+#include <cutils/log.h>
+
#include "Keymaster4_1HidlTest.h"
#include <cutils/properties.h>
@@ -23,6 +26,10 @@
#include <keymasterV4_1/attestation_record.h>
#include <keymasterV4_1/authorization_set.h>
+// Not to dump the attestation by default. Can enable by specify the parameter
+// "--dump_attestations" on lunching VTS
+static bool dumpAttestations = false;
+
namespace android::hardware::keymaster::V4_0 {
bool operator==(const AuthorizationSet& a, const AuthorizationSet& b) {
@@ -57,6 +64,10 @@
return retval;
}
+inline void dumpContent(string content) {
+ std::cout << content << std::endl;
+}
+
struct AuthorizationSetDifferences {
string aName;
string bName;
@@ -126,6 +137,23 @@
}
}
+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::INCLUDE_UNIQUE_ID, Tag::BLOB_USAGE_REQUIREMENTS, Tag::EC_CURVE,
+ Tag::HARDWARE_TYPE, Tag::VENDOR_PATCHLEVEL, Tag::BOOT_PATCHLEVEL,
+ Tag::CREATION_DATETIME,
+ };
+ return std::find(tag_list.begin(), tag_list.end(), (V4_1::Tag)entry.tag) != tag_list.end();
+}
+
+AuthorizationSet filter_tags(const AuthorizationSet& set) {
+ AuthorizationSet filtered;
+ std::remove_copy_if(set.begin(), set.end(), std::back_inserter(filtered), tag_in_list);
+ return filtered;
+}
+
void check_attestation_record(AttestationRecord attestation, const HidlBuf& challenge,
AuthorizationSet expected_sw_enforced,
AuthorizationSet expected_hw_enforced,
@@ -144,9 +172,9 @@
attestation.software_enforced.Sort();
attestation.hardware_enforced.Sort();
- EXPECT_EQ(expected_sw_enforced, attestation.software_enforced)
+ EXPECT_EQ(filter_tags(expected_sw_enforced), filter_tags(attestation.software_enforced))
<< DIFFERENCE(expected_sw_enforced, attestation.software_enforced);
- EXPECT_EQ(expected_hw_enforced, attestation.hardware_enforced)
+ EXPECT_EQ(filter_tags(expected_hw_enforced), filter_tags(attestation.hardware_enforced))
<< DIFFERENCE(expected_hw_enforced, attestation.hardware_enforced);
}
@@ -155,8 +183,8 @@
using std::string;
using DeviceUniqueAttestationTest = Keymaster4_1HidlTest;
-TEST_P(DeviceUniqueAttestationTest, StrongBoxOnly) {
- if (SecLevel() != SecurityLevel::STRONGBOX) return;
+TEST_P(DeviceUniqueAttestationTest, NonStrongBoxOnly) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
ASSERT_EQ(ErrorCode::OK, convert(GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
@@ -193,11 +221,11 @@
if (SecLevel() != SecurityLevel::STRONGBOX) return;
ASSERT_EQ(ErrorCode::OK,
convert(GenerateKey(AuthorizationSetBuilder()
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .RsaSigningKey(2048, 65537)
- .Digest(Digest::SHA_2_256)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
- .Authorization(TAG_CREATION_DATETIME, 1))));
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaSigningKey(2048, 65537)
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .Authorization(TAG_INCLUDE_UNIQUE_ID))));
hidl_vec<hidl_vec<uint8_t>> cert_chain;
HidlBuf challenge("challenge");
@@ -209,14 +237,14 @@
.Authorization(TAG_ATTESTATION_APPLICATION_ID, app_id),
&cert_chain)));
- EXPECT_EQ(1U, cert_chain.size());
+ EXPECT_EQ(2U, cert_chain.size());
+ if (dumpAttestations) dumpContent(bin2hex(cert_chain[0]));
auto [err, attestation] = parse_attestation_record(cert_chain[0]);
- EXPECT_EQ(ErrorCode::OK, err);
+ ASSERT_EQ(ErrorCode::OK, err);
check_attestation_record(attestation, challenge,
/* sw_enforced */
AuthorizationSetBuilder()
- .Authorization(TAG_CREATION_DATETIME, 1)
.Authorization(TAG_ATTESTATION_APPLICATION_ID, app_id),
/* hw_enforced */
AuthorizationSetBuilder()
@@ -238,7 +266,7 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
.Digest(Digest::SHA_2_256)
- .Authorization(TAG_CREATION_DATETIME, 1))));
+ .Authorization(TAG_INCLUDE_UNIQUE_ID))));
hidl_vec<hidl_vec<uint8_t>> cert_chain;
HidlBuf challenge("challenge");
@@ -250,29 +278,42 @@
.Authorization(TAG_ATTESTATION_APPLICATION_ID, app_id),
&cert_chain)));
- EXPECT_EQ(1U, cert_chain.size());
+ EXPECT_EQ(2U, cert_chain.size());
+ if (dumpAttestations) dumpContent(bin2hex(cert_chain[0]));
auto [err, attestation] = parse_attestation_record(cert_chain[0]);
- EXPECT_EQ(ErrorCode::OK, err);
+ ASSERT_EQ(ErrorCode::OK, err);
check_attestation_record(attestation, challenge,
- /* sw_enforced */
- AuthorizationSetBuilder()
- .Authorization(TAG_CREATION_DATETIME, 1)
- .Authorization(TAG_ATTESTATION_APPLICATION_ID, app_id),
- /* hw_enforced */
- AuthorizationSetBuilder()
- .Authorization(TAG_DEVICE_UNIQUE_ATTESTATION)
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .EcdsaSigningKey(256)
- .Digest(Digest::SHA_2_256)
- .Authorization(TAG_EC_CURVE, EcCurve::P_256)
- .Authorization(TAG_ORIGIN, KeyOrigin::GENERATED)
- .Authorization(TAG_OS_VERSION, os_version())
- .Authorization(TAG_OS_PATCHLEVEL, os_patch_level()),
- SecLevel());
+ /* sw_enforced */
+ AuthorizationSetBuilder().Authorization(TAG_ATTESTATION_APPLICATION_ID, app_id),
+ /* hw_enforced */
+ AuthorizationSetBuilder()
+ .Authorization(TAG_DEVICE_UNIQUE_ATTESTATION)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .EcdsaSigningKey(256)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_EC_CURVE, EcCurve::P_256)
+ .Authorization(TAG_ORIGIN, KeyOrigin::GENERATED)
+ .Authorization(TAG_OS_VERSION, os_version())
+ .Authorization(TAG_OS_PATCHLEVEL, os_patch_level()),
+ SecLevel());
}
INSTANTIATE_KEYMASTER_4_1_HIDL_TEST(DeviceUniqueAttestationTest);
} // namespace test
} // namespace android::hardware::keymaster::V4_1
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ for (int i = 1; i < argc; ++i) {
+ if (argv[i][0] == '-') {
+ if (std::string(argv[i]) == "--dump_attestations") {
+ dumpAttestations = true;
+ }
+ }
+ }
+ int status = RUN_ALL_TESTS();
+ ALOGI("Test result = %d", status);
+ return status;
+}
diff --git a/media/omx/1.0/vts/functional/audio/Android.bp b/media/omx/1.0/vts/functional/audio/Android.bp
index 532521e..ec7357c 100644
--- a/media/omx/1.0/vts/functional/audio/Android.bp
+++ b/media/omx/1.0/vts/functional/audio/Android.bp
@@ -25,7 +25,7 @@
data: [":media_omx_audio_res"],
test_config: "VtsHalMediaOmxV1_0TargetAudioEncTest.xml",
test_suites: [
- "vts-core",
+ "vts",
],
}
@@ -40,6 +40,6 @@
data: [":media_omx_audio_res"],
test_config: "VtsHalMediaOmxV1_0TargetAudioDecTest.xml",
test_suites: [
- "vts-core",
+ "vts",
],
}
diff --git a/media/omx/1.0/vts/functional/component/Android.bp b/media/omx/1.0/vts/functional/component/Android.bp
index c7be2cc..8fb627a 100644
--- a/media/omx/1.0/vts/functional/component/Android.bp
+++ b/media/omx/1.0/vts/functional/component/Android.bp
@@ -19,6 +19,6 @@
defaults: ["VtsHalMediaOmxV1_0Defaults"],
srcs: ["VtsHalMediaOmxV1_0TargetComponentTest.cpp"],
test_suites: [
- "vts-core",
+ "vts",
],
}
diff --git a/media/omx/1.0/vts/functional/master/Android.bp b/media/omx/1.0/vts/functional/master/Android.bp
index 0eb2cc9..5953eb5 100644
--- a/media/omx/1.0/vts/functional/master/Android.bp
+++ b/media/omx/1.0/vts/functional/master/Android.bp
@@ -19,6 +19,7 @@
defaults: ["VtsHalMediaOmxV1_0Defaults"],
srcs: ["VtsHalMediaOmxV1_0TargetMasterTest.cpp"],
test_suites: [
- "vts-core",
+ "general-tests",
+ "vts",
],
}
diff --git a/media/omx/1.0/vts/functional/master/VtsHalMediaOmxV1_0TargetMasterTest.cpp b/media/omx/1.0/vts/functional/master/VtsHalMediaOmxV1_0TargetMasterTest.cpp
index c14f1da..9b4722e 100644
--- a/media/omx/1.0/vts/functional/master/VtsHalMediaOmxV1_0TargetMasterTest.cpp
+++ b/media/omx/1.0/vts/functional/master/VtsHalMediaOmxV1_0TargetMasterTest.cpp
@@ -20,6 +20,7 @@
#endif
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <android/hardware/media/omx/1.0/IOmx.h>
#include <android/hardware/media/omx/1.0/IOmxNode.h>
@@ -33,21 +34,22 @@
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
-using ::android::hardware::media::omx::V1_0::IOmx;
-using ::android::hardware::media::omx::V1_0::IOmxObserver;
-using ::android::hardware::media::omx::V1_0::IOmxNode;
-using ::android::hardware::media::omx::V1_0::IOmxStore;
-using ::android::hardware::media::omx::V1_0::Message;
-using ::android::hardware::media::omx::V1_0::CodecBuffer;
-using ::android::hardware::media::omx::V1_0::PortMode;
-using ::android::hidl::allocator::V1_0::IAllocator;
-using ::android::hidl::memory::V1_0::IMemory;
-using ::android::hidl::memory::V1_0::IMapper;
+using ::android::sp;
+using ::android::base::Join;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
-using ::android::sp;
+using ::android::hardware::media::omx::V1_0::CodecBuffer;
+using ::android::hardware::media::omx::V1_0::IOmx;
+using ::android::hardware::media::omx::V1_0::IOmxNode;
+using ::android::hardware::media::omx::V1_0::IOmxObserver;
+using ::android::hardware::media::omx::V1_0::IOmxStore;
+using ::android::hardware::media::omx::V1_0::Message;
+using ::android::hardware::media::omx::V1_0::PortMode;
+using ::android::hidl::allocator::V1_0::IAllocator;
+using ::android::hidl::memory::V1_0::IMapper;
+using ::android::hidl::memory::V1_0::IMemory;
#include <getopt.h>
#include <media_hidl_test_common.h>
@@ -70,6 +72,11 @@
}
};
+struct AttributePattern {
+ const testing::internal::RE key;
+ const testing::internal::RE value;
+};
+
void displayComponentInfo(hidl_vec<IOmx::ComponentInfo>& nodeList) {
for (size_t i = 0; i < nodeList.size(); i++) {
printf("%s | ", nodeList[i].mName.c_str());
@@ -80,6 +87,109 @@
}
}
+/*
+ * Returns the role based on is_encoder and mime.
+ *
+ * The mapping from a pair (is_encoder, mime) to a role string is
+ * defined in frameworks/av/media/libmedia/MediaDefs.cpp and
+ * frameworks/av/media/libstagefright/omx/OMXUtils.cpp. This function
+ * does essentially the same work as GetComponentRole() in
+ * OMXUtils.cpp.
+ *
+ * Args:
+ * is_encoder: A boolean indicating whether the role is for an
+ * encoder or a decoder.
+ * mime: A string of the desired mime type.
+ *
+ * Returns:
+ * A const string for the requested role name, empty if mime is not
+ * recognized.
+ */
+const std::string getComponentRole(bool isEncoder, const std::string mime) {
+ // Mapping from mime types to roles.
+ // These values come from MediaDefs.cpp and OMXUtils.cpp
+ const std::map<const std::string, const std::string> audioMimeToRole = {
+ {"3gpp", "amrnb"}, {"ac3", "ac3"}, {"amr-wb", "amrwb"},
+ {"eac3", "eac3"}, {"flac", "flac"}, {"g711-alaw", "g711alaw"},
+ {"g711-mlaw", "g711mlaw"}, {"gsm", "gsm"}, {"mp4a-latm", "aac"},
+ {"mpeg", "mp3"}, {"mpeg-L1", "mp1"}, {"mpeg-L2", "mp2"},
+ {"opus", "opus"}, {"raw", "raw"}, {"vorbis", "vorbis"},
+ };
+ const std::map<const std::string, const std::string> videoMimeToRole = {
+ {"3gpp", "h263"}, {"avc", "avc"}, {"dolby-vision", "dolby-vision"},
+ {"hevc", "hevc"}, {"mp4v-es", "mpeg4"}, {"mpeg2", "mpeg2"},
+ {"x-vnd.on2.vp8", "vp8"}, {"x-vnd.on2.vp9", "vp9"},
+ };
+ const std::map<const std::string, const std::string> imageMimeToRole = {
+ {"vnd.android.heic", "heic"},
+ };
+
+ // Suffix begins after the mime prefix.
+ const size_t prefixEnd = mime.find("/");
+ if (prefixEnd == std::string::npos || prefixEnd == mime.size()) return "";
+ const std::string mime_suffix = mime.substr(prefixEnd + 1, mime.size() - 1);
+ const std::string middle = isEncoder ? "encoder." : "decoder.";
+ std::string prefix;
+ std::string suffix;
+ if (mime.rfind("audio/", 0) != std::string::npos) {
+ const auto it = audioMimeToRole.find(mime_suffix);
+ if (it == audioMimeToRole.end()) return "";
+ prefix = "audio_";
+ suffix = it->second;
+ } else if (mime.rfind("video/", 0) != std::string::npos) {
+ const auto it = videoMimeToRole.find(mime_suffix);
+ if (it == videoMimeToRole.end()) return "";
+ prefix = "video_";
+ suffix = it->second;
+ } else if (mime.rfind("image/", 0) != std::string::npos) {
+ const auto it = imageMimeToRole.find(mime_suffix);
+ if (it == imageMimeToRole.end()) return "";
+ prefix = "image_";
+ suffix = it->second;
+ } else {
+ return "";
+ }
+ return prefix + middle + suffix;
+}
+
+void validateAttributes(
+ const std::map<const std::string, const testing::internal::RE>& knownPatterns,
+ const std::vector<const struct AttributePattern>& unknownPatterns,
+ hidl_vec<IOmxStore::Attribute> attributes) {
+ std::set<const std::string> attributeKeys;
+ for (const auto& attr : attributes) {
+ // Make sure there are no duplicates
+ const auto [nodeIter, inserted] = attributeKeys.insert(attr.key);
+ EXPECT_EQ(inserted, true) << "Attribute \"" << attr.key << "\" has duplicates.";
+
+ // Check the value against the corresponding regular
+ // expression.
+ const auto knownPattern = knownPatterns.find(attr.key);
+ if (knownPattern != knownPatterns.end()) {
+ EXPECT_EQ(testing::internal::RE::FullMatch(attr.value, knownPattern->second), true)
+ << "Attribute \"" << attr.key << "\" has invalid value \"" << attr.value << ".";
+ ;
+ } else {
+ // Failed to find exact attribute, check against
+ // possible patterns.
+ bool keyFound = false;
+ for (const auto& unknownPattern : unknownPatterns) {
+ if (testing::internal::RE::PartialMatch(attr.key, unknownPattern.key)) {
+ keyFound = true;
+ EXPECT_EQ(testing::internal::RE::FullMatch(attr.value, unknownPattern.value),
+ true)
+ << "Attribute \"" << attr.key << "\" has invalid value \"" << attr.value
+ << ".";
+ }
+ }
+ if (!keyFound) {
+ std::cout << "Warning, Unrecognized attribute \"" << attr.key << "\" with value \""
+ << attr.value << "\"." << std::endl;
+ }
+ }
+ }
+}
+
// Make sure IOmx and IOmxStore have the same set of instances.
TEST(MasterHidlTest, instanceMatchValidation) {
auto omxInstances = android::hardware::getAllHalInstanceNames(IOmx::descriptor);
@@ -91,7 +201,7 @@
}
}
-// list service attributes
+// list service attributes and verify expected formats
TEST_P(MasterHidlTest, ListServiceAttr) {
description("list service attributes");
android::hardware::media::omx::V1_0::Status status;
@@ -105,7 +215,36 @@
})
.isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
- if (attributes.size() == 0) ALOGV("Warning, Attribute list empty");
+ if (attributes.size() == 0) {
+ std::cout << "Warning, Attribute list empty" << std::endl;
+ } else {
+ /*
+ * knownPatterns is a map whose keys are the known "key" for a service
+ * attribute pair (see IOmxStore::Attribute), and whose values are the
+ * corresponding regular expressions that will have to match with the
+ * "value" of the attribute pair. If listServiceAttributes() returns an
+ * attribute that has a matching key but an unmatched value, the test
+ * will fail.
+ */
+ const std::map<const std::string, const testing::internal::RE> knownPatterns = {
+ {"max-video-encoder-input-buffers", "0|[1-9][0-9]*"},
+ {"supports-multiple-secure-codecs", "0|1"},
+ {"supports-secure-with-non-secure-codec", "0|1"},
+ };
+ /*
+ * unknownPatterns is a vector of pairs of regular expressions.
+ * For each attribute whose key is not known (i.e., does not match any
+ * of the keys in the "knownPatterns" variable defined above), that key will be
+ * tried for a match with the first element of each pair of the variable
+ * "unknownPatterns". If a match occurs, the value of that same attribute will be
+ * tried for a match with the second element of the pair. If this second
+ * match fails, the test will fail.
+ */
+ const std::vector<const struct AttributePattern> unknownPatterns = {
+ {"supports-[a-z0-9-]*", "0|1"}};
+
+ validateAttributes(knownPatterns, unknownPatterns, attributes);
+ }
}
// get node prefix
@@ -114,17 +253,183 @@
hidl_string prefix;
omxStore->getNodePrefix(
[&prefix](hidl_string const& _nl) { prefix = _nl; });
- if (prefix.empty()) ALOGV("Warning, Node Prefix empty");
+ if (prefix.empty()) std::cout << "Warning, Node Prefix empty" << std::endl;
}
-// list roles
+// list roles and validate all RoleInfo objects
TEST_P(MasterHidlTest, ListRoles) {
description("list roles");
hidl_vec<IOmxStore::RoleInfo> roleList;
omxStore->listRoles([&roleList](hidl_vec<IOmxStore::RoleInfo> const& _nl) {
roleList = _nl;
});
- if (roleList.size() == 0) ALOGV("Warning, RoleInfo list empty");
+ if (roleList.size() == 0) {
+ GTEST_SKIP() << "Warning, RoleInfo list empty";
+ return;
+ }
+
+ // Basic patterns for matching
+ const std::string toggle = "(0|1)";
+ const std::string string = "(.*)";
+ const std::string num = "(0|([1-9][0-9]*))";
+ const std::string size = "(" + num + "x" + num + ")";
+ const std::string ratio = "(" + num + ":" + num + ")";
+ const std::string range_num = "((" + num + "-" + num + ")|" + num + ")";
+ const std::string range_size = "((" + size + "-" + size + ")|" + size + ")";
+ const std::string range_ratio = "((" + ratio + "-" + ratio + ")|" + ratio + ")";
+ const std::string list_range_num = "(" + range_num + "(," + range_num + ")*)";
+
+ // Matching rules for node attributes with fixed keys
+ const std::map<const std::string, const testing::internal::RE> knownPatterns = {
+ {"alignment", size},
+ {"bitrate-range", range_num},
+ {"block-aspect-ratio-range", range_ratio},
+ {"block-count-range", range_num},
+ {"block-size", size},
+ {"blocks-per-second-range", range_num},
+ {"complexity-default", num},
+ {"complexity-range", range_num},
+ {"feature-adaptive-playback", toggle},
+ {"feature-bitrate-control", "(VBR|CBR|CQ)[,(VBR|CBR|CQ)]*"},
+ {"feature-can-swap-width-height", toggle},
+ {"feature-intra-refresh", toggle},
+ {"feature-partial-frame", toggle},
+ {"feature-secure-playback", toggle},
+ {"feature-tunneled-playback", toggle},
+ {"frame-rate-range", range_num},
+ {"max-channel-count", num},
+ {"max-concurrent-instances", num},
+ {"max-supported-instances", num},
+ {"pixel-aspect-ratio-range", range_ratio},
+ {"quality-default", num},
+ {"quality-range", range_num},
+ {"quality-scale", string},
+ {"sample-rate-ranges", list_range_num},
+ {"size-range", range_size},
+ };
+
+ // Strings for matching rules for node attributes with key patterns
+ const std::vector<const struct AttributePattern> unknownPatterns = {
+ {"measured-frame-rate-" + size + "-range", range_num},
+ {"feature-[a-zA-Z0-9_-]+", string},
+ };
+
+ // Matching rules for node names and owners
+ const testing::internal::RE nodeNamePattern = "[a-zA-Z0-9.-]+";
+ const testing::internal::RE nodeOwnerPattern = "[a-zA-Z0-9._-]+";
+
+ std::set<const std::string> roleKeys;
+ std::map<const std::string, std::set<const std::string>> nodeToRoles;
+ std::map<const std::string, std::set<const std::string>> ownerToNodes;
+ for (const IOmxStore::RoleInfo& role : roleList) {
+ // Make sure there are no duplicates
+ const auto [roleIter, inserted] = roleKeys.insert(role.role);
+ EXPECT_EQ(inserted, true) << "Role \"" << role.role << "\" has duplicates.";
+
+ // Make sure role name follows expected format based on type and
+ // isEncoder
+ const std::string role_name = getComponentRole(role.isEncoder, role.type);
+ EXPECT_EQ(role_name, role.role) << "Role \"" << role.role << "\" does not match "
+ << (role.isEncoder ? "an encoder " : "a decoder ")
+ << "for mime type \"" << role.type << ".";
+
+ // Check the nodes for this role
+ std::set<const std::string> nodeKeys;
+ for (const IOmxStore::NodeInfo& node : role.nodes) {
+ // Make sure there are no duplicates
+ const auto [nodeIter, inserted] = nodeKeys.insert(node.name);
+ EXPECT_EQ(inserted, true) << "Node \"" << node.name << "\" has duplicates.";
+
+ // Check the format of node name
+ EXPECT_EQ(testing::internal::RE::FullMatch(node.name, nodeNamePattern), true)
+ << "Node name \"" << node.name << " is invalid.";
+ // Check the format of node owner
+ EXPECT_EQ(testing::internal::RE::FullMatch(node.owner, nodeOwnerPattern), true)
+ << "Node owner \"" << node.owner << " is invalid.";
+
+ validateAttributes(knownPatterns, unknownPatterns, node.attributes);
+
+ ownerToNodes[node.owner].insert(node.name);
+ nodeToRoles[node.name].insert(role.role);
+ }
+ }
+
+ // Verify the information with IOmx::listNodes().
+ // IOmxStore::listRoles() and IOmx::listNodes() should give consistent
+ // information about nodes and roles.
+ for (const auto& [owner, nodes] : ownerToNodes) {
+ // Obtain the IOmx instance for each "owner"
+ const sp<IOmx> omx = omxStore->getOmx(owner);
+ EXPECT_NE(nullptr, omx);
+
+ // Invoke IOmx::listNodes()
+ android::hardware::media::omx::V1_0::Status status;
+ hidl_vec<IOmx::ComponentInfo> nodeList;
+ EXPECT_TRUE(
+ omx->listNodes([&status, &nodeList](android::hardware::media::omx::V1_0::Status _s,
+ hidl_vec<IOmx::ComponentInfo> const& _nl) {
+ status = _s;
+ nodeList = _nl;
+ }).isOk());
+ ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
+
+ // Verify that roles for each node match with the information from
+ // IOmxStore::listRoles().
+ std::set<const std::string> nodeKeys;
+ for (IOmx::ComponentInfo node : nodeList) {
+ // Make sure there are no duplicates
+ const auto [nodeIter, inserted] = nodeKeys.insert(node.mName);
+ EXPECT_EQ(inserted, true)
+ << "IOmx::listNodes() lists duplicate nodes \"" << node.mName << "\".";
+
+ // Skip "hidden" nodes, i.e. those that are not advertised by
+ // IOmxStore::listRoles().
+ if (nodes.find(node.mName) == nodes.end()) {
+ std::cout << "Warning, IOmx::listNodes() lists unknown node \"" << node.mName
+ << "\" for IOmx instance \"" << owner << "\"." << std::endl;
+ continue;
+ }
+
+ // All the roles advertised by IOmxStore::listRoles() for this
+ // node must be included in roleKeys.
+ std::set<const std::string> difference;
+ std::set_difference(nodeToRoles[node.mName].begin(), nodeToRoles[node.mName].end(),
+ roleKeys.begin(), roleKeys.end(),
+ std::inserter(difference, difference.begin()));
+ EXPECT_EQ(difference.empty(), true) << "IOmx::listNodes() for IOmx "
+ "instance \""
+ << owner
+ << "\" does not report some "
+ "expected nodes: "
+ << android::base::Join(difference, ", ") << ".";
+ }
+ // Check that all nodes obtained from IOmxStore::listRoles() are
+ // supported by the their corresponding IOmx instances.
+ std::set<const std::string> difference;
+ std::set_difference(nodes.begin(), nodes.end(), nodeKeys.begin(), nodeKeys.end(),
+ std::inserter(difference, difference.begin()));
+ EXPECT_EQ(difference.empty(), true) << "IOmx::listNodes() for IOmx "
+ "instance \""
+ << owner
+ << "\" does not report some "
+ "expected nodes: "
+ << android::base::Join(difference, ", ") << ".";
+ }
+
+ if (!nodeToRoles.empty()) {
+ // Check that the prefix is a sensible string.
+ hidl_string prefix;
+ omxStore->getNodePrefix([&prefix](hidl_string const& _nl) { prefix = _nl; });
+ EXPECT_EQ(testing::internal::RE::PartialMatch(prefix, nodeNamePattern), true)
+ << "\"" << prefix << "\" is not a valid prefix for node names.";
+
+ // Check that all node names have the said prefix.
+ for (const auto& node : nodeToRoles) {
+ EXPECT_NE(node.first.rfind(prefix, 0), std::string::npos)
+ << "Node \"" << node.first << "\" does not start with prefix \"" << prefix
+ << "\".";
+ }
+ }
}
// list components and roles.
@@ -143,7 +448,7 @@
.isOk());
ASSERT_EQ(status, android::hardware::media::omx::V1_0::Status::OK);
if (nodeList.size() == 0)
- ALOGV("Warning, ComponentInfo list empty");
+ std::cout << "Warning, ComponentInfo list empty" << std::endl;
else {
// displayComponentInfo(nodeList);
for (size_t i = 0; i < nodeList.size(); i++) {
diff --git a/media/omx/1.0/vts/functional/video/Android.bp b/media/omx/1.0/vts/functional/video/Android.bp
index 7e93faa..b35c26c 100644
--- a/media/omx/1.0/vts/functional/video/Android.bp
+++ b/media/omx/1.0/vts/functional/video/Android.bp
@@ -25,7 +25,7 @@
data: [":media_omx_video_res"],
test_config: "VtsHalMediaOmxV1_0TargetVideoDecTest.xml",
test_suites: [
- "vts-core",
+ "vts",
],
}
@@ -43,6 +43,6 @@
data: [":media_omx_video_res"],
test_config: "VtsHalMediaOmxV1_0TargetVideoEncTest.xml",
test_suites: [
- "vts-core",
+ "vts",
],
}
diff --git a/neuralnetworks/1.0/vts/functional/AndroidTest.xml b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..54e6e91
--- /dev/null
+++ b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalNeuralnetworksV1_0TargetTest.">
+ <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="VtsHalNeuralnetworksV1_0TargetTest->/data/local/tmp/VtsHalNeuralnetworksV1_0TargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <!-- b/155577050, temporarily disable the failing tests.
+ Must be deleted after corresponding driver issues are fixed.
+ -->
+ <option name="native-test-flag" value="--gtest_filter=-*Validation*:*CycleTest*:*sample_float_fast*:*sample_float_slow*:*sample_minimal*:*sample_quant*" />
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalNeuralnetworksV1_0TargetTest" />
+ </test>
+</configuration>
diff --git a/neuralnetworks/1.0/vts/functional/BasicTests.cpp b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
index cc44c9e..bda43b1 100644
--- a/neuralnetworks/1.0/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
@@ -18,8 +18,12 @@
#include "VtsHalNeuralnetworks.h"
+#include "1.0/Callbacks.h"
+
namespace android::hardware::neuralnetworks::V1_0::vts::functional {
+using implementation::PreparedModelCallback;
+
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -43,4 +47,136 @@
EXPECT_TRUE(ret.isOk());
}
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel(model, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_0::vts::functional
diff --git a/neuralnetworks/1.0/vts/functional/Utils.cpp b/neuralnetworks/1.0/vts/functional/Utils.cpp
index 3613e69..32850b0 100644
--- a/neuralnetworks/1.0/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.0/vts/functional/Utils.cpp
@@ -29,7 +29,11 @@
#include <gtest/gtest.h>
#include <algorithm>
+#include <cstring>
+#include <functional>
#include <iostream>
+#include <map>
+#include <numeric>
#include <vector>
namespace android::hardware::neuralnetworks {
@@ -172,6 +176,45 @@
return outputBuffers;
}
+uint32_t sizeOfData(V1_0::OperandType type) {
+ switch (type) {
+ case V1_0::OperandType::FLOAT32:
+ case V1_0::OperandType::INT32:
+ case V1_0::OperandType::UINT32:
+ case V1_0::OperandType::TENSOR_FLOAT32:
+ case V1_0::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+ return 1;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_0::OperandType type) {
+ switch (type) {
+ case V1_0::OperandType::FLOAT32:
+ case V1_0::OperandType::INT32:
+ case V1_0::OperandType::UINT32:
+ return false;
+ case V1_0::OperandType::TENSOR_FLOAT32:
+ case V1_0::OperandType::TENSOR_INT32:
+ case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_0::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
std::string gtestCompliantName(std::string name) {
// gtest test names must only contain alphanumeric characters
std::replace_if(
diff --git a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
index 79d8594..5ffbd43 100644
--- a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
@@ -17,9 +17,14 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
#include "1.0/Callbacks.h"
+#include "1.0/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_0::vts::functional {
using implementation::PreparedModelCallback;
@@ -67,26 +72,6 @@
validatePrepareModel(device, message, model);
}
-// Delete element from hidl_vec. hidl_vec doesn't support a "remove" operation,
-// so this is efficiently accomplished by moving the element to the end and
-// resizing the hidl_vec to one less.
-template <typename Type>
-static void hidl_vec_removeAt(hidl_vec<Type>* vec, uint32_t index) {
- if (vec) {
- std::rotate(vec->begin() + index, vec->begin() + index + 1, vec->end());
- vec->resize(vec->size() - 1);
- }
-}
-
-template <typename Type>
-static uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
- // assume vec is valid
- const uint32_t index = vec->size();
- vec->resize(index + 1);
- (*vec)[index] = value;
- return index;
-}
-
static uint32_t addOperand(Model* model) {
return hidl_vec_push_back(&model->operands,
{
@@ -107,6 +92,211 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const int32_t invalidOperandTypes[] = {
@@ -218,9 +408,233 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model, [operand, invalidLifeTime](Model* model) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model, [operand, changedLifeTime](Model* model) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+ const V1_0::Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model, [operand, invalidNumberOfConsumers](Model* model) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model, [operation, badOutputNum](Model* model) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size(); ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue = model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -351,6 +765,33 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model, [operation, outputNum](Model* model) {
+ uint32_t& outputOperandIndex = model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex = hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -476,14 +917,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
index 3292f79..7bd0460 100644
--- a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
+++ b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
@@ -21,6 +21,7 @@
#include <android/hardware/neuralnetworks/1.0/types.h>
#include <android/hardware_buffer.h>
#include <android/hidl/memory/1.0/IMemory.h>
+#include <gtest/gtest.h>
#include <algorithm>
#include <iosfwd>
#include <string>
@@ -108,6 +109,15 @@
vec->resize(vec->size() - 1);
}
+// Assumes there is exactly one instance of the value in the vector.
+template <typename Type>
+inline void hidl_vec_remove(hidl_vec<Type>* vec, const Type& val) {
+ CHECK(vec != nullptr);
+ auto where = std::find(vec->begin(), vec->end(), val);
+ ASSERT_NE(where, vec->end());
+ hidl_vec_removeAt(vec, where - vec->begin());
+}
+
template <typename Type>
inline uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
CHECK(vec != nullptr);
@@ -117,6 +127,18 @@
return index;
}
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_0::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_0::Operand& operand);
+
template <typename Type>
using Named = std::pair<std::string, Type>;
diff --git a/neuralnetworks/1.1/vts/functional/AndroidTest.xml b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..a6f812f
--- /dev/null
+++ b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalNeuralnetworksV1_1TargetTest.">
+ <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="VtsHalNeuralnetworksV1_1TargetTest->/data/local/tmp/VtsHalNeuralnetworksV1_1TargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <!-- b/155577050, temporarily disable the failing tests.
+ Must be deleted after corresponding driver issues are fixed.
+ -->
+ <option name="native-test-flag" value="--gtest_filter=-*Validation*:*CycleTest*:*sample_float_fast*:*sample_float_slow*:*sample_minimal*:*sample_quant*" />
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalNeuralnetworksV1_1TargetTest" />
+ </test>
+</configuration>
diff --git a/neuralnetworks/1.1/vts/functional/BasicTests.cpp b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
index 44836f0..baadd1b 100644
--- a/neuralnetworks/1.1/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
@@ -18,10 +18,16 @@
#include "VtsHalNeuralnetworks.h"
+#include "1.0/Callbacks.h"
+
namespace android::hardware::neuralnetworks::V1_1::vts::functional {
using V1_0::DeviceStatus;
using V1_0::ErrorStatus;
+using V1_0::Operand;
+using V1_0::OperandLifeTime;
+using V1_0::OperandType;
+using V1_0::implementation::PreparedModelCallback;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -48,4 +54,137 @@
EXPECT_TRUE(ret.isOk());
}
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_1() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_1(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_1() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_1(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_1::vts::functional
diff --git a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
index 3b6f0f8..1f4e4ed 100644
--- a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
@@ -16,13 +16,19 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
#include "1.0/Callbacks.h"
#include "1.0/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_1::vts::functional {
+using V1_0::DataLocation;
using V1_0::ErrorStatus;
using V1_0::IPreparedModel;
using V1_0::Operand;
@@ -105,6 +111,212 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const int32_t invalidOperandTypes[] = {
@@ -221,9 +433,240 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+ const V1_1::Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -358,6 +801,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*) {
+ uint32_t& outputOperandIndex =
+ model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -504,14 +978,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index 481eb80..182f716 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -15,11 +15,12 @@
//
cc_library_static {
- name: "VtsHalNeuralNetworksV1_2Callbacks",
+ name: "VtsHalNeuralNetworksV1_2_utils",
defaults: ["neuralnetworks_vts_functional_defaults"],
export_include_dirs: ["include"],
srcs: [
"Callbacks.cpp",
+ "Utils.cpp",
],
static_libs: [
"android.hardware.neuralnetworks@1.0",
@@ -51,7 +52,7 @@
],
static_libs: [
"VtsHalNeuralNetworksV1_0_utils",
- "VtsHalNeuralNetworksV1_2Callbacks",
+ "VtsHalNeuralNetworksV1_2_utils",
"android.hardware.neuralnetworks@1.0",
"android.hardware.neuralnetworks@1.1",
"android.hardware.neuralnetworks@1.2",
diff --git a/neuralnetworks/1.2/vts/functional/AndroidTest.xml b/neuralnetworks/1.2/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..adbdf40
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalNeuralnetworksV1_2TargetTest.">
+ <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="VtsHalNeuralnetworksV1_2TargetTest->/data/local/tmp/VtsHalNeuralnetworksV1_2TargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <!-- b/155577050, b/155674368, b/153876253, temporarily disable the test.
+ Must be deleted after corresponding driver issues are fixed.
+ -->
+ <option name="native-test-flag" value="--gtest_filter=-*Validation*:*squeeze*_all*_inputs*:*strided_slice*_all*_inputs*:*transpose*_all*_inputs*:*l2_normalization_axis_corner_case*:*sample_float_fast*:*sample_float_slow*:*sample_minimal*:*sample_quant*" />
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalNeuralnetworksV1_2TargetTest" />
+ </test>
+</configuration>
diff --git a/neuralnetworks/1.2/vts/functional/BasicTests.cpp b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
index 58d3c4a..77340e7 100644
--- a/neuralnetworks/1.2/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
@@ -20,9 +20,13 @@
namespace android::hardware::neuralnetworks::V1_2::vts::functional {
+using implementation::PreparedModelCallback;
using V1_0::DeviceStatus;
using V1_0::ErrorStatus;
+using V1_0::OperandLifeTime;
using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -123,4 +127,139 @@
});
EXPECT_TRUE(ret.isOk());
}
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_2() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_2(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_2() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_2(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
+ hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_2::vts::functional
diff --git a/neuralnetworks/1.2/vts/functional/Utils.cpp b/neuralnetworks/1.2/vts/functional/Utils.cpp
new file mode 100644
index 0000000..cc654f2
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/Utils.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+#include <functional>
+#include <numeric>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+uint32_t sizeOfData(V1_2::OperandType type) {
+ switch (type) {
+ case V1_2::OperandType::FLOAT32:
+ case V1_2::OperandType::INT32:
+ case V1_2::OperandType::UINT32:
+ case V1_2::OperandType::TENSOR_FLOAT32:
+ case V1_2::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_2::OperandType::TENSOR_FLOAT16:
+ case V1_2::OperandType::FLOAT16:
+ case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+ return 2;
+ case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_2::OperandType::BOOL:
+ case V1_2::OperandType::TENSOR_BOOL8:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+ return 1;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_2::OperandType type) {
+ switch (type) {
+ case V1_2::OperandType::FLOAT32:
+ case V1_2::OperandType::INT32:
+ case V1_2::OperandType::UINT32:
+ case V1_2::OperandType::FLOAT16:
+ case V1_2::OperandType::BOOL:
+ return false;
+ case V1_2::OperandType::TENSOR_FLOAT32:
+ case V1_2::OperandType::TENSOR_INT32:
+ case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_2::OperandType::TENSOR_FLOAT16:
+ case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+ case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_2::OperandType::TENSOR_BOOL8:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_2::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
+} // namespace neuralnetworks
+} // namespace hardware
+} // namespace android
diff --git a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
index 7451f09..3375602 100644
--- a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
@@ -16,14 +16,21 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
#include "1.0/Utils.h"
#include "1.2/Callbacks.h"
+#include "1.2/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_2::vts::functional {
using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
using V1_0::ErrorStatus;
using V1_0::OperandLifeTime;
using V1_1::ExecutionPreference;
@@ -105,6 +112,250 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ size_t size = 0;
+
+ size += sizeForBinder(symmPerChannelQuantParams.scales);
+ size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operand::ExtraParams& extraParams) {
+ using Discriminator = Operand::ExtraParams::hidl_discriminator;
+ switch (extraParams.getDiscriminator()) {
+ case Discriminator::none:
+ return 0;
+ case Discriminator::channelQuant:
+ return sizeForBinder(extraParams.channelQuant());
+ case Discriminator::extension:
+ return sizeForBinder(extraParams.extension());
+ }
+ LOG(FATAL) << "Unrecognized extraParams enum: "
+ << static_cast<int>(extraParams.getDiscriminator());
+ return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+ size += sizeForBinder(operand.extraParams);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+ size_t size = 0;
+
+ size += sizeForBinder(extensionNameToPrefix.name);
+ size += sizeForBinder(extensionNameToPrefix.prefix);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+ size += sizeForBinder(model.extensionNameToPrefix);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const uint32_t invalidOperandTypes[] = {
@@ -251,9 +502,239 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -461,6 +942,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*) {
+ uint32_t& outputOperandIndex =
+ model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -711,14 +1223,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
new file mode 100644
index 0000000..61a8d74
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
@@ -0,0 +1,42 @@
+/*
+ * 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_NEURALNETWORKS_V1_2_UTILS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
+
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_2::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_2::Operand& operand);
+
+} // namespace neuralnetworks
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index 2c1be0b..771fc54 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -54,7 +54,7 @@
],
static_libs: [
"VtsHalNeuralNetworksV1_0_utils",
- "VtsHalNeuralNetworksV1_2Callbacks",
+ "VtsHalNeuralNetworksV1_2_utils",
"VtsHalNeuralNetworksV1_3_utils",
"android.hardware.neuralnetworks@1.0",
"android.hardware.neuralnetworks@1.1",
diff --git a/neuralnetworks/1.3/vts/functional/BasicTests.cpp b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
index 1c25369..6fcfc34 100644
--- a/neuralnetworks/1.3/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
@@ -20,11 +20,14 @@
namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+using implementation::PreparedModelCallback;
using V1_0::DeviceStatus;
using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
using V1_2::Constant;
using V1_2::DeviceType;
using V1_2::Extension;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -65,4 +68,143 @@
});
EXPECT_TRUE(ret.isOk());
}
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ Subgraph subgraph = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ };
+ const Model model = {
+ .main = std::move(subgraph),
+ .referenced = {},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_2() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_3(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.main.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_3() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_3(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, Priority::MEDIUM, {},
+ hidl_vec<hidl_handle>(), hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/Utils.cpp b/neuralnetworks/1.3/vts/functional/Utils.cpp
index 23e2af8..c460e11 100644
--- a/neuralnetworks/1.3/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.3/vts/functional/Utils.cpp
@@ -17,11 +17,78 @@
#include "1.3/Utils.h"
#include <iostream>
+#include <numeric>
+#include "android-base/logging.h"
+#include "android/hardware/neuralnetworks/1.3/types.h"
-namespace android::hardware::neuralnetworks::V1_3 {
+namespace android::hardware::neuralnetworks {
+
+uint32_t sizeOfData(V1_3::OperandType type) {
+ switch (type) {
+ case V1_3::OperandType::FLOAT32:
+ case V1_3::OperandType::INT32:
+ case V1_3::OperandType::UINT32:
+ case V1_3::OperandType::TENSOR_FLOAT32:
+ case V1_3::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_3::OperandType::TENSOR_FLOAT16:
+ case V1_3::OperandType::FLOAT16:
+ case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+ return 2;
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_3::OperandType::BOOL:
+ case V1_3::OperandType::TENSOR_BOOL8:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return 1;
+ case V1_3::OperandType::SUBGRAPH:
+ return 0;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_3::OperandType type) {
+ switch (type) {
+ case V1_3::OperandType::FLOAT32:
+ case V1_3::OperandType::INT32:
+ case V1_3::OperandType::UINT32:
+ case V1_3::OperandType::FLOAT16:
+ case V1_3::OperandType::BOOL:
+ case V1_3::OperandType::SUBGRAPH:
+ return false;
+ case V1_3::OperandType::TENSOR_FLOAT32:
+ case V1_3::OperandType::TENSOR_INT32:
+ case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_3::OperandType::TENSOR_FLOAT16:
+ case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_3::OperandType::TENSOR_BOOL8:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_3::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
+namespace V1_3 {
::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
return os << toString(errorStatus);
}
-} // namespace android::hardware::neuralnetworks::V1_3
+} // namespace V1_3
+} // namespace android::hardware::neuralnetworks
diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
index 4c0100e..849ef7b 100644
--- a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
@@ -16,15 +16,22 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
#include "1.0/Utils.h"
#include "1.3/Callbacks.h"
#include "1.3/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_3::vts::functional {
using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
using V1_1::ExecutionPreference;
using V1_2::SymmPerChannelQuantParams;
using HidlToken =
@@ -112,6 +119,262 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ size_t size = 0;
+
+ size += sizeForBinder(symmPerChannelQuantParams.scales);
+ size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Operand::ExtraParams& extraParams) {
+ using Discriminator = V1_2::Operand::ExtraParams::hidl_discriminator;
+ switch (extraParams.getDiscriminator()) {
+ case Discriminator::none:
+ return 0;
+ case Discriminator::channelQuant:
+ return sizeForBinder(extraParams.channelQuant());
+ case Discriminator::extension:
+ return sizeForBinder(extraParams.extension());
+ }
+ LOG(FATAL) << "Unrecognized extraParams enum: "
+ << static_cast<int>(extraParams.getDiscriminator());
+ return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+ size += sizeForBinder(operand.extraParams);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Subgraph& subgraph) {
+ size_t size = 0;
+
+ size += sizeForBinder(subgraph.operands);
+ size += sizeForBinder(subgraph.operations);
+ size += sizeForBinder(subgraph.inputIndexes);
+ size += sizeForBinder(subgraph.outputIndexes);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+ size_t size = 0;
+
+ size += sizeForBinder(extensionNameToPrefix.name);
+ size += sizeForBinder(extensionNameToPrefix.prefix);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.main);
+ size += sizeForBinder(model.referenced);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+ size += sizeForBinder(model.extensionNameToPrefix);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const Operation& operationObj = model.main.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.main.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin() + operation,
+ operations.begin() + operation + 1, operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const uint32_t invalidOperandTypes[] = {
@@ -261,9 +524,245 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::SUBGRAPH_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::SUBGRAPH_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.main.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ hidl_vec_remove(&model->main.inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT: {
+ hidl_vec_remove(&model->main.outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ hidl_vec_push_back(&model->main.inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ hidl_vec_push_back(&model->main.outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::SUBGRAPH_INPUT;
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::SUBGRAPH_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.main.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.main.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*,
+ Priority*) {
+ model->main.operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0;
+ badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) {
+ const uint32_t outputOperandIndex =
+ model.main.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) {
+ Operation newOperation = model->main.operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->main.operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->main.operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->main.operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->main.operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -511,6 +1010,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum];
+ if (model.main.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*, Priority*) {
+ uint32_t& outputOperandIndex =
+ model->main.operations[operation].outputs[outputNum];
+ Operand operandValue = model->main.operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->main.operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -535,13 +1065,18 @@
removeValueAndDecrementGreaterValues(&model->main.outputIndexes, index);
}
-static bool removeOperandSkip(size_t operand, const Model& model) {
+static bool removeOperandSkip(size_t operandIndex, const Model& model) {
+ const Operand& operand = model.main.operands[operandIndex];
+ if (operand.numberOfConsumers == 0) {
+ // Removing an unused operand has no effect.
+ return true;
+ }
for (const Operation& operation : model.main.operations) {
// Skip removeOperandTest for the following operations.
// - SPLIT's outputs are not checked during prepareModel.
if (operation.type == OperationType::SPLIT) {
- for (const size_t outOprand : operation.outputs) {
- if (operand == outOprand) {
+ for (const size_t index : operation.outputs) {
+ if (index == operandIndex) {
return true;
}
}
@@ -556,8 +1091,8 @@
operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_RNN ||
operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM ||
operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) {
- for (const size_t outOprand : operation.outputs) {
- if (operand == outOprand) {
+ for (const size_t index : operation.outputs) {
+ if (index == operandIndex) {
return true;
}
}
@@ -799,14 +1334,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
index 3661b66..e07e73b 100644
--- a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
+++ b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
@@ -24,6 +24,18 @@
inline constexpr V1_3::Priority kDefaultPriority = V1_3::Priority::MEDIUM;
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_3::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_3::Operand& operand);
+
} // namespace android::hardware::neuralnetworks
namespace android::hardware::neuralnetworks::V1_3 {
diff --git a/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp b/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp
index ba08ee7..7e0ae9c 100644
--- a/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp
+++ b/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp
@@ -19,6 +19,8 @@
#include <cutils/properties.h>
+#include <android-base/file.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <android/hardware/power/1.0/IPower.h>
#include <gtest/gtest.h>
@@ -73,26 +75,16 @@
TEST_P(PowerHidlTest, TryDifferentGovernors) {
Return<void> ret;
- unique_fd fd1(open(CPU_GOVERNOR_PATH, O_RDWR));
- unique_fd fd2(open(AVAILABLE_GOVERNORS_PATH, O_RDONLY));
- if (fd1 < 0 || fd2 < 0) {
+ std::string old_governor, governors;
+ if (!android::base::ReadFileToString(CPU_GOVERNOR_PATH, &old_governor) ||
+ !android::base::ReadFileToString(AVAILABLE_GOVERNORS_PATH, &governors)) {
// Files don't exist, so skip the rest of the test case
SUCCEED();
return;
}
-
- char old_governor[80];
- ASSERT_LE(0, read(fd1, old_governor, 80));
-
- char governors[1024];
- unsigned len = read(fd2, governors, 1024);
- ASSERT_LE(0u, len);
- governors[len] = '\0';
-
- char *saveptr;
- char *name = strtok_r(governors, " \n", &saveptr);
- while (name) {
- ASSERT_LE(0, write(fd1, name, strlen(name)));
+ auto all_governors = android::base::Split(governors, " \n");
+ for (const auto &governor : all_governors) {
+ ASSERT_TRUE(android::base::WriteStringToFile(governor, CPU_GOVERNOR_PATH));
ret = power->setInteractive(true);
ASSERT_TRUE(ret.isOk());
@@ -104,11 +96,9 @@
power->powerHint(PowerHint::LAUNCH, 1);
power->powerHint(PowerHint::LAUNCH, 0);
-
- name = strtok_r(NULL, " \n", &saveptr);
}
- ASSERT_LE(0, write(fd1, old_governor, strlen(old_governor)));
+ ASSERT_TRUE(android::base::WriteStringToFile(old_governor, CPU_GOVERNOR_PATH));
}
// Sanity check Power::powerHint on good and bad inputs.
diff --git a/radio/1.0/vts/functional/vts_hal_radio_target_test.xml b/radio/1.0/vts/functional/vts_hal_radio_target_test.xml
index 5e4a1cd..b91119d 100644
--- a/radio/1.0/vts/functional/vts_hal_radio_target_test.xml
+++ b/radio/1.0/vts/functional/vts_hal_radio_target_test.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/radio/1.0/vts/functional/vts_hal_sap_target_test.xml b/radio/1.0/vts/functional/vts_hal_sap_target_test.xml
index 457d700..876e1fb 100644
--- a/radio/1.0/vts/functional/vts_hal_sap_target_test.xml
+++ b/radio/1.0/vts/functional/vts_hal_sap_target_test.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/radio/1.1/vts/functional/AndroidTest.xml b/radio/1.1/vts/functional/AndroidTest.xml
index 5badadd..3699575 100644
--- a/radio/1.1/vts/functional/AndroidTest.xml
+++ b/radio/1.1/vts/functional/AndroidTest.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/radio/1.2/vts/functional/AndroidTest.xml b/radio/1.2/vts/functional/AndroidTest.xml
index 5d92248..9904760 100644
--- a/radio/1.2/vts/functional/AndroidTest.xml
+++ b/radio/1.2/vts/functional/AndroidTest.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/radio/1.3/vts/functional/AndroidTest.xml b/radio/1.3/vts/functional/AndroidTest.xml
index c910047..9df8f9c 100644
--- a/radio/1.3/vts/functional/AndroidTest.xml
+++ b/radio/1.3/vts/functional/AndroidTest.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/radio/1.4/vts/functional/AndroidTest.xml b/radio/1.4/vts/functional/AndroidTest.xml
index c910047..9df8f9c 100644
--- a/radio/1.4/vts/functional/AndroidTest.xml
+++ b/radio/1.4/vts/functional/AndroidTest.xml
@@ -17,9 +17,10 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
+ <!-- TODO: b/154638140, b/152655658: bad interactions -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.MultiSimPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
diff --git a/secure_element/1.0/vts/OWNERS b/secure_element/1.0/vts/OWNERS
new file mode 100644
index 0000000..c7963e7
--- /dev/null
+++ b/secure_element/1.0/vts/OWNERS
@@ -0,0 +1,4 @@
+alisher@google.com
+jackcwyu@google.com
+georgekgchang@google.com
+zachoverflow@google.com
diff --git a/secure_element/1.1/vts/OWNERS b/secure_element/1.1/vts/OWNERS
new file mode 100644
index 0000000..c7963e7
--- /dev/null
+++ b/secure_element/1.1/vts/OWNERS
@@ -0,0 +1,4 @@
+alisher@google.com
+jackcwyu@google.com
+georgekgchang@google.com
+zachoverflow@google.com
diff --git a/secure_element/1.2/vts/OWNERS b/secure_element/1.2/vts/OWNERS
new file mode 100644
index 0000000..c7963e7
--- /dev/null
+++ b/secure_element/1.2/vts/OWNERS
@@ -0,0 +1,4 @@
+alisher@google.com
+jackcwyu@google.com
+georgekgchang@google.com
+zachoverflow@google.com
diff --git a/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp b/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
index 98e4502..9392f14 100644
--- a/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
+++ b/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
@@ -85,6 +85,7 @@
* Reset:
* Calls reset()
* Checks status
+ * Check onStateChange is received with connected state set to false
* Check onStateChange is received with connected state set to true
*/
TEST_P(SecureElementHidlTest, Reset) {
@@ -92,6 +93,10 @@
auto res = se_cb_->WaitForCallback(kCallbackNameOnStateChange);
EXPECT_TRUE(res.no_timeout);
+ EXPECT_FALSE(res.args->state_);
+
+ res = se_cb_->WaitForCallback(kCallbackNameOnStateChange);
+ EXPECT_TRUE(res.no_timeout);
EXPECT_TRUE(res.args->state_);
}
diff --git a/sensors/2.0/multihal/Android.bp b/sensors/2.0/multihal/Android.bp
index 3ce3390..bf51fcd 100644
--- a/sensors/2.0/multihal/Android.bp
+++ b/sensors/2.0/multihal/Android.bp
@@ -25,6 +25,9 @@
],
init_rc: ["android.hardware.sensors@2.0-service-multihal.rc"],
vintf_fragments: ["android.hardware.sensors@2.0-multihal.xml"],
+ header_libs: [
+ "android.hardware.sensors@2.X-shared-utils",
+ ],
shared_libs: [
"android.hardware.sensors@2.0",
"android.hardware.sensors@2.0-ScopedWakelock",
@@ -37,5 +40,8 @@
"libpower",
"libutils",
],
- static_libs: ["android.hardware.sensors@2.X-multihal"],
+ static_libs: [
+ "android.hardware.sensors@1.0-convert",
+ "android.hardware.sensors@2.X-multihal",
+ ],
}
diff --git a/sensors/2.0/multihal/service.cpp b/sensors/2.0/multihal/service.cpp
index ef77048..f50ad7e 100644
--- a/sensors/2.0/multihal/service.cpp
+++ b/sensors/2.0/multihal/service.cpp
@@ -23,12 +23,12 @@
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::hardware::sensors::V2_0::ISensors;
-using android::hardware::sensors::V2_0::implementation::HalProxy;
+using android::hardware::sensors::V2_1::implementation::HalProxyV2_0;
int main(int /* argc */, char** /* argv */) {
configureRpcThreadpool(1, true);
- android::sp<ISensors> halProxy = new HalProxy();
+ android::sp<ISensors> halProxy = new HalProxyV2_0();
if (halProxy->registerAsService() != ::android::OK) {
ALOGE("Failed to register Sensors HAL instance");
return -1;
diff --git a/sensors/2.1/multihal/Android.bp b/sensors/2.1/multihal/Android.bp
new file mode 100644
index 0000000..6a7cac9
--- /dev/null
+++ b/sensors/2.1/multihal/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.sensors@2.1-service.multihal",
+ defaults: [
+ "hidl_defaults",
+ ],
+ vendor: true,
+ relative_install_path: "hw",
+ srcs: [
+ "service.cpp",
+ ],
+ init_rc: ["android.hardware.sensors@2.1-service-multihal.rc"],
+ vintf_fragments: ["android.hardware.sensors@2.1-multihal.xml"],
+ header_libs: [
+ "android.hardware.sensors@2.X-shared-utils",
+ ],
+ shared_libs: [
+ "android.hardware.sensors@2.0",
+ "android.hardware.sensors@2.0-ScopedWakelock",
+ "android.hardware.sensors@2.1",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "liblog",
+ "libpower",
+ "libutils",
+ ],
+ static_libs: [
+ "android.hardware.sensors@1.0-convert",
+ "android.hardware.sensors@2.X-multihal",
+ ],
+}
diff --git a/sensors/2.1/multihal/OWNERS b/sensors/2.1/multihal/OWNERS
new file mode 100644
index 0000000..e955670
--- /dev/null
+++ b/sensors/2.1/multihal/OWNERS
@@ -0,0 +1,3 @@
+arthuri@google.com
+bduddie@google.com
+stange@google.com
\ No newline at end of file
diff --git a/sensors/2.1/multihal/android.hardware.sensors@2.1-multihal.xml b/sensors/2.1/multihal/android.hardware.sensors@2.1-multihal.xml
new file mode 100644
index 0000000..18bd3ae
--- /dev/null
+++ b/sensors/2.1/multihal/android.hardware.sensors@2.1-multihal.xml
@@ -0,0 +1,11 @@
+<manifest version="1.0" type="device">
+ <hal format="hidl">
+ <name>android.hardware.sensors</name>
+ <transport>hwbinder</transport>
+ <version>2.1</version>
+ <interface>
+ <name>ISensors</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc b/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc
new file mode 100644
index 0000000..fc99ee7
--- /dev/null
+++ b/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc
@@ -0,0 +1,7 @@
+service vendor.sensors-hal-2-1-multihal /vendor/bin/hw/android.hardware.sensors@2.1-service.multihal
+ class hal
+ user system
+ group system wakelock context_hub
+ writepid /dev/cpuset/system-background/tasks
+ capabilities BLOCK_SUSPEND
+ rlimit rtprio 10 10
diff --git a/sensors/2.1/multihal/service.cpp b/sensors/2.1/multihal/service.cpp
new file mode 100644
index 0000000..d68d9a3
--- /dev/null
+++ b/sensors/2.1/multihal/service.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/hardware/sensors/2.1/ISensors.h>
+#include <hidl/HidlTransportSupport.h>
+#include <log/log.h>
+#include <utils/StrongPointer.h>
+#include "HalProxy.h"
+
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+using android::hardware::sensors::V2_1::ISensors;
+using android::hardware::sensors::V2_1::implementation::HalProxyV2_1;
+
+int main(int /* argc */, char** /* argv */) {
+ configureRpcThreadpool(1, true);
+
+ android::sp<ISensors> halProxy = new HalProxyV2_1();
+ if (halProxy->registerAsService() != ::android::OK) {
+ ALOGE("Failed to register Sensors HAL instance");
+ return -1;
+ }
+
+ joinRpcThreadpool();
+ return 1; // joinRpcThreadpool shouldn't exit
+}
diff --git a/sensors/common/default/2.X/multihal/Android.bp b/sensors/common/default/2.X/multihal/Android.bp
index 6122323..c80c47a 100644
--- a/sensors/common/default/2.X/multihal/Android.bp
+++ b/sensors/common/default/2.X/multihal/Android.bp
@@ -17,6 +17,7 @@
name: "android.hardware.sensors@2.X-multihal-defaults",
header_libs: [
"android.hardware.sensors@2.X-multihal.header",
+ "android.hardware.sensors@2.X-shared-utils",
],
shared_libs: [
"android.hardware.sensors@1.0",
@@ -30,6 +31,9 @@
"libpower",
"libutils",
],
+ static_libs: [
+ "android.hardware.sensors@1.0-convert",
+ ],
cflags: ["-DLOG_TAG=\"SensorsMultiHal\""],
}
@@ -62,6 +66,7 @@
],
srcs: [
"HalProxy.cpp",
+ "HalProxyCallback.cpp",
],
vendor_available: true,
export_header_lib_headers: [
diff --git a/sensors/common/default/2.X/multihal/HalProxy.cpp b/sensors/common/default/2.X/multihal/HalProxy.cpp
index 869c033..75ffc17 100644
--- a/sensors/common/default/2.X/multihal/HalProxy.cpp
+++ b/sensors/common/default/2.X/multihal/HalProxy.cpp
@@ -32,15 +32,17 @@
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace implementation {
+using ::android::hardware::sensors::V1_0::Result;
using ::android::hardware::sensors::V2_0::EventQueueFlagBits;
using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
using ::android::hardware::sensors::V2_0::implementation::getTimeNow;
using ::android::hardware::sensors::V2_0::implementation::kWakelockTimeoutNs;
-typedef ISensorsSubHal*(SensorsHalGetSubHalFunc)(uint32_t*);
+typedef V2_0::implementation::ISensorsSubHal*(SensorsHalGetSubHalFunc)(uint32_t*);
+typedef V2_1::implementation::ISensorsSubHal*(SensorsHalGetSubHalV2_1Func)(uint32_t*);
static constexpr int32_t kBitsAfterSubHalIndex = 24;
@@ -85,7 +87,24 @@
init();
}
-HalProxy::HalProxy(std::vector<ISensorsSubHal*>& subHalList) : mSubHalList(subHalList) {
+HalProxy::HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList) {
+ for (ISensorsSubHalV2_0* subHal : subHalList) {
+ mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
+ }
+
+ init();
+}
+
+HalProxy::HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList,
+ std::vector<ISensorsSubHalV2_1*>& subHalListV2_1) {
+ for (ISensorsSubHalV2_0* subHal : subHalList) {
+ mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
+ }
+
+ for (ISensorsSubHalV2_1* subHal : subHalListV2_1) {
+ mSubHalList.push_back(std::make_unique<SubHalWrapperV2_1>(subHal));
+ }
+
init();
}
@@ -93,8 +112,8 @@
stopThreads();
}
-Return<void> HalProxy::getSensorsList(getSensorsList_cb _hidl_cb) {
- std::vector<SensorInfo> sensors;
+Return<void> HalProxy::getSensorsList_2_1(ISensorsV2_1::getSensorsList_2_1_cb _hidl_cb) {
+ std::vector<V2_1::SensorInfo> sensors;
for (const auto& iter : mSensors) {
sensors.push_back(iter.second);
}
@@ -102,22 +121,31 @@
return Void();
}
+Return<void> HalProxy::getSensorsList(ISensorsV2_0::getSensorsList_cb _hidl_cb) {
+ std::vector<V1_0::SensorInfo> sensors;
+ for (const auto& iter : mSensors) {
+ sensors.push_back(convertToOldSensorInfo(iter.second));
+ }
+ _hidl_cb(sensors);
+ return Void();
+}
+
Return<Result> HalProxy::setOperationMode(OperationMode mode) {
Result result = Result::OK;
size_t subHalIndex;
for (subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) {
- ISensorsSubHal* subHal = mSubHalList[subHalIndex];
- result = subHal->setOperationMode(mode);
+ result = mSubHalList[subHalIndex]->setOperationMode(mode);
if (result != Result::OK) {
- ALOGE("setOperationMode failed for SubHal: %s", subHal->getName().c_str());
+ ALOGE("setOperationMode failed for SubHal: %s",
+ mSubHalList[subHalIndex]->getName().c_str());
break;
}
}
+
if (result != Result::OK) {
// Reset the subhal operation modes that have been flipped
for (size_t i = 0; i < subHalIndex; i++) {
- ISensorsSubHal* subHal = mSubHalList[i];
- subHal->setOperationMode(mCurrentOperationMode);
+ mSubHalList[i]->setOperationMode(mCurrentOperationMode);
}
} else {
mCurrentOperationMode = mode;
@@ -133,10 +161,42 @@
->activate(clearSubHalIndex(sensorHandle), enabled);
}
-Return<Result> HalProxy::initialize(
- const ::android::hardware::MQDescriptorSync<Event>& eventQueueDescriptor,
+Return<Result> HalProxy::initialize_2_1(
+ const ::android::hardware::MQDescriptorSync<V2_1::Event>& eventQueueDescriptor,
const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
- const sp<ISensorsCallback>& sensorsCallback) {
+ const sp<V2_1::ISensorsCallback>& sensorsCallback) {
+ sp<ISensorsCallbackWrapperBase> dynamicCallback =
+ new ISensorsCallbackWrapperV2_1(sensorsCallback);
+
+ // Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
+ auto eventQueue =
+ std::make_unique<EventMessageQueueV2_1>(eventQueueDescriptor, true /* resetPointers */);
+ std::unique_ptr<EventMessageQueueWrapperBase> queue =
+ std::make_unique<EventMessageQueueWrapperV2_1>(eventQueue);
+
+ return initializeCommon(queue, wakeLockDescriptor, dynamicCallback);
+}
+
+Return<Result> HalProxy::initialize(
+ const ::android::hardware::MQDescriptorSync<V1_0::Event>& eventQueueDescriptor,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<V2_0::ISensorsCallback>& sensorsCallback) {
+ sp<ISensorsCallbackWrapperBase> dynamicCallback =
+ new ISensorsCallbackWrapperV2_0(sensorsCallback);
+
+ // Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
+ auto eventQueue =
+ std::make_unique<EventMessageQueueV2_0>(eventQueueDescriptor, true /* resetPointers */);
+ std::unique_ptr<EventMessageQueueWrapperBase> queue =
+ std::make_unique<EventMessageQueueWrapperV1_0>(eventQueue);
+
+ return initializeCommon(queue, wakeLockDescriptor, dynamicCallback);
+}
+
+Return<Result> HalProxy::initializeCommon(
+ std::unique_ptr<EventMessageQueueWrapperBase>& eventQueue,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<ISensorsCallbackWrapperBase>& sensorsCallback) {
Result result = Result::OK;
stopThreads();
@@ -147,7 +207,7 @@
disableAllSensors();
// Clears the queue if any events were pending write before.
- mPendingWriteEventsQueue = std::queue<std::pair<std::vector<Event>, size_t>>();
+ mPendingWriteEventsQueue = std::queue<std::pair<std::vector<V2_1::Event>, size_t>>();
mSizePendingWriteEventsQueue = 0;
// Clears previously connected dynamic sensors
@@ -156,8 +216,7 @@
mDynamicSensorsCallback = sensorsCallback;
// Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
- mEventQueue =
- std::make_unique<EventMessageQueue>(eventQueueDescriptor, true /* resetPointers */);
+ mEventQueue = std::move(eventQueue);
// Create the Wake Lock FMQ that is used by the framework to communicate whenever WAKE_UP
// events have been successfully read and handled by the framework.
@@ -186,12 +245,10 @@
mWakelockThread = std::thread(startWakelockThread, this);
for (size_t i = 0; i < mSubHalList.size(); i++) {
- auto subHal = mSubHalList[i];
- const auto& subHalCallback = mSubHalCallbacks[i];
- Result currRes = subHal->initialize(subHalCallback);
+ Result currRes = mSubHalList[i]->initialize(this, this, i);
if (currRes != Result::OK) {
result = currRes;
- ALOGE("Subhal '%s' failed to initialize.", subHal->getName().c_str());
+ ALOGE("Subhal '%s' failed to initialize.", mSubHalList[i]->getName().c_str());
break;
}
}
@@ -217,7 +274,11 @@
return getSubHalForSensorHandle(sensorHandle)->flush(clearSubHalIndex(sensorHandle));
}
-Return<Result> HalProxy::injectSensorData(const Event& event) {
+Return<Result> HalProxy::injectSensorData_2_1(const V2_1::Event& event) {
+ return injectSensorData(convertToOldEvent(event));
+}
+
+Return<Result> HalProxy::injectSensorData(const V1_0::Event& event) {
Result result = Result::OK;
if (mCurrentOperationMode == OperationMode::NORMAL &&
event.sensorType != V1_0::SensorType::ADDITIONAL_INFO) {
@@ -226,18 +287,19 @@
result = Result::BAD_VALUE;
}
if (result == Result::OK) {
- Event subHalEvent = event;
+ V1_0::Event subHalEvent = event;
if (!isSubHalIndexValid(event.sensorHandle)) {
return Result::BAD_VALUE;
}
subHalEvent.sensorHandle = clearSubHalIndex(event.sensorHandle);
- result = getSubHalForSensorHandle(event.sensorHandle)->injectSensorData(subHalEvent);
+ result = getSubHalForSensorHandle(event.sensorHandle)
+ ->injectSensorData(convertToNewEvent(subHalEvent));
}
return result;
}
Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& mem,
- registerDirectChannel_cb _hidl_cb) {
+ ISensorsV2_0::registerDirectChannel_cb _hidl_cb) {
if (mDirectChannelSubHal == nullptr) {
_hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */);
} else {
@@ -257,7 +319,8 @@
}
Return<void> HalProxy::configDirectReport(int32_t sensorHandle, int32_t channelHandle,
- RateLevel rate, configDirectReport_cb _hidl_cb) {
+ RateLevel rate,
+ ISensorsV2_0::configDirectReport_cb _hidl_cb) {
if (mDirectChannelSubHal == nullptr) {
_hidl_cb(Result::INVALID_OPERATION, -1 /* reportToken */);
} else if (sensorHandle == -1 && rate != RateLevel::STOP) {
@@ -302,7 +365,7 @@
stream << " # of non-dynamic sensors across all subhals: " << mSensors.size() << std::endl;
stream << " # of dynamic sensors across all subhals: " << mDynamicSensors.size() << std::endl;
stream << "SubHals (" << mSubHalList.size() << "):" << std::endl;
- for (ISensorsSubHal* subHal : mSubHalList) {
+ for (auto& subHal : mSubHalList) {
stream << " Name: " << subHal->getName() << std::endl;
stream << " Debug dump: " << std::endl;
android::base::WriteStringToFd(stream.str(), writeFd);
@@ -363,26 +426,43 @@
} else {
std::string subHalLibraryFile;
while (subHalConfigStream >> subHalLibraryFile) {
- void* handle = dlopen(subHalLibraryFile.c_str(), RTLD_NOW);
+ void* handle = getHandleForSubHalSharedObject(subHalLibraryFile);
if (handle == nullptr) {
ALOGE("dlopen failed for library: %s", subHalLibraryFile.c_str());
} else {
SensorsHalGetSubHalFunc* sensorsHalGetSubHalPtr =
(SensorsHalGetSubHalFunc*)dlsym(handle, "sensorsHalGetSubHal");
- if (sensorsHalGetSubHalPtr == nullptr) {
- ALOGE("Failed to locate sensorsHalGetSubHal function for library: %s",
- subHalLibraryFile.c_str());
- } else {
+ if (sensorsHalGetSubHalPtr != nullptr) {
std::function<SensorsHalGetSubHalFunc> sensorsHalGetSubHal =
*sensorsHalGetSubHalPtr;
uint32_t version;
- ISensorsSubHal* subHal = sensorsHalGetSubHal(&version);
+ ISensorsSubHalV2_0* subHal = sensorsHalGetSubHal(&version);
if (version != SUB_HAL_2_0_VERSION) {
ALOGE("SubHal version was not 2.0 for library: %s",
subHalLibraryFile.c_str());
} else {
ALOGV("Loaded SubHal from library: %s", subHalLibraryFile.c_str());
- mSubHalList.push_back(subHal);
+ mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
+ }
+ } else {
+ SensorsHalGetSubHalV2_1Func* getSubHalV2_1Ptr =
+ (SensorsHalGetSubHalV2_1Func*)dlsym(handle, "sensorsHalGetSubHal_2_1");
+
+ if (getSubHalV2_1Ptr == nullptr) {
+ ALOGE("Failed to locate sensorsHalGetSubHal function for library: %s",
+ subHalLibraryFile.c_str());
+ } else {
+ std::function<SensorsHalGetSubHalV2_1Func> sensorsHalGetSubHal_2_1 =
+ *getSubHalV2_1Ptr;
+ uint32_t version;
+ ISensorsSubHalV2_1* subHal = sensorsHalGetSubHal_2_1(&version);
+ if (version != SUB_HAL_2_1_VERSION) {
+ ALOGE("SubHal version was not 2.1 for library: %s",
+ subHalLibraryFile.c_str());
+ } else {
+ ALOGV("Loaded SubHal from library: %s", subHalLibraryFile.c_str());
+ mSubHalList.push_back(std::make_unique<SubHalWrapperV2_1>(subHal));
+ }
}
}
}
@@ -390,36 +470,47 @@
}
}
-void HalProxy::initializeSubHalCallbacks() {
- for (size_t subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) {
- sp<IHalProxyCallback> callback = new HalProxyCallback(this, subHalIndex);
- mSubHalCallbacks.push_back(callback);
- }
-}
-
void HalProxy::initializeSensorList() {
for (size_t subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) {
- ISensorsSubHal* subHal = mSubHalList[subHalIndex];
- auto result = subHal->getSensorsList([&](const auto& list) {
+ auto result = mSubHalList[subHalIndex]->getSensorsList([&](const auto& list) {
for (SensorInfo sensor : list) {
if (!subHalIndexIsClear(sensor.sensorHandle)) {
ALOGE("SubHal sensorHandle's first byte was not 0");
} else {
ALOGV("Loaded sensor: %s", sensor.name.c_str());
sensor.sensorHandle = setSubHalIndex(sensor.sensorHandle, subHalIndex);
- setDirectChannelFlags(&sensor, subHal);
+ setDirectChannelFlags(&sensor, mSubHalList[subHalIndex]);
mSensors[sensor.sensorHandle] = sensor;
}
}
});
if (!result.isOk()) {
- ALOGE("getSensorsList call failed for SubHal: %s", subHal->getName().c_str());
+ ALOGE("getSensorsList call failed for SubHal: %s",
+ mSubHalList[subHalIndex]->getName().c_str());
}
}
}
+void* HalProxy::getHandleForSubHalSharedObject(const std::string& filename) {
+ static const std::string kSubHalShareObjectLocations[] = {
+ "", // Default locations will be searched
+#ifdef __LP64__
+ "/vendor/lib64/hw/", "/odm/lib64/hw/"
+#else
+ "/vendor/lib/hw/", "/odm/lib/hw/"
+#endif
+ };
+
+ for (const std::string& dir : kSubHalShareObjectLocations) {
+ void* handle = dlopen((dir + filename).c_str(), RTLD_NOW);
+ if (handle != nullptr) {
+ return handle;
+ }
+ }
+ return nullptr;
+}
+
void HalProxy::init() {
- initializeSubHalCallbacks();
initializeSensorList();
}
@@ -552,7 +643,7 @@
}
void HalProxy::postEventsToMessageQueue(const std::vector<Event>& events, size_t numWakeupEvents,
- ScopedWakelock wakelock) {
+ V2_0::implementation::ScopedWakelock wakelock) {
size_t numToWrite = 0;
std::lock_guard<std::mutex> lock(mEventQueueWriteMutex);
if (wakelock.isLocked()) {
@@ -610,7 +701,8 @@
}
}
-void HalProxy::setDirectChannelFlags(SensorInfo* sensorInfo, ISensorsSubHal* subHal) {
+void HalProxy::setDirectChannelFlags(SensorInfo* sensorInfo,
+ std::shared_ptr<ISubHalWrapperBase> subHal) {
bool sensorSupportsDirectChannel =
(sensorInfo->flags & (V1_0::SensorFlagBits::MASK_DIRECT_REPORT |
V1_0::SensorFlagBits::MASK_DIRECT_CHANNEL)) != 0;
@@ -624,7 +716,7 @@
}
}
-ISensorsSubHal* HalProxy::getSubHalForSensorHandle(int32_t sensorHandle) {
+std::shared_ptr<ISubHalWrapperBase> HalProxy::getSubHalForSensorHandle(int32_t sensorHandle) {
return mSubHalList[extractSubHalIndex(sensorHandle)];
}
@@ -651,46 +743,8 @@
return (sensorHandle & kSensorHandleSubHalIndexMask) == 0;
}
-void HalProxyCallback::postEvents(const std::vector<Event>& events, ScopedWakelock wakelock) {
- if (events.empty() || !mHalProxy->areThreadsRunning()) return;
- size_t numWakeupEvents;
- std::vector<Event> processedEvents = processEvents(events, &numWakeupEvents);
- if (numWakeupEvents > 0) {
- ALOG_ASSERT(wakelock.isLocked(),
- "Wakeup events posted while wakelock unlocked for subhal"
- " w/ index %" PRId32 ".",
- mSubHalIndex);
- } else {
- ALOG_ASSERT(!wakelock.isLocked(),
- "No Wakeup events posted but wakelock locked for subhal"
- " w/ index %" PRId32 ".",
- mSubHalIndex);
- }
- mHalProxy->postEventsToMessageQueue(processedEvents, numWakeupEvents, std::move(wakelock));
-}
-
-ScopedWakelock HalProxyCallback::createScopedWakelock(bool lock) {
- ScopedWakelock wakelock(mHalProxy, lock);
- return wakelock;
-}
-
-std::vector<Event> HalProxyCallback::processEvents(const std::vector<Event>& events,
- size_t* numWakeupEvents) const {
- *numWakeupEvents = 0;
- std::vector<Event> eventsOut;
- for (Event event : events) {
- event.sensorHandle = setSubHalIndex(event.sensorHandle, mSubHalIndex);
- eventsOut.push_back(event);
- const SensorInfo& sensor = mHalProxy->getSensorInfo(event.sensorHandle);
- if ((sensor.flags & V1_0::SensorFlagBits::WAKE_UP) != 0) {
- (*numWakeupEvents)++;
- }
- }
- return eventsOut;
-}
-
} // namespace implementation
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/default/2.X/multihal/HalProxyCallback.cpp b/sensors/common/default/2.X/multihal/HalProxyCallback.cpp
new file mode 100644
index 0000000..3c1b17c
--- /dev/null
+++ b/sensors/common/default/2.X/multihal/HalProxyCallback.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HalProxyCallback.h"
+
+#include <cinttypes>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace V2_0 {
+namespace implementation {
+
+static constexpr int32_t kBitsAfterSubHalIndex = 24;
+
+/**
+ * Set the subhal index as first byte of sensor handle and return this modified version.
+ *
+ * @param sensorHandle The sensor handle to modify.
+ * @param subHalIndex The index in the hal proxy of the sub hal this sensor belongs to.
+ *
+ * @return The modified sensor handle.
+ */
+int32_t setSubHalIndex(int32_t sensorHandle, size_t subHalIndex) {
+ return sensorHandle | (static_cast<int32_t>(subHalIndex) << kBitsAfterSubHalIndex);
+}
+
+void HalProxyCallbackBase::postEvents(const std::vector<V2_1::Event>& events,
+ ScopedWakelock wakelock) {
+ if (events.empty() || !mCallback->areThreadsRunning()) return;
+ size_t numWakeupEvents;
+ std::vector<V2_1::Event> processedEvents = processEvents(events, &numWakeupEvents);
+ if (numWakeupEvents > 0) {
+ ALOG_ASSERT(wakelock.isLocked(),
+ "Wakeup events posted while wakelock unlocked for subhal"
+ " w/ index %" PRId32 ".",
+ mSubHalIndex);
+ } else {
+ ALOG_ASSERT(!wakelock.isLocked(),
+ "No Wakeup events posted but wakelock locked for subhal"
+ " w/ index %" PRId32 ".",
+ mSubHalIndex);
+ }
+ mCallback->postEventsToMessageQueue(processedEvents, numWakeupEvents, std::move(wakelock));
+}
+
+ScopedWakelock HalProxyCallbackBase::createScopedWakelock(bool lock) {
+ ScopedWakelock wakelock(mRefCounter, lock);
+ return wakelock;
+}
+
+std::vector<V2_1::Event> HalProxyCallbackBase::processEvents(const std::vector<V2_1::Event>& events,
+ size_t* numWakeupEvents) const {
+ *numWakeupEvents = 0;
+ std::vector<V2_1::Event> eventsOut;
+ for (V2_1::Event event : events) {
+ event.sensorHandle = setSubHalIndex(event.sensorHandle, mSubHalIndex);
+ eventsOut.push_back(event);
+ const V2_1::SensorInfo& sensor = mCallback->getSensorInfo(event.sensorHandle);
+ if ((sensor.flags & V1_0::SensorFlagBits::WAKE_UP) != 0) {
+ (*numWakeupEvents)++;
+ }
+ }
+ return eventsOut;
+}
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace sensors
+} // namespace hardware
+} // namespace android
diff --git a/sensors/common/default/2.X/multihal/include/HalProxy.h b/sensors/common/default/2.X/multihal/include/HalProxy.h
index d7e8795..35d7c8b 100644
--- a/sensors/common/default/2.X/multihal/include/HalProxy.h
+++ b/sensors/common/default/2.X/multihal/include/HalProxy.h
@@ -16,12 +16,17 @@
#pragma once
+#include "EventMessageQueueWrapper.h"
+#include "HalProxyCallback.h"
+#include "ISensorsCallbackWrapper.h"
+#include "SubHalWrapper.h"
#include "V2_0/ScopedWakelock.h"
#include "V2_0/SubHal.h"
#include "V2_1/SubHal.h"
+#include "convertV2_1.h"
-#include <android/hardware/sensors/2.0/ISensors.h>
-#include <android/hardware/sensors/2.0/types.h>
+#include <android/hardware/sensors/2.1/ISensors.h>
+#include <android/hardware/sensors/2.1/types.h>
#include <fmq/MessageQueue.h>
#include <hardware_legacy/power.h>
#include <hidl/MQDescriptor.h>
@@ -38,96 +43,97 @@
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace implementation {
-using ::android::sp;
-using ::android::hardware::EventFlag;
-using ::android::hardware::hidl_string;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::MessageQueue;
-using ::android::hardware::MQDescriptor;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-
-class HalProxy : public ISensors, public IScopedWakelockRefCounter {
+/**
+ * HalProxy is the main interface for Multi-HAL. It is responsible for managing subHALs and
+ * proxying function calls to/from the subHAL APIs from the sensors framework. It also manages any
+ * wakelocks allocated through the IHalProxyCallback and manages posting events to the sensors
+ * framework.
+ */
+class HalProxy : public V2_0::implementation::IScopedWakelockRefCounter,
+ public V2_0::implementation::ISubHalCallback {
public:
- using Event = ::android::hardware::sensors::V1_0::Event;
+ using Event = ::android::hardware::sensors::V2_1::Event;
using OperationMode = ::android::hardware::sensors::V1_0::OperationMode;
using RateLevel = ::android::hardware::sensors::V1_0::RateLevel;
using Result = ::android::hardware::sensors::V1_0::Result;
- using SensorInfo = ::android::hardware::sensors::V1_0::SensorInfo;
+ using SensorInfo = ::android::hardware::sensors::V2_1::SensorInfo;
using SharedMemInfo = ::android::hardware::sensors::V1_0::SharedMemInfo;
- using ISensorsSubHal = ::android::hardware::sensors::V2_0::implementation::ISensorsSubHal;
+ using IHalProxyCallbackV2_0 = V2_0::implementation::IHalProxyCallback;
+ using IHalProxyCallbackV2_1 = V2_1::implementation::IHalProxyCallback;
+ using ISensorsSubHalV2_0 = V2_0::implementation::ISensorsSubHal;
+ using ISensorsSubHalV2_1 = V2_1::implementation::ISensorsSubHal;
+ using ISensorsV2_0 = V2_0::ISensors;
+ using ISensorsV2_1 = V2_1::ISensors;
+ using HalProxyCallbackBase = V2_0::implementation::HalProxyCallbackBase;
explicit HalProxy();
// Test only constructor.
- explicit HalProxy(std::vector<ISensorsSubHal*>& subHalList);
+ explicit HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList);
+ explicit HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList,
+ std::vector<ISensorsSubHalV2_1*>& subHalListV2_1);
~HalProxy();
+ // Methods from ::android::hardware::sensors::V2_1::ISensors follow.
+ Return<void> getSensorsList_2_1(ISensorsV2_1::getSensorsList_2_1_cb _hidl_cb);
+
+ Return<Result> initialize_2_1(
+ const ::android::hardware::MQDescriptorSync<V2_1::Event>& eventQueueDescriptor,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<V2_1::ISensorsCallback>& sensorsCallback);
+
+ Return<Result> injectSensorData_2_1(const Event& event);
+
// Methods from ::android::hardware::sensors::V2_0::ISensors follow.
- Return<void> getSensorsList(getSensorsList_cb _hidl_cb) override;
+ Return<void> getSensorsList(ISensorsV2_0::getSensorsList_cb _hidl_cb);
- Return<Result> setOperationMode(OperationMode mode) override;
+ Return<Result> setOperationMode(OperationMode mode);
- Return<Result> activate(int32_t sensorHandle, bool enabled) override;
+ Return<Result> activate(int32_t sensorHandle, bool enabled);
Return<Result> initialize(
- const ::android::hardware::MQDescriptorSync<Event>& eventQueueDescriptor,
+ const ::android::hardware::MQDescriptorSync<V1_0::Event>& eventQueueDescriptor,
const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
- const sp<ISensorsCallback>& sensorsCallback) override;
+ const sp<V2_0::ISensorsCallback>& sensorsCallback);
+
+ Return<Result> initializeCommon(
+ std::unique_ptr<EventMessageQueueWrapperBase>& eventQueue,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<ISensorsCallbackWrapperBase>& sensorsCallback);
Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
- int64_t maxReportLatencyNs) override;
+ int64_t maxReportLatencyNs);
- Return<Result> flush(int32_t sensorHandle) override;
+ Return<Result> flush(int32_t sensorHandle);
- Return<Result> injectSensorData(const Event& event) override;
+ Return<Result> injectSensorData(const V1_0::Event& event);
Return<void> registerDirectChannel(const SharedMemInfo& mem,
- registerDirectChannel_cb _hidl_cb) override;
+ ISensorsV2_0::registerDirectChannel_cb _hidl_cb);
- Return<Result> unregisterDirectChannel(int32_t channelHandle) override;
+ Return<Result> unregisterDirectChannel(int32_t channelHandle);
Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle, RateLevel rate,
- configDirectReport_cb _hidl_cb) override;
+ ISensorsV2_0::configDirectReport_cb _hidl_cb);
- Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) override;
+ Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args);
- // Below methods from ::android::hardware::sensors::V2_0::ISensorsCallback with a minor change
- // to pass in the sub-HAL index. While the above methods are invoked from the sensors framework
- // via the binder, these methods are invoked from a callback provided to sub-HALs inside the
- // same process as the HalProxy, but potentially running on different threads.
Return<void> onDynamicSensorsConnected(const hidl_vec<SensorInfo>& dynamicSensorsAdded,
- int32_t subHalIndex);
+ int32_t subHalIndex) override;
Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& dynamicSensorHandlesRemoved,
- int32_t subHalIndex);
+ int32_t subHalIndex) override;
- // Below methods are for HalProxyCallback
-
- /**
- * Post events to the event message queue if there is room to write them. Otherwise post the
- * remaining events to a background thread for a blocking write with a kPendingWriteTimeoutNs
- * timeout.
- *
- * @param events The list of events to post to the message queue.
- * @param numWakeupEvents The number of wakeup events in events.
- * @param wakelock The wakelock associated with this post of events.
- */
void postEventsToMessageQueue(const std::vector<Event>& events, size_t numWakeupEvents,
- ScopedWakelock wakelock);
+ V2_0::implementation::ScopedWakelock wakelock) override;
- /**
- * Get the sensor info associated with that sensorHandle.
- *
- * @param sensorHandle The sensor handle.
- *
- * @return The sensor info object in the mapping.
- */
- const SensorInfo& getSensorInfo(int32_t sensorHandle) { return mSensors[sensorHandle]; }
+ const SensorInfo& getSensorInfo(int32_t sensorHandle) override {
+ return mSensors[sensorHandle];
+ }
- bool areThreadsRunning() { return mThreadsRun.load(); }
+ bool areThreadsRunning() override { return mThreadsRun.load(); }
// Below methods are from IScopedWakelockRefCounter interface
bool incrementRefCountAndMaybeAcquireWakelock(size_t delta,
@@ -136,13 +142,14 @@
void decrementRefCountAndMaybeReleaseWakelock(size_t delta, int64_t timeoutStart = -1) override;
private:
- using EventMessageQueue = MessageQueue<Event, kSynchronizedReadWrite>;
+ using EventMessageQueueV2_1 = MessageQueue<V2_1::Event, kSynchronizedReadWrite>;
+ using EventMessageQueueV2_0 = MessageQueue<V1_0::Event, kSynchronizedReadWrite>;
using WakeLockMessageQueue = MessageQueue<uint32_t, kSynchronizedReadWrite>;
/**
* The Event FMQ where sensor events are written
*/
- std::unique_ptr<EventMessageQueue> mEventQueue;
+ std::unique_ptr<EventMessageQueueWrapperBase> mEventQueue;
/**
* The Wake Lock FMQ that is read to determine when the framework has handled WAKE_UP events
@@ -161,15 +168,12 @@
/**
* Callback to the sensors framework to inform it that new sensors have been added or removed.
*/
- sp<ISensorsCallback> mDynamicSensorsCallback;
+ sp<ISensorsCallbackWrapperBase> mDynamicSensorsCallback;
/**
- * SubHal object pointers that have been saved from vendor dynamic libraries.
+ * SubHal objects that have been saved from vendor dynamic libraries.
*/
- std::vector<ISensorsSubHal*> mSubHalList;
-
- //! The list of subhal callbacks for each subhal where the indices correlate with mSubHalList
- std::vector<const sp<IHalProxyCallback>> mSubHalCallbacks;
+ std::vector<std::shared_ptr<ISubHalWrapperBase>> mSubHalList;
/**
* Map of sensor handles to SensorInfo objects that contains the sensor info from subhals as
@@ -187,7 +191,7 @@
OperationMode mCurrentOperationMode = OperationMode::NORMAL;
//! The single subHal that supports directChannel reporting.
- ISensorsSubHal* mDirectChannelSubHal = nullptr;
+ std::shared_ptr<ISubHalWrapperBase> mDirectChannelSubHal;
//! The timeout for each pending write on background thread for events.
static const int64_t kPendingWriteTimeoutNs = 5 * INT64_C(1000000000) /* 5 seconds */;
@@ -239,9 +243,9 @@
//! The refcount of how many ScopedWakelocks and pending wakeup events are active
size_t mWakelockRefCount = 0;
- int64_t mWakelockTimeoutStartTime = getTimeNow();
+ int64_t mWakelockTimeoutStartTime = V2_0::implementation::getTimeNow();
- int64_t mWakelockTimeoutResetTime = getTimeNow();
+ int64_t mWakelockTimeoutResetTime = V2_0::implementation::getTimeNow();
const char* kWakelockName = "SensorsHAL_WAKEUP";
@@ -263,6 +267,16 @@
void initializeSensorList();
/**
+ * Try using the default include directories as well as the directories defined in
+ * kSubHalShareObjectLocations to get a handle for dlsym for a subhal.
+ *
+ * @param filename The file name to search for.
+ *
+ * @return The handle or nullptr if search failed.
+ */
+ void* getHandleForSubHalSharedObject(const std::string& filename);
+
+ /**
* Calls the helper methods that all ctors use.
*/
void init();
@@ -321,7 +335,7 @@
* disabled.
* @param subHal The subhal pointer that the current sensorInfo object came from.
*/
- void setDirectChannelFlags(SensorInfo* sensorInfo, ISensorsSubHal* subHal);
+ void setDirectChannelFlags(SensorInfo* sensorInfo, std::shared_ptr<ISubHalWrapperBase> subHal);
/*
* Get the subhal pointer which can be found by indexing into the mSubHalList vector
@@ -329,7 +343,7 @@
*
* @param sensorHandle The handle used to identify a sensor in one of the subhals.
*/
- ISensorsSubHal* getSubHalForSensorHandle(int32_t sensorHandle);
+ std::shared_ptr<ISubHalWrapperBase> getSubHalForSensorHandle(int32_t sensorHandle);
/**
* Checks that sensorHandle's subhal index byte is within bounds of mSubHalList.
@@ -368,39 +382,81 @@
};
/**
- * Callback class used to provide the HalProxy with the index of which subHal is invoking
+ * Since a newer HAL can't masquerade as a older HAL, IHalProxy enables the HalProxy to be compiled
+ * either for HAL 2.0 or HAL 2.1 depending on the build configuration.
*/
-class HalProxyCallback : public IHalProxyCallback {
- using SensorInfo = ::android::hardware::sensors::V1_0::SensorInfo;
-
- public:
- HalProxyCallback(HalProxy* halProxy, int32_t subHalIndex)
- : mHalProxy(halProxy), mSubHalIndex(subHalIndex) {}
-
- Return<void> onDynamicSensorsConnected(
- const hidl_vec<SensorInfo>& dynamicSensorsAdded) override {
- return mHalProxy->onDynamicSensorsConnected(dynamicSensorsAdded, mSubHalIndex);
+template <class ISensorsVersion>
+class IHalProxy : public HalProxy, public ISensorsVersion {
+ Return<void> getSensorsList(ISensorsV2_0::getSensorsList_cb _hidl_cb) override {
+ return HalProxy::getSensorsList(_hidl_cb);
}
- Return<void> onDynamicSensorsDisconnected(
- const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override {
- return mHalProxy->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved, mSubHalIndex);
+ Return<Result> setOperationMode(OperationMode mode) override {
+ return HalProxy::setOperationMode(mode);
}
- void postEvents(const std::vector<Event>& events, ScopedWakelock wakelock);
+ Return<Result> activate(int32_t sensorHandle, bool enabled) override {
+ return HalProxy::activate(sensorHandle, enabled);
+ }
- ScopedWakelock createScopedWakelock(bool lock);
+ Return<Result> initialize(
+ const ::android::hardware::MQDescriptorSync<V1_0::Event>& eventQueueDescriptor,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<V2_0::ISensorsCallback>& sensorsCallback) override {
+ return HalProxy::initialize(eventQueueDescriptor, wakeLockDescriptor, sensorsCallback);
+ }
- private:
- HalProxy* mHalProxy;
- int32_t mSubHalIndex;
+ Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+ int64_t maxReportLatencyNs) override {
+ return HalProxy::batch(sensorHandle, samplingPeriodNs, maxReportLatencyNs);
+ }
- std::vector<Event> processEvents(const std::vector<Event>& events,
- size_t* numWakeupEvents) const;
+ Return<Result> flush(int32_t sensorHandle) override { return HalProxy::flush(sensorHandle); }
+
+ Return<Result> injectSensorData(const V1_0::Event& event) override {
+ return HalProxy::injectSensorData(event);
+ }
+
+ Return<void> registerDirectChannel(const SharedMemInfo& mem,
+ ISensorsV2_0::registerDirectChannel_cb _hidl_cb) override {
+ return HalProxy::registerDirectChannel(mem, _hidl_cb);
+ }
+
+ Return<Result> unregisterDirectChannel(int32_t channelHandle) override {
+ return HalProxy::unregisterDirectChannel(channelHandle);
+ }
+
+ Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle, RateLevel rate,
+ ISensorsV2_0::configDirectReport_cb _hidl_cb) override {
+ return HalProxy::configDirectReport(sensorHandle, channelHandle, rate, _hidl_cb);
+ }
+
+ Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) override {
+ return HalProxy::debug(fd, args);
+ }
+};
+
+class HalProxyV2_0 : public IHalProxy<V2_0::ISensors> {};
+
+class HalProxyV2_1 : public IHalProxy<V2_1::ISensors> {
+ Return<void> getSensorsList_2_1(ISensorsV2_1::getSensorsList_2_1_cb _hidl_cb) override {
+ return HalProxy::getSensorsList_2_1(_hidl_cb);
+ }
+
+ Return<Result> initialize_2_1(
+ const ::android::hardware::MQDescriptorSync<V2_1::Event>& eventQueueDescriptor,
+ const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
+ const sp<V2_1::ISensorsCallback>& sensorsCallback) override {
+ return HalProxy::initialize_2_1(eventQueueDescriptor, wakeLockDescriptor, sensorsCallback);
+ }
+
+ Return<Result> injectSensorData_2_1(const Event& event) override {
+ return HalProxy::injectSensorData_2_1(event);
+ }
};
} // namespace implementation
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/default/2.X/multihal/include/HalProxyCallback.h b/sensors/common/default/2.X/multihal/include/HalProxyCallback.h
new file mode 100644
index 0000000..e62b7d1
--- /dev/null
+++ b/sensors/common/default/2.X/multihal/include/HalProxyCallback.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "V2_0/ScopedWakelock.h"
+#include "V2_0/SubHal.h"
+#include "V2_1/SubHal.h"
+#include "convertV2_1.h"
+
+#include <android/hardware/sensors/2.1/ISensors.h>
+#include <android/hardware/sensors/2.1/types.h>
+#include <log/log.h>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace V2_0 {
+namespace implementation {
+
+/**
+ * Interface used to communicate with the HalProxy when subHals interact with their provided
+ * callback.
+ */
+class ISubHalCallback {
+ public:
+ virtual ~ISubHalCallback() {}
+
+ // Below methods from ::android::hardware::sensors::V2_0::ISensorsCallback with a minor change
+ // to pass in the sub-HAL index. While the above methods are invoked from the sensors framework
+ // via the binder, these methods are invoked from a callback provided to sub-HALs inside the
+ // same process as the HalProxy, but potentially running on different threads.
+ virtual Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V2_1::SensorInfo>& dynamicSensorsAdded, int32_t subHalIndex) = 0;
+
+ virtual Return<void> onDynamicSensorsDisconnected(
+ const hidl_vec<int32_t>& dynamicSensorHandlesRemoved, int32_t subHalIndex) = 0;
+
+ /**
+ * Post events to the event message queue if there is room to write them. Otherwise post the
+ * remaining events to a background thread for a blocking write with a kPendingWriteTimeoutNs
+ * timeout.
+ *
+ * @param events The list of events to post to the message queue.
+ * @param numWakeupEvents The number of wakeup events in events.
+ * @param wakelock The wakelock associated with this post of events.
+ */
+ virtual void postEventsToMessageQueue(const std::vector<V2_1::Event>& events,
+ size_t numWakeupEvents,
+ V2_0::implementation::ScopedWakelock wakelock) = 0;
+
+ /**
+ * Get the sensor info associated with that sensorHandle.
+ *
+ * @param sensorHandle The sensor handle.
+ *
+ * @return The sensor info object in the mapping.
+ */
+ virtual const V2_1::SensorInfo& getSensorInfo(int32_t sensorHandle) = 0;
+
+ virtual bool areThreadsRunning() = 0;
+};
+
+/**
+ * Callback class given to subhals that allows the HalProxy to know which subhal a given invocation
+ * is coming from.
+ */
+class HalProxyCallbackBase : public VirtualLightRefBase {
+ public:
+ HalProxyCallbackBase(ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex)
+ : mCallback(callback), mRefCounter(refCounter), mSubHalIndex(subHalIndex) {}
+
+ void postEvents(const std::vector<V2_1::Event>& events,
+ V2_0::implementation::ScopedWakelock wakelock);
+
+ V2_0::implementation::ScopedWakelock createScopedWakelock(bool lock);
+
+ protected:
+ ISubHalCallback* mCallback;
+ V2_0::implementation::IScopedWakelockRefCounter* mRefCounter;
+ int32_t mSubHalIndex;
+
+ private:
+ std::vector<V2_1::Event> processEvents(const std::vector<V2_1::Event>& events,
+ size_t* numWakeupEvents) const;
+};
+
+class HalProxyCallbackV2_0 : public HalProxyCallbackBase,
+ public V2_0::implementation::IHalProxyCallback {
+ public:
+ HalProxyCallbackV2_0(ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex)
+ : HalProxyCallbackBase(callback, refCounter, subHalIndex) {}
+
+ Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V1_0::SensorInfo>& dynamicSensorsAdded) override {
+ return mCallback->onDynamicSensorsConnected(
+ V2_1::implementation::convertToNewSensorInfos(dynamicSensorsAdded), mSubHalIndex);
+ }
+
+ Return<void> onDynamicSensorsDisconnected(
+ const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override {
+ return mCallback->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved, mSubHalIndex);
+ }
+
+ void postEvents(const std::vector<V1_0::Event>& events,
+ V2_0::implementation::ScopedWakelock wakelock) override {
+ HalProxyCallbackBase::postEvents(V2_1::implementation::convertToNewEvents(events),
+ std::move(wakelock));
+ }
+
+ V2_0::implementation::ScopedWakelock createScopedWakelock(bool lock) override {
+ return HalProxyCallbackBase::createScopedWakelock(lock);
+ }
+};
+
+class HalProxyCallbackV2_1 : public HalProxyCallbackBase,
+ public V2_1::implementation::IHalProxyCallback {
+ public:
+ HalProxyCallbackV2_1(ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex)
+ : HalProxyCallbackBase(callback, refCounter, subHalIndex) {}
+
+ Return<void> onDynamicSensorsConnected_2_1(
+ const hidl_vec<V2_1::SensorInfo>& dynamicSensorsAdded) override {
+ return mCallback->onDynamicSensorsConnected(dynamicSensorsAdded, mSubHalIndex);
+ }
+
+ Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V1_0::SensorInfo>& /* dynamicSensorsAdded */) override {
+ LOG_ALWAYS_FATAL("Old dynamic sensors method can't be used");
+ return Void();
+ }
+
+ Return<void> onDynamicSensorsDisconnected(
+ const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override {
+ return mCallback->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved, mSubHalIndex);
+ }
+
+ void postEvents(const std::vector<V2_1::Event>& events,
+ V2_0::implementation::ScopedWakelock wakelock) override {
+ return HalProxyCallbackBase::postEvents(events, std::move(wakelock));
+ }
+
+ V2_0::implementation::ScopedWakelock createScopedWakelock(bool lock) override {
+ return HalProxyCallbackBase::createScopedWakelock(lock);
+ }
+};
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace sensors
+} // namespace hardware
+} // namespace android
\ No newline at end of file
diff --git a/sensors/common/default/2.X/multihal/include/SubHalWrapper.h b/sensors/common/default/2.X/multihal/include/SubHalWrapper.h
new file mode 100644
index 0000000..149bb5e
--- /dev/null
+++ b/sensors/common/default/2.X/multihal/include/SubHalWrapper.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "HalProxyCallback.h"
+#include "V2_0/SubHal.h"
+#include "V2_1/SubHal.h"
+
+#include "android/hardware/sensors/1.0/ISensors.h"
+#include "android/hardware/sensors/1.0/types.h"
+#include "android/hardware/sensors/2.0/ISensors.h"
+#include "android/hardware/sensors/2.0/ISensorsCallback.h"
+#include "android/hardware/sensors/2.1/ISensors.h"
+#include "android/hardware/sensors/2.1/ISensorsCallback.h"
+#include "android/hardware/sensors/2.1/types.h"
+
+#include <utils/LightRefBase.h>
+
+#include <cassert>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace V2_1 {
+namespace implementation {
+
+/**
+ * The following subHal wrapper classes abstract away common functionality across V2.0 and V2.1
+ * subHal interfaces. Much of the logic is common between the two versions and this allows users of
+ * the classes to only care about the type used at initialization and then interact with either
+ * version of the subHal interface without worrying about the type.
+ */
+class ISubHalWrapperBase {
+ protected:
+ using Event = ::android::hardware::sensors::V2_1::Event;
+ using OperationMode = ::android::hardware::sensors::V1_0::OperationMode;
+ using RateLevel = ::android::hardware::sensors::V1_0::RateLevel;
+ using Result = ::android::hardware::sensors::V1_0::Result;
+ using SensorInfo = ::android::hardware::sensors::V2_1::SensorInfo;
+ using SharedMemInfo = ::android::hardware::sensors::V1_0::SharedMemInfo;
+
+ public:
+ virtual ~ISubHalWrapperBase() {}
+
+ virtual bool supportsNewEvents() = 0;
+
+ virtual Return<Result> initialize(V2_0::implementation::ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex) = 0;
+
+ virtual Return<void> getSensorsList(
+ ::android::hardware::sensors::V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb) = 0;
+
+ virtual Return<Result> setOperationMode(OperationMode mode) = 0;
+
+ virtual Return<Result> activate(int32_t sensorHandle, bool enabled) = 0;
+
+ virtual Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+ int64_t maxReportLatencyNs) = 0;
+
+ virtual Return<Result> flush(int32_t sensorHandle) = 0;
+
+ virtual Return<Result> injectSensorData(const Event& event) = 0;
+
+ virtual Return<void> registerDirectChannel(const SharedMemInfo& mem,
+ ISensors::registerDirectChannel_cb _hidl_cb) = 0;
+
+ virtual Return<Result> unregisterDirectChannel(int32_t channelHandle) = 0;
+
+ virtual Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle,
+ RateLevel rate,
+ ISensors::configDirectReport_cb _hidl_cb) = 0;
+
+ virtual Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) = 0;
+
+ virtual const std::string getName() = 0;
+};
+
+template <typename T>
+class SubHalWrapperBase : public ISubHalWrapperBase {
+ public:
+ SubHalWrapperBase(T* subHal) : mSubHal(subHal){};
+
+ virtual bool supportsNewEvents() override { return false; }
+
+ virtual Return<void> getSensorsList(
+ ::android::hardware::sensors::V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb) override {
+ return mSubHal->getSensorsList(
+ [&](const auto& list) { _hidl_cb(convertToNewSensorInfos(list)); });
+ }
+
+ Return<Result> setOperationMode(OperationMode mode) override {
+ return mSubHal->setOperationMode(mode);
+ }
+
+ Return<Result> activate(int32_t sensorHandle, bool enabled) override {
+ return mSubHal->activate(sensorHandle, enabled);
+ }
+
+ Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+ int64_t maxReportLatencyNs) override {
+ return mSubHal->batch(sensorHandle, samplingPeriodNs, maxReportLatencyNs);
+ }
+
+ Return<Result> flush(int32_t sensorHandle) override { return mSubHal->flush(sensorHandle); }
+
+ virtual Return<Result> injectSensorData(const Event& event) override {
+ return mSubHal->injectSensorData(convertToOldEvent(event));
+ }
+
+ Return<void> registerDirectChannel(const SharedMemInfo& mem,
+ ISensors::registerDirectChannel_cb _hidl_cb) override {
+ return mSubHal->registerDirectChannel(mem, _hidl_cb);
+ }
+
+ Return<Result> unregisterDirectChannel(int32_t channelHandle) override {
+ return mSubHal->unregisterDirectChannel(channelHandle);
+ }
+
+ Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle, RateLevel rate,
+ ISensors::configDirectReport_cb _hidl_cb) override {
+ return mSubHal->configDirectReport(sensorHandle, channelHandle, rate, _hidl_cb);
+ }
+
+ Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) override {
+ return mSubHal->debug(fd, args);
+ }
+
+ const std::string getName() override { return mSubHal->getName(); }
+
+ protected:
+ T* mSubHal;
+};
+
+class SubHalWrapperV2_0 : public SubHalWrapperBase<V2_0::implementation::ISensorsSubHal> {
+ public:
+ SubHalWrapperV2_0(V2_0::implementation::ISensorsSubHal* subHal) : SubHalWrapperBase(subHal){};
+
+ Return<Result> initialize(V2_0::implementation::ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex) override {
+ return mSubHal->initialize(
+ new V2_0::implementation::HalProxyCallbackV2_0(callback, refCounter, subHalIndex));
+ }
+};
+
+class SubHalWrapperV2_1 : public SubHalWrapperBase<V2_1::implementation::ISensorsSubHal> {
+ public:
+ SubHalWrapperV2_1(V2_1::implementation::ISensorsSubHal* subHal) : SubHalWrapperBase(subHal) {}
+
+ bool supportsNewEvents() override { return true; }
+
+ virtual Return<void> getSensorsList(
+ ::android::hardware::sensors::V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb) override {
+ return mSubHal->getSensorsList_2_1([&](const auto& list) { _hidl_cb(list); });
+ }
+
+ virtual Return<Result> injectSensorData(const Event& event) override {
+ return mSubHal->injectSensorData_2_1(event);
+ }
+
+ Return<Result> initialize(V2_0::implementation::ISubHalCallback* callback,
+ V2_0::implementation::IScopedWakelockRefCounter* refCounter,
+ int32_t subHalIndex) override {
+ return mSubHal->initialize(
+ new V2_0::implementation::HalProxyCallbackV2_1(callback, refCounter, subHalIndex));
+ }
+};
+
+} // namespace implementation
+} // namespace V2_1
+} // namespace sensors
+} // namespace hardware
+} // namespace android
diff --git a/sensors/common/default/2.X/multihal/include/V2_0/ScopedWakelock.h b/sensors/common/default/2.X/multihal/include/V2_0/ScopedWakelock.h
index aa6d9db..1cc5cd5 100644
--- a/sensors/common/default/2.X/multihal/include/V2_0/ScopedWakelock.h
+++ b/sensors/common/default/2.X/multihal/include/V2_0/ScopedWakelock.h
@@ -88,7 +88,7 @@
bool isLocked() const { return mLocked; }
private:
- friend class HalProxyCallback;
+ friend class HalProxyCallbackBase;
IScopedWakelockRefCounter* mRefCounter;
int64_t mCreatedAtTimeNs;
bool mLocked;
diff --git a/sensors/common/default/2.X/multihal/tests/Android.bp b/sensors/common/default/2.X/multihal/tests/Android.bp
index 7692b51..a15faed 100644
--- a/sensors/common/default/2.X/multihal/tests/Android.bp
+++ b/sensors/common/default/2.X/multihal/tests/Android.bp
@@ -20,6 +20,7 @@
],
header_libs: [
"android.hardware.sensors@2.0-multihal.header",
+ "android.hardware.sensors@2.X-shared-utils",
],
export_include_dirs: ["fake_subhal"],
shared_libs: [
@@ -36,6 +37,7 @@
"libutils",
],
static_libs: [
+ "android.hardware.sensors@1.0-convert",
"android.hardware.sensors@2.X-multihal",
],
cflags: [
@@ -48,6 +50,7 @@
vendor: true,
defaults: ["android.hardware.sensors@2.X-fakesubhal-defaults"],
cflags: [
+ "-DSUB_HAL_VERSION_2_0",
"-DSUPPORT_CONTINUOUS_SENSORS",
"-DSUB_HAL_NAME=\"FakeSubHal-Continuous\"",
],
@@ -58,6 +61,17 @@
vendor: true,
defaults: ["android.hardware.sensors@2.X-fakesubhal-defaults"],
cflags: [
+ "-DSUB_HAL_VERSION_2_0",
+ "-DSUPPORT_ON_CHANGE_SENSORS",
+ "-DSUB_HAL_NAME=\"FakeSubHal-OnChange\"",
+ ],
+}
+
+cc_library {
+ name: "android.hardware.sensors@2.X-fakesubhal-config3",
+ vendor: true,
+ defaults: ["android.hardware.sensors@2.X-fakesubhal-defaults"],
+ cflags: [
"-DSUPPORT_ON_CHANGE_SENSORS",
"-DSUB_HAL_NAME=\"FakeSubHal-OnChange\"",
],
@@ -78,7 +92,11 @@
name: "android.hardware.sensors@2.X-halproxy-unit-tests",
srcs: ["HalProxy_test.cpp"],
vendor: true,
+ header_libs: [
+ "android.hardware.sensors@2.X-shared-utils",
+ ],
static_libs: [
+ "android.hardware.sensors@1.0-convert",
"android.hardware.sensors@2.0-ScopedWakelock.testlib",
"android.hardware.sensors@2.X-multihal",
"android.hardware.sensors@2.X-fakesubhal-unittest",
@@ -86,7 +104,6 @@
shared_libs: [
"android.hardware.sensors@1.0",
"android.hardware.sensors@2.0",
- "android.hardware.sensors@2.0-ScopedWakelock",
"android.hardware.sensors@2.1",
"libbase",
"libcutils",
diff --git a/sensors/common/default/2.X/multihal/tests/HalProxy_test.cpp b/sensors/common/default/2.X/multihal/tests/HalProxy_test.cpp
index 867c4a1..858786a 100644
--- a/sensors/common/default/2.X/multihal/tests/HalProxy_test.cpp
+++ b/sensors/common/default/2.X/multihal/tests/HalProxy_test.cpp
@@ -15,12 +15,15 @@
#include <gtest/gtest.h>
+#include <android/hardware/sensors/1.0/types.h>
#include <android/hardware/sensors/2.0/types.h>
+#include <android/hardware/sensors/2.1/types.h>
#include <fmq/MessageQueue.h>
#include "HalProxy.h"
#include "SensorsSubHal.h"
#include "V2_0/ScopedWakelock.h"
+#include "convertV2_1.h"
#include <chrono>
#include <set>
@@ -38,27 +41,35 @@
using ::android::hardware::sensors::V1_0::SensorInfo;
using ::android::hardware::sensors::V1_0::SensorType;
using ::android::hardware::sensors::V2_0::EventQueueFlagBits;
-using ::android::hardware::sensors::V2_0::ISensorsCallback;
using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
-using ::android::hardware::sensors::V2_0::implementation::HalProxy;
-using ::android::hardware::sensors::V2_0::implementation::HalProxyCallback;
-using ::android::hardware::sensors::V2_0::subhal::implementation::AddAndRemoveDynamicSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::AllSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::
+using ::android::hardware::sensors::V2_0::implementation::HalProxyCallbackBase;
+using ::android::hardware::sensors::V2_0::implementation::ScopedWakelock;
+using ::android::hardware::sensors::V2_1::implementation::convertToNewEvents;
+using ::android::hardware::sensors::V2_1::implementation::convertToNewSensorInfos;
+using ::android::hardware::sensors::V2_1::implementation::HalProxy;
+using ::android::hardware::sensors::V2_1::subhal::implementation::AddAndRemoveDynamicSensorsSubHal;
+using ::android::hardware::sensors::V2_1::subhal::implementation::AllSensorsSubHal;
+using ::android::hardware::sensors::V2_1::subhal::implementation::
AllSupportDirectChannelSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::ContinuousSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::
+using ::android::hardware::sensors::V2_1::subhal::implementation::ContinuousSensorsSubHal;
+using ::android::hardware::sensors::V2_1::subhal::implementation::
DoesNotSupportDirectChannelSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::OnChangeSensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::SensorsSubHal;
-using ::android::hardware::sensors::V2_0::subhal::implementation::
+using ::android::hardware::sensors::V2_1::subhal::implementation::OnChangeSensorsSubHal;
+using ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_0;
+using ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_1;
+using ::android::hardware::sensors::V2_1::subhal::implementation::
SetOperationModeFailingSensorsSubHal;
-using EventMessageQueue = MessageQueue<Event, ::android::hardware::kSynchronizedReadWrite>;
+using ISensorsCallbackV2_0 = ::android::hardware::sensors::V2_0::ISensorsCallback;
+using ISensorsCallbackV2_1 = ::android::hardware::sensors::V2_1::ISensorsCallback;
+using EventV1_0 = ::android::hardware::sensors::V1_0::Event;
+using EventV2_1 = ::android::hardware::sensors::V2_1::Event;
+using EventMessageQueueV2_1 = MessageQueue<EventV2_1, ::android::hardware::kSynchronizedReadWrite>;
+using EventMessageQueueV2_0 = MessageQueue<EventV1_0, ::android::hardware::kSynchronizedReadWrite>;
using WakeupMessageQueue = MessageQueue<uint32_t, ::android::hardware::kSynchronizedReadWrite>;
// The barebones sensors callback class passed into halproxy initialize calls
-class SensorsCallback : public ISensorsCallback {
+class SensorsCallback : public ISensorsCallbackV2_0 {
public:
Return<void> onDynamicSensorsConnected(
const hidl_vec<SensorInfo>& /*dynamicSensorsAdded*/) override {
@@ -73,8 +84,30 @@
}
};
+class SensorsCallbackV2_1 : public ISensorsCallbackV2_1 {
+ public:
+ Return<void> onDynamicSensorsConnected_2_1(
+ const hidl_vec<::android::hardware::sensors::V2_1::SensorInfo>& /*dynamicSensorsAdded*/)
+ override {
+ // Nothing yet
+ return Return<void>();
+ }
+
+ Return<void> onDynamicSensorsConnected(
+ const hidl_vec<SensorInfo>& /*dynamicSensorsAdded*/) override {
+ // Nothing yet
+ return Return<void>();
+ }
+
+ Return<void> onDynamicSensorsDisconnected(
+ const hidl_vec<int32_t>& /*dynamicSensorHandlesRemoved*/) override {
+ // Nothing yet
+ return Return<void>();
+ }
+};
+
// The sensors callback that expects a variable list of sensors to be added
-class TestSensorsCallback : public ISensorsCallback {
+class TestSensorsCallback : public ISensorsCallbackV2_0 {
public:
Return<void> onDynamicSensorsConnected(
const hidl_vec<SensorInfo>& dynamicSensorsAdded) override {
@@ -129,10 +162,10 @@
void ackWakeupEventsToHalProxy(size_t numEvents, std::unique_ptr<WakeupMessageQueue>& wakelockQueue,
EventFlag* wakelockQueueFlag);
-bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueue>& eventQueue,
+bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueueV2_0>& eventQueue,
EventFlag* eventQueueFlag);
-std::unique_ptr<EventMessageQueue> makeEventFMQ(size_t size);
+std::unique_ptr<EventMessageQueueV2_0> makeEventFMQ(size_t size);
std::unique_ptr<WakeupMessageQueue> makeWakelockFMQ(size_t size);
@@ -142,7 +175,7 @@
*
* @return A proximity event.
*/
-Event makeProximityEvent();
+EventV1_0 makeProximityEvent();
/**
* Construct and return a HIDL Event type thats sensorHandle refers to a proximity sensor
@@ -150,7 +183,7 @@
*
* @return A proximity event.
*/
-Event makeAccelerometerEvent();
+EventV1_0 makeAccelerometerEvent();
/**
* Make a certain number of proximity type events with the sensorHandle field set to
@@ -160,7 +193,7 @@
*
* @return The created list of events.
*/
-std::vector<Event> makeMultipleProximityEvents(size_t numEvents);
+std::vector<EventV1_0> makeMultipleProximityEvents(size_t numEvents);
/**
* Make a certain number of accelerometer type events with the sensorHandle field set to
@@ -170,7 +203,7 @@
*
* @return The created list of events.
*/
-std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents);
+std::vector<EventV1_0> makeMultipleAccelerometerEvents(size_t numEvents);
/**
* Given a SensorInfo vector and a sensor handles vector populate 'sensors' with SensorInfo
@@ -188,7 +221,7 @@
// Tests follow
TEST(HalProxyTest, GetSensorsListOneSubHalTest) {
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> fakeSubHals{&subHal};
HalProxy proxy(fakeSubHals);
@@ -200,8 +233,8 @@
}
TEST(HalProxyTest, GetSensorsListTwoSubHalTest) {
- ContinuousSensorsSubHal continuousSubHal;
- OnChangeSensorsSubHal onChangeSubHal;
+ ContinuousSensorsSubHal<SensorsSubHalV2_0> continuousSubHal;
+ OnChangeSensorsSubHal<SensorsSubHalV2_0> onChangeSubHal;
std::vector<ISensorsSubHal*> fakeSubHals;
fakeSubHals.push_back(&continuousSubHal);
fakeSubHals.push_back(&onChangeSubHal);
@@ -221,8 +254,8 @@
}
TEST(HalProxyTest, SetOperationModeTwoSubHalSuccessTest) {
- ContinuousSensorsSubHal subHal1;
- OnChangeSensorsSubHal subHal2;
+ ContinuousSensorsSubHal<SensorsSubHalV2_0> subHal1;
+ OnChangeSensorsSubHal<SensorsSubHalV2_0> subHal2;
std::vector<ISensorsSubHal*> fakeSubHals{&subHal1, &subHal2};
HalProxy proxy(fakeSubHals);
@@ -238,7 +271,7 @@
}
TEST(HalProxyTest, SetOperationModeTwoSubHalFailTest) {
- AllSensorsSubHal subHal1;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal1;
SetOperationModeFailingSensorsSubHal subHal2;
std::vector<ISensorsSubHal*> fakeSubHals{&subHal1, &subHal2};
@@ -279,16 +312,16 @@
TEST(HalProxyTest, PostSingleNonWakeupEvent) {
constexpr size_t kQueueSize = 5;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events{makeAccelerometerEvent()};
- subHal.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events{makeAccelerometerEvent()};
+ subHal.postEvents(convertToNewEvents(events), false /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), 1);
}
@@ -296,28 +329,28 @@
TEST(HalProxyTest, PostMultipleNonWakeupEvent) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 3;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
- subHal.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+ subHal.postEvents(convertToNewEvents(events), false /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
}
TEST(HalProxyTest, PostSingleWakeupEvent) {
constexpr size_t kQueueSize = 5;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
EventFlag* eventQueueFlag;
@@ -326,8 +359,8 @@
EventFlag* wakelockQueueFlag;
EventFlag::createEventFlag(wakeLockQueue->getEventFlagWord(), &wakelockQueueFlag);
- std::vector<Event> events{makeProximityEvent()};
- subHal.postEvents(events, true /* wakeup */);
+ std::vector<EventV1_0> events{makeProximityEvent()};
+ subHal.postEvents(convertToNewEvents(events), true /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), 1);
@@ -338,12 +371,12 @@
TEST(HalProxyTest, PostMultipleWakeupEvents) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 3;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
EventFlag* eventQueueFlag;
@@ -352,8 +385,8 @@
EventFlag* wakelockQueueFlag;
EventFlag::createEventFlag(wakeLockQueue->getEventFlagWord(), &wakelockQueueFlag);
- std::vector<Event> events = makeMultipleProximityEvents(kNumEvents);
- subHal.postEvents(events, true /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleProximityEvents(kNumEvents);
+ subHal.postEvents(convertToNewEvents(events), true /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
@@ -364,20 +397,20 @@
TEST(HalProxyTest, PostEventsMultipleSubhals) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 2;
- AllSensorsSubHal subHal1, subHal2;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal1, subHal2;
std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
- subHal1.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+ subHal1.postEvents(convertToNewEvents(events), false /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), kNumEvents);
- subHal2.postEvents(events, false /* wakeup */);
+ subHal2.postEvents(convertToNewEvents(events), false /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2);
}
@@ -385,19 +418,19 @@
TEST(HalProxyTest, PostEventsDelayedWrite) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 6;
- AllSensorsSubHal subHal1, subHal2;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal1, subHal2;
std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
EventFlag* eventQueueFlag;
EventFlag::createEventFlag(eventQueue->getEventFlagWord(), &eventQueueFlag);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
- subHal1.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+ subHal1.postEvents(convertToNewEvents(events), false /* wakeup */);
EXPECT_EQ(eventQueue->availableToRead(), kQueueSize);
@@ -413,18 +446,20 @@
TEST(HalProxyTest, PostEventsMultipleSubhalsThreaded) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 2;
- AllSensorsSubHal subHal1, subHal2;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal1, subHal2;
std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
- std::thread t1(&AllSensorsSubHal::postEvents, &subHal1, events, false);
- std::thread t2(&AllSensorsSubHal::postEvents, &subHal2, events, false);
+ std::thread t1(&AllSensorsSubHal<SensorsSubHalV2_0>::postEvents, &subHal1,
+ convertToNewEvents(events), false);
+ std::thread t2(&AllSensorsSubHal<SensorsSubHalV2_0>::postEvents, &subHal2,
+ convertToNewEvents(events), false);
t1.join();
t2.join();
@@ -435,34 +470,34 @@
TEST(HalProxyTest, DestructingWithEventsPendingOnBackgroundThread) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 6;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
- subHal.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+ subHal.postEvents(convertToNewEvents(events), false /* wakeup */);
// Destructing HalProxy object with events on the background thread
}
TEST(HalProxyTest, DestructingWithUnackedWakeupEventsPosted) {
constexpr size_t kQueueSize = 5;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events{makeProximityEvent()};
- subHal.postEvents(events, true /* wakeup */);
+ std::vector<EventV1_0> events{makeProximityEvent()};
+ subHal.postEvents(convertToNewEvents(events), true /* wakeup */);
// Not sending any acks back through wakeLockQueue
@@ -472,17 +507,17 @@
TEST(HalProxyTest, ReinitializeWithEventsPendingOnBackgroundThread) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumEvents = 10;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents);
- subHal.postEvents(events, false /* wakeup */);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+ subHal.postEvents(convertToNewEvents(events), false /* wakeup */);
eventQueue = makeEventFMQ(kQueueSize);
wakeLockQueue = makeWakelockFMQ(kQueueSize);
@@ -492,23 +527,23 @@
EXPECT_EQ(secondInitResult, Result::OK);
// Small sleep so that pending writes thread has a change to hit writeBlocking call.
std::this_thread::sleep_for(std::chrono::milliseconds(5));
- Event eventOut;
+ EventV1_0 eventOut;
EXPECT_FALSE(eventQueue->read(&eventOut));
}
TEST(HalProxyTest, ReinitializingWithUnackedWakeupEventsPosted) {
constexpr size_t kQueueSize = 5;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- std::vector<Event> events{makeProximityEvent()};
- subHal.postEvents(events, true /* wakeup */);
+ std::vector<EventV1_0> events{makeProximityEvent()};
+ subHal.postEvents(convertToNewEvents(events), true /* wakeup */);
// Not sending any acks back through wakeLockQueue
@@ -523,12 +558,12 @@
TEST(HalProxyTest, InitializeManyTimesInARow) {
constexpr size_t kQueueSize = 5;
constexpr size_t kNumTimesToInit = 100;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
for (size_t i = 0; i < kNumTimesToInit; i++) {
@@ -540,15 +575,15 @@
TEST(HalProxyTest, OperationModeResetOnInitialize) {
constexpr size_t kQueueSize = 5;
- AllSensorsSubHal subHal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.setOperationMode(OperationMode::DATA_INJECTION);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
- Event event = makeAccelerometerEvent();
+ EventV1_0 event = makeAccelerometerEvent();
// Should not be able to inject a non AdditionInfo type event because operation mode should
// have been reset to NORMAL
EXPECT_EQ(proxy.injectSensorData(event), Result::BAD_VALUE);
@@ -559,7 +594,7 @@
constexpr size_t kNumSensors = 5;
AddAndRemoveDynamicSensorsSubHal subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
HalProxy proxy(subHals);
@@ -574,9 +609,9 @@
}
TestSensorsCallback* callback = new TestSensorsCallback();
- ::android::sp<ISensorsCallback> callbackPtr = callback;
+ ::android::sp<ISensorsCallbackV2_0> callbackPtr = callback;
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr);
- subHal.addDynamicSensors(sensorsToConnect);
+ subHal.addDynamicSensors(convertToNewSensorInfos(sensorsToConnect));
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr);
subHal.removeDynamicSensors(sensorHandlesToAttemptToRemove);
@@ -593,7 +628,7 @@
AddAndRemoveDynamicSensorsSubHal subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(0);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(0);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(0);
std::vector<SensorInfo> sensorsToConnect;
@@ -602,9 +637,9 @@
sensorHandlesToExpect);
TestSensorsCallback* callback = new TestSensorsCallback();
- ::android::sp<ISensorsCallback> callbackPtr = callback;
+ ::android::sp<ISensorsCallbackV2_0> callbackPtr = callback;
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr);
- subHal.addDynamicSensors(sensorsToConnect);
+ subHal.addDynamicSensors(convertToNewSensorInfos(sensorsToConnect));
std::vector<SensorInfo> sensorsSeen = callback->getSensorsConnected();
EXPECT_EQ(kNumSensors, sensorsSeen.size());
@@ -621,7 +656,7 @@
AddAndRemoveDynamicSensorsSubHal subHal;
std::vector<ISensorsSubHal*> subHals{&subHal};
HalProxy proxy(subHals);
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(0);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(0);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(0);
std::vector<SensorInfo> sensorsToConnect;
@@ -646,9 +681,9 @@
nonDynamicSensorHandles.end());
TestSensorsCallback* callback = new TestSensorsCallback();
- ::android::sp<ISensorsCallback> callbackPtr = callback;
+ ::android::sp<ISensorsCallbackV2_0> callbackPtr = callback;
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr);
- subHal.addDynamicSensors(sensorsToConnect);
+ subHal.addDynamicSensors(convertToNewSensorInfos(sensorsToConnect));
subHal.removeDynamicSensors(sensorHandlesToAttemptToRemove);
std::vector<int32_t> sensorHandlesSeen = callback->getSensorHandlesDisconnected();
@@ -667,15 +702,15 @@
constexpr size_t kNumSubHals = 3;
constexpr size_t kQueueSize = 5;
int32_t kNumSubHalsInt32 = static_cast<int32_t>(kNumSubHals);
- std::vector<AllSensorsSubHal> subHalObjs(kNumSubHals);
+ std::vector<AllSensorsSubHal<SensorsSubHalV2_0>> subHalObjs(kNumSubHals);
std::vector<ISensorsSubHal*> subHals;
for (const auto& subHal : subHalObjs) {
subHals.push_back((ISensorsSubHal*)(&subHal));
}
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
// Initialize for the injectSensorData call so callback postEvents is valid
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
@@ -687,7 +722,7 @@
EXPECT_EQ(proxy.activate(0x00000001 | (kNumSubHalsInt32 << 24), true), Result::BAD_VALUE);
EXPECT_EQ(proxy.batch(0x00000001 | (kNumSubHalsInt32 << 24), 0, 0), Result::BAD_VALUE);
EXPECT_EQ(proxy.flush(0x00000001 | (kNumSubHalsInt32 << 24)), Result::BAD_VALUE);
- Event event;
+ EventV1_0 event;
event.sensorHandle = 0x00000001 | (kNumSubHalsInt32 << 24);
EXPECT_EQ(proxy.injectSensorData(event), Result::BAD_VALUE);
}
@@ -696,28 +731,28 @@
constexpr size_t kQueueSize = 5;
constexpr int32_t subhal1Index = 0;
constexpr int32_t subhal2Index = 1;
- AllSensorsSubHal subhal1;
- AllSensorsSubHal subhal2;
+ AllSensorsSubHal<SensorsSubHalV2_0> subhal1;
+ AllSensorsSubHal<SensorsSubHalV2_0> subhal2;
std::vector<ISensorsSubHal*> subHals{&subhal1, &subhal2};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
int32_t sensorHandleToPost = 0x00000001;
- Event eventIn = makeAccelerometerEvent();
+ EventV1_0 eventIn = makeAccelerometerEvent();
eventIn.sensorHandle = sensorHandleToPost;
- std::vector<Event> eventsToPost{eventIn};
- subhal1.postEvents(eventsToPost, false);
+ std::vector<EventV1_0> eventsToPost{eventIn};
+ subhal1.postEvents(convertToNewEvents(eventsToPost), false);
- Event eventOut;
+ EventV1_0 eventOut;
EXPECT_TRUE(eventQueue->read(&eventOut));
EXPECT_EQ(eventOut.sensorHandle, (subhal1Index << 24) | sensorHandleToPost);
- subhal2.postEvents(eventsToPost, false);
+ subhal2.postEvents(convertToNewEvents(eventsToPost), false);
EXPECT_TRUE(eventQueue->read(&eventOut));
@@ -728,22 +763,22 @@
constexpr size_t kQueueSize = 5;
// TODO: Make this constant linked to same limit in HalProxy.h
constexpr size_t kMaxPendingQueueSize = 100000;
- AllSensorsSubHal subhal;
+ AllSensorsSubHal<SensorsSubHalV2_0> subhal;
std::vector<ISensorsSubHal*> subHals{&subhal};
- std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize);
+ std::unique_ptr<EventMessageQueueV2_0> eventQueue = makeEventFMQ(kQueueSize);
std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
- ::android::sp<ISensorsCallback> callback = new SensorsCallback();
+ ::android::sp<ISensorsCallbackV2_0> callback = new SensorsCallback();
EventFlag* eventQueueFlag;
EventFlag::createEventFlag(eventQueue->getEventFlagWord(), &eventQueueFlag);
HalProxy proxy(subHals);
proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
// Fill pending queue
- std::vector<Event> events = makeMultipleAccelerometerEvents(kQueueSize);
- subhal.postEvents(events, false);
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kQueueSize);
+ subhal.postEvents(convertToNewEvents(events), false);
events = makeMultipleAccelerometerEvents(kMaxPendingQueueSize);
- subhal.postEvents(events, false);
+ subhal.postEvents(convertToNewEvents(events), false);
// Drain pending queue
for (int i = 0; i < kMaxPendingQueueSize + kQueueSize; i += kQueueSize) {
@@ -752,9 +787,9 @@
// Put one event on pending queue
events = makeMultipleAccelerometerEvents(kQueueSize);
- subhal.postEvents(events, false);
+ subhal.postEvents(convertToNewEvents(events), false);
events = {makeAccelerometerEvent()};
- subhal.postEvents(events, false);
+ subhal.postEvents(convertToNewEvents(events), false);
// Read out to make room for one event on pending queue to write to FMQ
ASSERT_TRUE(readEventsOutOfQueue(kQueueSize, eventQueue, eventQueueFlag));
@@ -763,6 +798,35 @@
EXPECT_TRUE(readEventsOutOfQueue(1, eventQueue, eventQueueFlag));
}
+TEST(HalProxyTest, PostEventsMultipleSubhalsThreadedV2_1) {
+ constexpr size_t kQueueSize = 5;
+ constexpr size_t kNumEvents = 2;
+ AllSensorsSubHal<SensorsSubHalV2_0> subHal1;
+ AllSensorsSubHal<SensorsSubHalV2_1> subHal2;
+ std::vector<::android::hardware::sensors::V2_0::implementation::ISensorsSubHal*> subHalsV2_0{
+ &subHal1};
+ std::vector<::android::hardware::sensors::V2_1::implementation::ISensorsSubHal*> subHalsV2_1{
+ &subHal2};
+ HalProxy proxy(subHalsV2_0, subHalsV2_1);
+ std::unique_ptr<EventMessageQueueV2_1> eventQueue =
+ std::make_unique<EventMessageQueueV2_1>(kQueueSize, true);
+ std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize);
+ ::android::sp<ISensorsCallbackV2_1> callback = new SensorsCallbackV2_1();
+ proxy.initialize_2_1(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback);
+
+ std::vector<EventV1_0> events = makeMultipleAccelerometerEvents(kNumEvents);
+
+ std::thread t1(&AllSensorsSubHal<SensorsSubHalV2_0>::postEvents, &subHal1,
+ convertToNewEvents(events), false);
+ std::thread t2(&AllSensorsSubHal<SensorsSubHalV2_1>::postEvents, &subHal2,
+ convertToNewEvents(events), false);
+
+ t1.join();
+ t2.join();
+
+ EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2);
+}
+
// Helper implementations follow
void testSensorsListFromProxyAndSubHal(const std::vector<SensorInfo>& proxySensorsList,
const std::vector<SensorInfo>& subHalSensorsList) {
@@ -801,26 +865,26 @@
wakelockQueueFlag->wake(static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN));
}
-bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueue>& eventQueue,
+bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueueV2_0>& eventQueue,
EventFlag* eventQueueFlag) {
constexpr int64_t kReadBlockingTimeout = INT64_C(500000000);
- std::vector<Event> events(numEvents);
+ std::vector<EventV1_0> events(numEvents);
return eventQueue->readBlocking(events.data(), numEvents,
static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ),
static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS),
kReadBlockingTimeout, eventQueueFlag);
}
-std::unique_ptr<EventMessageQueue> makeEventFMQ(size_t size) {
- return std::make_unique<EventMessageQueue>(size, true);
+std::unique_ptr<EventMessageQueueV2_0> makeEventFMQ(size_t size) {
+ return std::make_unique<EventMessageQueueV2_0>(size, true);
}
std::unique_ptr<WakeupMessageQueue> makeWakelockFMQ(size_t size) {
return std::make_unique<WakeupMessageQueue>(size, true);
}
-Event makeProximityEvent() {
- Event event;
+EventV1_0 makeProximityEvent() {
+ EventV1_0 event;
event.timestamp = 0xFF00FF00;
// This is the sensorhandle of proximity, which is wakeup type
event.sensorHandle = 0x00000008;
@@ -829,8 +893,8 @@
return event;
}
-Event makeAccelerometerEvent() {
- Event event;
+EventV1_0 makeAccelerometerEvent() {
+ EventV1_0 event;
event.timestamp = 0xFF00FF00;
// This is the sensorhandle of proximity, which is wakeup type
event.sensorHandle = 0x00000001;
@@ -839,16 +903,16 @@
return event;
}
-std::vector<Event> makeMultipleProximityEvents(size_t numEvents) {
- std::vector<Event> events;
+std::vector<EventV1_0> makeMultipleProximityEvents(size_t numEvents) {
+ std::vector<EventV1_0> events;
for (size_t i = 0; i < numEvents; i++) {
events.push_back(makeProximityEvent());
}
return events;
}
-std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents) {
- std::vector<Event> events;
+std::vector<EventV1_0> makeMultipleAccelerometerEvents(size_t numEvents) {
+ std::vector<EventV1_0> events;
for (size_t i = 0; i < numEvents; i++) {
events.push_back(makeAccelerometerEvent());
}
diff --git a/sensors/common/default/2.X/multihal/tests/fake_subhal/IHalProxyCallbackWrapper.h b/sensors/common/default/2.X/multihal/tests/fake_subhal/IHalProxyCallbackWrapper.h
new file mode 100644
index 0000000..4542bfd
--- /dev/null
+++ b/sensors/common/default/2.X/multihal/tests/fake_subhal/IHalProxyCallbackWrapper.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "V2_0/SubHal.h"
+#include "V2_1/SubHal.h"
+#include "convertV2_1.h"
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace V2_1 {
+namespace subhal {
+namespace implementation {
+
+/**
+ * The following callback wrapper classes abstract away common functionality across V2.0 and V2.1
+ * interfaces. Much of the logic is common between the two versions and this allows users of the
+ * classes to only care about the type used at initialization and then interact with either version
+ * of the callback interface without worrying about the type.
+ */
+class IHalProxyCallbackWrapperBase {
+ protected:
+ using ScopedWakelock = V2_0::implementation::ScopedWakelock;
+
+ public:
+ virtual ~IHalProxyCallbackWrapperBase() {}
+
+ virtual Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V2_1::SensorInfo>& sensorInfos) = 0;
+
+ virtual Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& sensorHandles) = 0;
+
+ virtual void postEvents(const std::vector<V2_1::Event>& events, ScopedWakelock wakelock) = 0;
+
+ virtual ScopedWakelock createScopedWakelock(bool lock) = 0;
+};
+
+template <typename T>
+class HalProxyCallbackWrapperBase : public IHalProxyCallbackWrapperBase {
+ public:
+ HalProxyCallbackWrapperBase(sp<T> callback) : mCallback(callback){};
+
+ Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& sensorHandles) override {
+ return mCallback->onDynamicSensorsDisconnected(sensorHandles);
+ }
+
+ ScopedWakelock createScopedWakelock(bool lock) override {
+ return mCallback->createScopedWakelock(lock);
+ }
+
+ protected:
+ sp<T> mCallback;
+};
+
+class HalProxyCallbackWrapperV2_0
+ : public HalProxyCallbackWrapperBase<V2_0::implementation::IHalProxyCallback> {
+ public:
+ HalProxyCallbackWrapperV2_0(sp<V2_0::implementation::IHalProxyCallback> callback)
+ : HalProxyCallbackWrapperBase(callback){};
+
+ Return<void> onDynamicSensorsConnected(const hidl_vec<V2_1::SensorInfo>& sensorInfos) override {
+ return mCallback->onDynamicSensorsConnected(
+ V2_1::implementation::convertToOldSensorInfos(sensorInfos));
+ }
+
+ void postEvents(const std::vector<V2_1::Event>& events, ScopedWakelock wakelock) override {
+ return mCallback->postEvents(V2_1::implementation::convertToOldEvents(events),
+ std::move(wakelock));
+ }
+};
+
+class HalProxyCallbackWrapperV2_1
+ : public HalProxyCallbackWrapperBase<V2_1::implementation::IHalProxyCallback> {
+ public:
+ HalProxyCallbackWrapperV2_1(sp<V2_1::implementation::IHalProxyCallback> callback)
+ : HalProxyCallbackWrapperBase(callback){};
+
+ Return<void> onDynamicSensorsConnected(const hidl_vec<V2_1::SensorInfo>& sensorInfos) override {
+ return mCallback->onDynamicSensorsConnected_2_1(sensorInfos);
+ }
+
+ void postEvents(const std::vector<V2_1::Event>& events, ScopedWakelock wakelock) {
+ return mCallback->postEvents(events, std::move(wakelock));
+ }
+};
+
+} // namespace implementation
+} // namespace subhal
+} // namespace V2_1
+} // namespace sensors
+} // namespace hardware
+} // namespace android
diff --git a/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.cpp b/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.cpp
index de89a00..1efd971 100644
--- a/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.cpp
+++ b/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.cpp
@@ -24,13 +24,18 @@
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace subhal {
namespace implementation {
using ::android::hardware::sensors::V1_0::MetaDataEventType;
+using ::android::hardware::sensors::V1_0::OperationMode;
+using ::android::hardware::sensors::V1_0::Result;
using ::android::hardware::sensors::V1_0::SensorFlagBits;
using ::android::hardware::sensors::V1_0::SensorStatus;
+using ::android::hardware::sensors::V2_1::Event;
+using ::android::hardware::sensors::V2_1::SensorInfo;
+using ::android::hardware::sensors::V2_1::SensorType;
Sensor::Sensor(int32_t sensorHandle, ISensorsEventCallback* callback)
: mIsEnabled(false),
@@ -343,7 +348,7 @@
} // namespace implementation
} // namespace subhal
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.h b/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.h
index 60f5d3d..5cf9f83 100644
--- a/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.h
+++ b/sensors/common/default/2.X/multihal/tests/fake_subhal/Sensor.h
@@ -16,7 +16,7 @@
#pragma once
-#include <android/hardware/sensors/1.0/types.h>
+#include <android/hardware/sensors/2.1/types.h>
#include <condition_variable>
#include <memory>
@@ -24,16 +24,16 @@
#include <thread>
#include <vector>
-using ::android::hardware::sensors::V1_0::Event;
using ::android::hardware::sensors::V1_0::OperationMode;
using ::android::hardware::sensors::V1_0::Result;
-using ::android::hardware::sensors::V1_0::SensorInfo;
-using ::android::hardware::sensors::V1_0::SensorType;
+using ::android::hardware::sensors::V2_1::Event;
+using ::android::hardware::sensors::V2_1::SensorInfo;
+using ::android::hardware::sensors::V2_1::SensorType;
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace subhal {
namespace implementation {
@@ -151,7 +151,7 @@
} // namespace implementation
} // namespace subhal
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.cpp b/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.cpp
index ff5ff38..20a4e9d 100644
--- a/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.cpp
+++ b/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.cpp
@@ -16,33 +16,66 @@
#include "SensorsSubHal.h"
-#include <android/hardware/sensors/2.0/types.h>
+#include <android/hardware/sensors/2.1/types.h>
#include <log/log.h>
-ISensorsSubHal* sensorsHalGetSubHal(uint32_t* version) {
+#ifdef SUB_HAL_VERSION_2_0
+::android::hardware::sensors::V2_0::implementation::ISensorsSubHal* sensorsHalGetSubHal(
+ uint32_t* version) {
#if defined SUPPORT_CONTINUOUS_SENSORS && defined SUPPORT_ON_CHANGE_SENSORS
- static ::android::hardware::sensors::V2_0::subhal::implementation::AllSensorsSubHal subHal;
+ static ::android::hardware::sensors::V2_1::subhal::implementation::AllSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_0>
+ subHal;
#elif defined SUPPORT_CONTINUOUS_SENSORS
- static ::android::hardware::sensors::V2_0::subhal::implementation::ContinuousSensorsSubHal
+ static ::android::hardware::sensors::V2_1::subhal::implementation::ContinuousSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_0>
subHal;
#elif defined SUPPORT_ON_CHANGE_SENSORS
- static ::android::hardware::sensors::V2_0::subhal::implementation::OnChangeSensorsSubHal subHal;
+ static ::android::hardware::sensors::V2_1::subhal::implementation::OnChangeSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_0>
+ subHal;
#else
- static ::android::hardware::sensors::V2_0::subhal::implementation::SensorsSubHal subHal;
+ static ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_0>
+ subHal;
#endif // defined SUPPORT_CONTINUOUS_SENSORS && defined SUPPORT_ON_CHANGE_SENSORS
*version = SUB_HAL_2_0_VERSION;
return &subHal;
}
+#else // SUB_HAL_VERSION_2_0
+
+::android::hardware::sensors::V2_1::implementation::ISensorsSubHal* sensorsHalGetSubHal_2_1(
+ uint32_t* version) {
+#if defined SUPPORT_CONTINUOUS_SENSORS && defined SUPPORT_ON_CHANGE_SENSORS
+ static ::android::hardware::sensors::V2_1::subhal::implementation::AllSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_1>
+ subHal;
+#elif defined SUPPORT_CONTINUOUS_SENSORS
+ static ::android::hardware::sensors::V2_1::subhal::implementation::ContinuousSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_1>
+ subHal;
+#elif defined SUPPORT_ON_CHANGE_SENSORS
+ static ::android::hardware::sensors::V2_1::subhal::implementation::OnChangeSensorsSubHal<
+ ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_1>
+ subHal;
+#else
+ static ::android::hardware::sensors::V2_1::subhal::implementation::SensorsSubHalV2_1 subHal;
+#endif // defined SUPPORT_CONTINUOUS_SENSORS && defined SUPPORT_ON_CHANGE_SENSORS
+ *version = SUB_HAL_2_1_VERSION;
+ return &subHal;
+}
+
+#endif // SUB_HAL_VERSION_2_0
+
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace subhal {
namespace implementation {
using ::android::hardware::Void;
-using ::android::hardware::sensors::V1_0::Event;
using ::android::hardware::sensors::V1_0::OperationMode;
using ::android::hardware::sensors::V1_0::RateLevel;
using ::android::hardware::sensors::V1_0::Result;
@@ -50,11 +83,12 @@
using ::android::hardware::sensors::V2_0::SensorTimeout;
using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
using ::android::hardware::sensors::V2_0::implementation::ScopedWakelock;
+using ::android::hardware::sensors::V2_1::Event;
-SensorsSubHal::SensorsSubHal() : mCallback(nullptr), mNextHandle(1) {}
+ISensorsSubHalBase::ISensorsSubHalBase() : mCallback(nullptr), mNextHandle(1) {}
// Methods from ::android::hardware::sensors::V2_0::ISensors follow.
-Return<void> SensorsSubHal::getSensorsList(getSensorsList_cb _hidl_cb) {
+Return<void> ISensorsSubHalBase::getSensorsList(V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb) {
std::vector<SensorInfo> sensors;
for (const auto& sensor : mSensors) {
sensors.push_back(sensor.second->getSensorInfo());
@@ -64,7 +98,7 @@
return Void();
}
-Return<Result> SensorsSubHal::setOperationMode(OperationMode mode) {
+Return<Result> ISensorsSubHalBase::setOperationMode(OperationMode mode) {
for (auto sensor : mSensors) {
sensor.second->setOperationMode(mode);
}
@@ -72,7 +106,7 @@
return Result::OK;
}
-Return<Result> SensorsSubHal::activate(int32_t sensorHandle, bool enabled) {
+Return<Result> ISensorsSubHalBase::activate(int32_t sensorHandle, bool enabled) {
auto sensor = mSensors.find(sensorHandle);
if (sensor != mSensors.end()) {
sensor->second->activate(enabled);
@@ -81,8 +115,8 @@
return Result::BAD_VALUE;
}
-Return<Result> SensorsSubHal::batch(int32_t sensorHandle, int64_t samplingPeriodNs,
- int64_t /* maxReportLatencyNs */) {
+Return<Result> ISensorsSubHalBase::batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+ int64_t /* maxReportLatencyNs */) {
auto sensor = mSensors.find(sensorHandle);
if (sensor != mSensors.end()) {
sensor->second->batch(samplingPeriodNs);
@@ -91,7 +125,7 @@
return Result::BAD_VALUE;
}
-Return<Result> SensorsSubHal::flush(int32_t sensorHandle) {
+Return<Result> ISensorsSubHalBase::flush(int32_t sensorHandle) {
auto sensor = mSensors.find(sensorHandle);
if (sensor != mSensors.end()) {
return sensor->second->flush();
@@ -99,7 +133,7 @@
return Result::BAD_VALUE;
}
-Return<Result> SensorsSubHal::injectSensorData(const Event& event) {
+Return<Result> ISensorsSubHalBase::injectSensorData(const Event& event) {
auto sensor = mSensors.find(event.sensorHandle);
if (sensor != mSensors.end()) {
return sensor->second->injectEvent(event);
@@ -108,24 +142,24 @@
return Result::BAD_VALUE;
}
-Return<void> SensorsSubHal::registerDirectChannel(const SharedMemInfo& /* mem */,
- registerDirectChannel_cb _hidl_cb) {
+Return<void> ISensorsSubHalBase::registerDirectChannel(
+ const SharedMemInfo& /* mem */, V2_0::ISensors::registerDirectChannel_cb _hidl_cb) {
_hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */);
return Return<void>();
}
-Return<Result> SensorsSubHal::unregisterDirectChannel(int32_t /* channelHandle */) {
+Return<Result> ISensorsSubHalBase::unregisterDirectChannel(int32_t /* channelHandle */) {
return Result::INVALID_OPERATION;
}
-Return<void> SensorsSubHal::configDirectReport(int32_t /* sensorHandle */,
- int32_t /* channelHandle */, RateLevel /* rate */,
- configDirectReport_cb _hidl_cb) {
+Return<void> ISensorsSubHalBase::configDirectReport(
+ int32_t /* sensorHandle */, int32_t /* channelHandle */, RateLevel /* rate */,
+ V2_0::ISensors::configDirectReport_cb _hidl_cb) {
_hidl_cb(Result::INVALID_OPERATION, 0 /* reportToken */);
return Return<void>();
}
-Return<void> SensorsSubHal::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) {
+Return<void> ISensorsSubHalBase::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) {
if (fd.getNativeHandle() == nullptr || fd->numFds < 1) {
ALOGE("%s: missing fd for writing", __FUNCTION__);
return Void();
@@ -156,44 +190,18 @@
return Return<void>();
}
-Return<Result> SensorsSubHal::initialize(const sp<IHalProxyCallback>& halProxyCallback) {
- mCallback = halProxyCallback;
+Return<Result> ISensorsSubHalBase::initialize(
+ std::unique_ptr<IHalProxyCallbackWrapperBase>& halProxyCallback) {
+ mCallback = std::move(halProxyCallback);
setOperationMode(OperationMode::NORMAL);
return Result::OK;
}
-void SensorsSubHal::postEvents(const std::vector<Event>& events, bool wakeup) {
+void ISensorsSubHalBase::postEvents(const std::vector<Event>& events, bool wakeup) {
ScopedWakelock wakelock = mCallback->createScopedWakelock(wakeup);
mCallback->postEvents(events, std::move(wakelock));
}
-ContinuousSensorsSubHal::ContinuousSensorsSubHal() {
- AddSensor<AccelSensor>();
- AddSensor<GyroSensor>();
- AddSensor<MagnetometerSensor>();
- AddSensor<PressureSensor>();
- AddSensor<DeviceTempSensor>();
-}
-
-OnChangeSensorsSubHal::OnChangeSensorsSubHal() {
- AddSensor<AmbientTempSensor>();
- AddSensor<LightSensor>();
- AddSensor<ProximitySensor>();
- AddSensor<RelativeHumiditySensor>();
-}
-
-AllSensorsSubHal::AllSensorsSubHal() {
- AddSensor<AccelSensor>();
- AddSensor<GyroSensor>();
- AddSensor<MagnetometerSensor>();
- AddSensor<PressureSensor>();
- AddSensor<DeviceTempSensor>();
- AddSensor<AmbientTempSensor>();
- AddSensor<LightSensor>();
- AddSensor<ProximitySensor>();
- AddSensor<RelativeHumiditySensor>();
-}
-
Return<Result> SetOperationModeFailingSensorsSubHal::setOperationMode(OperationMode /*mode*/) {
return Result::BAD_VALUE;
}
@@ -206,7 +214,7 @@
sensorInfo.flags |= V1_0::SensorFlagBits::MASK_DIRECT_REPORT;
sensors.push_back(sensorInfo);
}
- _hidl_cb(sensors);
+ _hidl_cb(V2_1::implementation::convertToOldSensorInfos(sensors));
return Void();
}
@@ -218,7 +226,7 @@
sensorInfo.flags &= ~static_cast<uint32_t>(V1_0::SensorFlagBits::MASK_DIRECT_REPORT);
sensors.push_back(sensorInfo);
}
- _hidl_cb(sensors);
+ _hidl_cb(V2_1::implementation::convertToOldSensorInfos(sensors));
return Void();
}
@@ -234,7 +242,7 @@
} // namespace implementation
} // namespace subhal
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.h b/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.h
index 6da4404..1a78e84 100644
--- a/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.h
+++ b/sensors/common/default/2.X/multihal/tests/fake_subhal/SensorsSubHal.h
@@ -17,7 +17,9 @@
#pragma once
#include "V2_0/SubHal.h"
+#include "V2_1/SubHal.h"
+#include "IHalProxyCallbackWrapper.h"
#include "Sensor.h"
#include <vector>
@@ -25,54 +27,54 @@
namespace android {
namespace hardware {
namespace sensors {
-namespace V2_0 {
+namespace V2_1 {
namespace subhal {
namespace implementation {
using ::android::hardware::sensors::V1_0::OperationMode;
using ::android::hardware::sensors::V1_0::Result;
-using ::android::hardware::sensors::V2_0::implementation::IHalProxyCallback;
/**
* Implementation of a ISensorsSubHal that can be used to test the implementation of multihal 2.0.
* See the README file for more details on how this class can be used for testing.
*/
-class SensorsSubHal : public ISensorsSubHal, public ISensorsEventCallback {
- using Event = ::android::hardware::sensors::V1_0::Event;
+class ISensorsSubHalBase : public ISensorsEventCallback {
+ protected:
+ using Event = ::android::hardware::sensors::V2_1::Event;
using RateLevel = ::android::hardware::sensors::V1_0::RateLevel;
using SharedMemInfo = ::android::hardware::sensors::V1_0::SharedMemInfo;
public:
- SensorsSubHal();
+ ISensorsSubHalBase();
+
+ Return<void> getSensorsList(V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb);
+ Return<Result> injectSensorData(const Event& event);
+ Return<Result> initialize(std::unique_ptr<IHalProxyCallbackWrapperBase>& halProxyCallback);
// Methods from ::android::hardware::sensors::V2_0::ISensors follow.
- virtual Return<void> getSensorsList(getSensorsList_cb _hidl_cb) override;
-
- virtual Return<Result> setOperationMode(OperationMode mode) override;
+ virtual Return<Result> setOperationMode(OperationMode mode);
OperationMode getOperationMode() const { return mCurrentOperationMode; }
- Return<Result> activate(int32_t sensorHandle, bool enabled) override;
+ Return<Result> activate(int32_t sensorHandle, bool enabled);
Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
- int64_t maxReportLatencyNs) override;
+ int64_t maxReportLatencyNs);
- Return<Result> flush(int32_t sensorHandle) override;
-
- Return<Result> injectSensorData(const Event& event) override;
+ Return<Result> flush(int32_t sensorHandle);
Return<void> registerDirectChannel(const SharedMemInfo& mem,
- registerDirectChannel_cb _hidl_cb) override;
+ V2_0::ISensors::registerDirectChannel_cb _hidl_cb);
- Return<Result> unregisterDirectChannel(int32_t channelHandle) override;
+ Return<Result> unregisterDirectChannel(int32_t channelHandle);
Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle, RateLevel rate,
- configDirectReport_cb _hidl_cb) override;
+ V2_0::ISensors::configDirectReport_cb _hidl_cb);
- Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) override;
+ Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args);
// Methods from ::android::hardware::sensors::V2_0::implementation::ISensorsSubHal follow.
- const std::string getName() override {
+ const std::string getName() {
#ifdef SUB_HAL_NAME
return SUB_HAL_NAME;
#else // SUB_HAL_NAME
@@ -80,8 +82,6 @@
#endif // SUB_HAL_NAME
}
- Return<Result> initialize(const sp<IHalProxyCallback>& halProxyCallback) override;
-
// Method from ISensorsEventCallback.
void postEvents(const std::vector<Event>& events, bool wakeup) override;
@@ -103,7 +103,7 @@
* disconnected, sensor events need to be sent to the framework, and when a wakelock should be
* acquired.
*/
- sp<IHalProxyCallback> mCallback;
+ std::unique_ptr<IHalProxyCallbackWrapperBase> mCallback;
private:
/**
@@ -118,40 +118,143 @@
int32_t mNextHandle;
};
-// SubHal that has continuous sensors for testing purposes.
-class ContinuousSensorsSubHal : public SensorsSubHal {
+template <class SubHalClass>
+class SensorsSubHalBase : public ISensorsSubHalBase, public SubHalClass {
public:
- ContinuousSensorsSubHal();
+ Return<Result> setOperationMode(OperationMode mode) override {
+ return ISensorsSubHalBase::setOperationMode(mode);
+ }
+
+ Return<Result> activate(int32_t sensorHandle, bool enabled) override {
+ return ISensorsSubHalBase::activate(sensorHandle, enabled);
+ }
+
+ Return<Result> batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+ int64_t maxReportLatencyNs) override {
+ return ISensorsSubHalBase::batch(sensorHandle, samplingPeriodNs, maxReportLatencyNs);
+ }
+
+ Return<Result> flush(int32_t sensorHandle) override {
+ return ISensorsSubHalBase::flush(sensorHandle);
+ }
+
+ Return<void> registerDirectChannel(const SharedMemInfo& mem,
+ V2_0::ISensors::registerDirectChannel_cb _hidl_cb) override {
+ return ISensorsSubHalBase::registerDirectChannel(mem, _hidl_cb);
+ }
+
+ Return<Result> unregisterDirectChannel(int32_t channelHandle) override {
+ return ISensorsSubHalBase::unregisterDirectChannel(channelHandle);
+ }
+
+ Return<void> configDirectReport(int32_t sensorHandle, int32_t channelHandle, RateLevel rate,
+ V2_0::ISensors::configDirectReport_cb _hidl_cb) override {
+ return ISensorsSubHalBase::configDirectReport(sensorHandle, channelHandle, rate, _hidl_cb);
+ }
+
+ Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& args) override {
+ return ISensorsSubHalBase::debug(fd, args);
+ }
+
+ const std::string getName() override { return ISensorsSubHalBase::getName(); }
+};
+
+class SensorsSubHalV2_0 : public SensorsSubHalBase<V2_0::implementation::ISensorsSubHal> {
+ public:
+ virtual Return<void> getSensorsList(V2_0::ISensors::getSensorsList_cb _hidl_cb) override {
+ return ISensorsSubHalBase::getSensorsList([&](const auto& list) {
+ _hidl_cb(V2_1::implementation::convertToOldSensorInfos(list));
+ });
+ }
+
+ Return<Result> injectSensorData(const V1_0::Event& event) override {
+ return ISensorsSubHalBase::injectSensorData(V2_1::implementation::convertToNewEvent(event));
+ }
+
+ Return<Result> initialize(
+ const sp<V2_0::implementation::IHalProxyCallback>& halProxyCallback) override {
+ std::unique_ptr<IHalProxyCallbackWrapperBase> wrapper =
+ std::make_unique<HalProxyCallbackWrapperV2_0>(halProxyCallback);
+ return ISensorsSubHalBase::initialize(wrapper);
+ }
+};
+
+class SensorsSubHalV2_1 : public SensorsSubHalBase<V2_1::implementation::ISensorsSubHal> {
+ public:
+ Return<void> getSensorsList_2_1(V2_1::ISensors::getSensorsList_2_1_cb _hidl_cb) override {
+ return ISensorsSubHalBase::getSensorsList(_hidl_cb);
+ }
+
+ Return<Result> injectSensorData_2_1(const V2_1::Event& event) override {
+ return ISensorsSubHalBase::injectSensorData(event);
+ }
+
+ Return<Result> initialize(
+ const sp<V2_1::implementation::IHalProxyCallback>& halProxyCallback) override {
+ std::unique_ptr<IHalProxyCallbackWrapperBase> wrapper =
+ std::make_unique<HalProxyCallbackWrapperV2_1>(halProxyCallback);
+ return ISensorsSubHalBase::initialize(wrapper);
+ }
+};
+
+// SubHal that has continuous sensors for testing purposes.
+template <class SubHalVersion>
+class ContinuousSensorsSubHal : public SubHalVersion {
+ public:
+ ContinuousSensorsSubHal() {
+ ISensorsSubHalBase::AddSensor<AccelSensor>();
+ ISensorsSubHalBase::AddSensor<GyroSensor>();
+ ISensorsSubHalBase::AddSensor<MagnetometerSensor>();
+ ISensorsSubHalBase::AddSensor<PressureSensor>();
+ ISensorsSubHalBase::AddSensor<DeviceTempSensor>();
+ }
};
// SubHal that has on-change sensors for testing purposes.
-class OnChangeSensorsSubHal : public SensorsSubHal {
+template <class SubHalVersion>
+class OnChangeSensorsSubHal : public SubHalVersion {
public:
- OnChangeSensorsSubHal();
+ OnChangeSensorsSubHal() {
+ ISensorsSubHalBase::AddSensor<AmbientTempSensor>();
+ ISensorsSubHalBase::AddSensor<LightSensor>();
+ ISensorsSubHalBase::AddSensor<ProximitySensor>();
+ ISensorsSubHalBase::AddSensor<RelativeHumiditySensor>();
+ }
};
// SubHal that has both continuous and on-change sensors for testing purposes.
-class AllSensorsSubHal : public SensorsSubHal {
+template <class SubHalVersion>
+class AllSensorsSubHal : public SubHalVersion {
public:
- AllSensorsSubHal();
+ AllSensorsSubHal() {
+ ISensorsSubHalBase::AddSensor<AccelSensor>();
+ ISensorsSubHalBase::AddSensor<GyroSensor>();
+ ISensorsSubHalBase::AddSensor<MagnetometerSensor>();
+ ISensorsSubHalBase::AddSensor<PressureSensor>();
+ ISensorsSubHalBase::AddSensor<DeviceTempSensor>();
+ ISensorsSubHalBase::AddSensor<AmbientTempSensor>();
+ ISensorsSubHalBase::AddSensor<LightSensor>();
+ ISensorsSubHalBase::AddSensor<ProximitySensor>();
+ ISensorsSubHalBase::AddSensor<RelativeHumiditySensor>();
+ }
};
-class SetOperationModeFailingSensorsSubHal : public AllSensorsSubHal {
+class SetOperationModeFailingSensorsSubHal : public AllSensorsSubHal<SensorsSubHalV2_0> {
public:
Return<Result> setOperationMode(OperationMode mode) override;
};
-class AllSupportDirectChannelSensorsSubHal : public AllSensorsSubHal {
+class AllSupportDirectChannelSensorsSubHal : public AllSensorsSubHal<SensorsSubHalV2_0> {
public:
- Return<void> getSensorsList(getSensorsList_cb _hidl_cb) override;
+ Return<void> getSensorsList(V2_0::ISensors::getSensorsList_cb _hidl_cb) override;
};
-class DoesNotSupportDirectChannelSensorsSubHal : public AllSensorsSubHal {
+class DoesNotSupportDirectChannelSensorsSubHal : public AllSensorsSubHal<SensorsSubHalV2_0> {
public:
- Return<void> getSensorsList(getSensorsList_cb _hidl_cb) override;
+ Return<void> getSensorsList(V2_0::ISensors::getSensorsList_cb _hidl_cb) override;
};
-class AddAndRemoveDynamicSensorsSubHal : public AllSensorsSubHal {
+class AddAndRemoveDynamicSensorsSubHal : public AllSensorsSubHal<SensorsSubHalV2_0> {
public:
void addDynamicSensors(const std::vector<SensorInfo>& sensorsAdded);
void removeDynamicSensors(const std::vector<int32_t>& sensorHandlesAdded);
@@ -159,7 +262,7 @@
} // namespace implementation
} // namespace subhal
-} // namespace V2_0
+} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android
diff --git a/sensors/common/utils/EventMessageQueueWrapper.h b/sensors/common/utils/EventMessageQueueWrapper.h
index bf3261f..c4f92c8 100644
--- a/sensors/common/utils/EventMessageQueueWrapper.h
+++ b/sensors/common/utils/EventMessageQueueWrapper.h
@@ -39,8 +39,14 @@
virtual std::atomic<uint32_t>* getEventFlagWord() = 0;
virtual size_t availableToRead() = 0;
+ virtual size_t availableToWrite() = 0;
virtual bool read(V2_1::Event* events, size_t numToRead) = 0;
+ virtual bool write(const V2_1::Event* events, size_t numToWrite) = 0;
virtual bool write(const std::vector<V2_1::Event>& events) = 0;
+ virtual bool writeBlocking(const V2_1::Event* events, size_t count, uint32_t readNotification,
+ uint32_t writeNotification, int64_t timeOutNanos,
+ android::hardware::EventFlag* evFlag) = 0;
+ virtual size_t getQuantumCount() = 0;
};
class EventMessageQueueWrapperV1_0 : public EventMessageQueueWrapperBase {
@@ -60,15 +66,30 @@
virtual size_t availableToRead() override { return mQueue->availableToRead(); }
+ size_t availableToWrite() override { return mQueue->availableToWrite(); }
+
virtual bool read(V2_1::Event* events, size_t numToRead) override {
return mQueue->read(reinterpret_cast<V1_0::Event*>(events), numToRead);
}
+ bool write(const V2_1::Event* events, size_t numToWrite) override {
+ return mQueue->write(reinterpret_cast<const V1_0::Event*>(events), numToWrite);
+ }
+
virtual bool write(const std::vector<V2_1::Event>& events) override {
const std::vector<V1_0::Event>& oldEvents = convertToOldEvents(events);
return mQueue->write(oldEvents.data(), oldEvents.size());
}
+ bool writeBlocking(const V2_1::Event* events, size_t count, uint32_t readNotification,
+ uint32_t writeNotification, int64_t timeOutNanos,
+ android::hardware::EventFlag* evFlag) override {
+ return mQueue->writeBlocking(reinterpret_cast<const V1_0::Event*>(events), count,
+ readNotification, writeNotification, timeOutNanos, evFlag);
+ }
+
+ size_t getQuantumCount() override { return mQueue->getQuantumCount(); }
+
private:
std::unique_ptr<EventMessageQueue> mQueue;
};
@@ -88,14 +109,29 @@
virtual size_t availableToRead() override { return mQueue->availableToRead(); }
+ size_t availableToWrite() override { return mQueue->availableToWrite(); }
+
virtual bool read(V2_1::Event* events, size_t numToRead) override {
return mQueue->read(events, numToRead);
}
+ bool write(const V2_1::Event* events, size_t numToWrite) override {
+ return mQueue->write(events, numToWrite);
+ }
+
bool write(const std::vector<V2_1::Event>& events) override {
return mQueue->write(events.data(), events.size());
}
+ bool writeBlocking(const V2_1::Event* events, size_t count, uint32_t readNotification,
+ uint32_t writeNotification, int64_t timeOutNanos,
+ android::hardware::EventFlag* evFlag) override {
+ return mQueue->writeBlocking(events, count, readNotification, writeNotification,
+ timeOutNanos, evFlag);
+ }
+
+ size_t getQuantumCount() override { return mQueue->getQuantumCount(); }
+
private:
std::unique_ptr<EventMessageQueue> mQueue;
};
diff --git a/sensors/common/utils/ISensorsCallbackWrapper.h b/sensors/common/utils/ISensorsCallbackWrapper.h
new file mode 100644
index 0000000..816b225
--- /dev/null
+++ b/sensors/common/utils/ISensorsCallbackWrapper.h
@@ -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.
+ */
+
+#ifndef ANDROID_HARDWARE_SENSORS_V2_1_ISENSORSCALLBACKWRAPPER_H
+#define ANDROID_HARDWARE_SENSORS_V2_1_ISENSORSCALLBACKWRAPPER_H
+
+#include "convertV2_1.h"
+
+#include "android/hardware/sensors/1.0/ISensors.h"
+#include "android/hardware/sensors/1.0/types.h"
+#include "android/hardware/sensors/2.0/ISensors.h"
+#include "android/hardware/sensors/2.0/ISensorsCallback.h"
+#include "android/hardware/sensors/2.1/ISensors.h"
+#include "android/hardware/sensors/2.1/ISensorsCallback.h"
+#include "android/hardware/sensors/2.1/types.h"
+
+#include <utils/LightRefBase.h>
+
+#include <cassert>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace V2_1 {
+namespace implementation {
+
+/**
+ * The ISensorsCallbackWrapper classes below abstract away the common logic between both the V2.0
+ * and V2.1 versions of the Sensors HAL interface. This allows users of these classes to only care
+ * about the HAL version at init time and then interact with either version of the callback without
+ * worrying about the class type by utilizing the base class.
+ */
+class ISensorsCallbackWrapperBase : public VirtualLightRefBase {
+ public:
+ virtual Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V2_1::SensorInfo>& sensorInfos) = 0;
+
+ virtual Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& sensorHandles) = 0;
+};
+
+template <typename T>
+class SensorsCallbackWrapperBase : public ISensorsCallbackWrapperBase {
+ public:
+ SensorsCallbackWrapperBase(sp<T> sensorsCallback) : mSensorsCallback(sensorsCallback){};
+
+ virtual Return<void> onDynamicSensorsConnected(
+ const hidl_vec<V2_1::SensorInfo>& sensorInfos) override {
+ return mSensorsCallback->onDynamicSensorsConnected(convertToOldSensorInfos(sensorInfos));
+ }
+
+ Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& sensorHandles) {
+ return mSensorsCallback->onDynamicSensorsDisconnected(sensorHandles);
+ }
+
+ protected:
+ sp<T> mSensorsCallback;
+};
+
+class ISensorsCallbackWrapperV2_0
+ : public SensorsCallbackWrapperBase<hardware::sensors::V2_0::ISensorsCallback> {
+ public:
+ ISensorsCallbackWrapperV2_0(sp<hardware::sensors::V2_0::ISensorsCallback> sensorsCallback)
+ : SensorsCallbackWrapperBase(sensorsCallback){};
+};
+
+class ISensorsCallbackWrapperV2_1
+ : public SensorsCallbackWrapperBase<hardware::sensors::V2_1::ISensorsCallback> {
+ public:
+ ISensorsCallbackWrapperV2_1(sp<hardware::sensors::V2_1::ISensorsCallback> sensorsCallback)
+ : SensorsCallbackWrapperBase(sensorsCallback) {}
+
+ Return<void> onDynamicSensorsConnected(const hidl_vec<V2_1::SensorInfo>& sensorInfos) override {
+ return mSensorsCallback->onDynamicSensorsConnected_2_1(sensorInfos);
+ }
+};
+
+} // namespace implementation
+} // namespace V2_1
+} // namespace sensors
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_SENSORS_V2_1_ISENSORSCALLBACKWRAPPER_H
\ No newline at end of file
diff --git a/sensors/common/vts/2_X/VtsHalSensorsV2_XTargetTest.h b/sensors/common/vts/2_X/VtsHalSensorsV2_XTargetTest.h
index 75f2c28..2e5aca4 100644
--- a/sensors/common/vts/2_X/VtsHalSensorsV2_XTargetTest.h
+++ b/sensors/common/vts/2_X/VtsHalSensorsV2_XTargetTest.h
@@ -367,11 +367,13 @@
<< s.sensorHandle << std::dec << " type=" << static_cast<int>(s.type)
<< " name=" << s.name);
- // Test non-empty type string
- EXPECT_FALSE(s.typeAsString.empty());
-
- // Test defined type matches defined string type
- EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+ // Test type string non-empty only for private sensor types.
+ if (s.type >= SensorTypeVersion::DEVICE_PRIVATE_BASE) {
+ EXPECT_FALSE(s.typeAsString.empty());
+ } else if (!s.typeAsString.empty()) {
+ // Test type string matches framework string if specified for non-private types.
+ EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+ }
// Test if all sensor has name and vendor
EXPECT_FALSE(s.name.empty());
@@ -450,6 +452,10 @@
for (const auto& s : sensors) {
auto events = callback.getEvents(s.sensorHandle);
auto lastEvent = events.back();
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << s.sensorHandle << std::dec << " type=" << static_cast<int>(s.type)
+ << " name=" << s.name);
// Verify that only a single event has been received
ASSERT_EQ(events.size(), 1);
@@ -576,6 +582,12 @@
// Flush the sensor
for (int32_t i = 0; i < flushCalls; i++) {
+ SCOPED_TRACE(::testing::Message()
+ << "Flush " << i << "/" << flushCalls << ": "
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec
+ << " type=" << static_cast<int>(sensor.type) << " name=" << sensor.name);
+
Result flushResult = flush(sensor.sensorHandle);
ASSERT_EQ(flushResult, expectedResponse);
}
@@ -593,6 +605,10 @@
// Check that the correct number of flushes are present for each sensor
for (const SensorInfoType& sensor : sensors) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
ASSERT_EQ(callback.getFlushCount(sensor.sensorHandle), expectedFlushCount);
}
}
@@ -641,6 +657,11 @@
activateAllSensors(false /* enable */);
for (const SensorInfoType& sensor : getSensorsList()) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
+
// Call batch on inactive sensor
// One shot sensors have minDelay set to -1 which is an invalid
// parameter. Use 0 instead to avoid errors.
@@ -673,6 +694,11 @@
// Verify that sensor events are generated when activate is called
for (const SensorInfoType& sensor : getSensorsList()) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
+
batch(sensor.sensorHandle, sensor.minDelay, 0 /* maxReportLatencyNs */);
ASSERT_EQ(activate(sensor.sensorHandle, true), Result::OK);
@@ -720,6 +746,10 @@
// Save the last received event for each sensor
std::map<int32_t, int64_t> lastEventTimestampMap;
for (const SensorInfoType& sensor : sensors) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
// Some on-change sensors may not report an event without stimulus
if (extractReportMode(sensor.flags) != SensorFlagBits::ON_CHANGE_MODE) {
ASSERT_GE(callback.getEvents(sensor.sensorHandle).size(), 1);
@@ -740,6 +770,11 @@
getEnvironment()->unregisterCallback();
for (const SensorInfoType& sensor : sensors) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
+
// Skip sensors that did not previously report an event
if (lastEventTimestampMap.find(sensor.sensorHandle) == lastEventTimestampMap.end()) {
continue;
@@ -762,6 +797,12 @@
RateLevel rateLevel) {
configDirectReport(sensor.sensorHandle, directChannelHandle, rateLevel,
[&](Result result, int32_t reportToken) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8)
+ << std::setfill('0') << sensor.sensorHandle << std::dec
+ << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
+
if (isDirectReportRateSupported(sensor, rateLevel)) {
ASSERT_EQ(result, Result::OK);
if (rateLevel != RateLevel::STOP) {
@@ -819,6 +860,11 @@
void SensorsHidlTest::verifyConfigure(const SensorInfoType& sensor, SharedMemType memType,
int32_t directChannelHandle, bool supportsAnyDirectChannel) {
+ SCOPED_TRACE(::testing::Message()
+ << " handle=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << sensor.sensorHandle << std::dec << " type=" << static_cast<int>(sensor.type)
+ << " name=" << sensor.name);
+
if (isDirectChannelTypeSupported(sensor, memType)) {
// Verify that each rate level is properly supported
checkRateLevel(sensor, directChannelHandle, RateLevel::NORMAL);
diff --git a/tests/lazy/1.0/.hidl_for_test b/tests/lazy/1.0/.hidl_for_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/lazy/1.0/.hidl_for_test
diff --git a/tests/lazy/1.0/Android.bp b/tests/lazy/1.0/Android.bp
new file mode 100644
index 0000000..d2f8175
--- /dev/null
+++ b/tests/lazy/1.0/Android.bp
@@ -0,0 +1,14 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.tests.lazy@1.0",
+ root: "android.hardware",
+ system_ext_specific: true,
+ srcs: [
+ "ILazy.hal",
+ ],
+ interfaces: [
+ "android.hidl.base@1.0",
+ ],
+ gen_java: true,
+}
diff --git a/tests/lazy/1.0/ILazy.hal b/tests/lazy/1.0/ILazy.hal
new file mode 100644
index 0000000..b0be48e
--- /dev/null
+++ b/tests/lazy/1.0/ILazy.hal
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.tests.lazy@1.0;
+
+interface ILazy {};
diff --git a/tv/tuner/1.0/default/Demux.cpp b/tv/tuner/1.0/default/Demux.cpp
index 43c4e3a..4e5ae4b 100644
--- a/tv/tuner/1.0/default/Demux.cpp
+++ b/tv/tuner/1.0/default/Demux.cpp
@@ -52,7 +52,7 @@
mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId);
- return startFrontendInputLoop();
+ return Result::SUCCESS;
}
Return<void> Demux::openFilter(const DemuxFilterType& type, uint32_t bufferSize,
@@ -60,7 +60,6 @@
ALOGV("%s", __FUNCTION__);
uint32_t filterId;
-
if (!mUnusedFilterIds.empty()) {
filterId = *mUnusedFilterIds.begin();
@@ -72,7 +71,7 @@
mUsedFilterIds.insert(filterId);
if (cb == nullptr) {
- ALOGW("callback can't be null");
+ ALOGW("[Demux] callback can't be null");
_hidl_cb(Result::INVALID_ARGUMENT, new Filter());
return Void();
}
@@ -85,8 +84,12 @@
}
mFilters[filterId] = filter;
+ bool result = true;
+ if (mDvr != nullptr && mDvr->getType() == DvrType::PLAYBACK) {
+ result = mDvr->addPlaybackFilter(filter);
+ }
- _hidl_cb(Result::SUCCESS, filter);
+ _hidl_cb(result ? Result::SUCCESS : Result::INVALID_ARGUMENT, filter);
return Void();
}
@@ -132,7 +135,7 @@
ALOGV("%s", __FUNCTION__);
if (cb == nullptr) {
- ALOGW("DVR callback can't be null");
+ ALOGW("[Demux] DVR callback can't be null");
_hidl_cb(Result::INVALID_ARGUMENT, new Dvr());
return Void();
}
@@ -176,11 +179,11 @@
void Demux::startBroadcastTsFilter(vector<uint8_t> data) {
set<uint32_t>::iterator it;
+ uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff));
+ if (DEBUG_DEMUX) {
+ ALOGW("[Demux] start ts filter pid: %d", pid);
+ }
for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) {
- uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff));
- if (DEBUG_FILTER) {
- ALOGW("start ts filter pid: %d", pid);
- }
if (pid == mFilters[*it]->getTpid()) {
mFilters[*it]->updateFilterOutput(data);
}
@@ -189,10 +192,10 @@
void Demux::sendFrontendInputToRecord(vector<uint8_t> data) {
set<uint32_t>::iterator it;
+ if (DEBUG_DEMUX) {
+ ALOGW("[Demux] update record filter output");
+ }
for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
- if (DEBUG_FILTER) {
- ALOGW("update record filter output");
- }
mFilters[*it]->updateRecordOutput(data);
}
}
@@ -234,11 +237,9 @@
return mFilters[filterId]->getTpid();
}
-Result Demux::startFrontendInputLoop() {
+void Demux::startFrontendInputLoop() {
pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this);
pthread_setname_np(mFrontendInputThread, "frontend_input_thread");
-
- return Result::SUCCESS;
}
void* Demux::__threadLoopFrontend(void* user) {
diff --git a/tv/tuner/1.0/default/Demux.h b/tv/tuner/1.0/default/Demux.h
index 1405d0c..3c91daf 100644
--- a/tv/tuner/1.0/default/Demux.h
+++ b/tv/tuner/1.0/default/Demux.h
@@ -89,6 +89,7 @@
void updateFilterOutput(uint16_t filterId, vector<uint8_t> data);
uint16_t getFilterTpid(uint32_t filterId);
void setIsRecording(bool isRecording);
+ void startFrontendInputLoop();
private:
// Tuner service
@@ -104,7 +105,6 @@
uint32_t filterId;
};
- Result startFrontendInputLoop();
static void* __threadLoopFrontend(void* user);
void frontendInputThreadLoop();
@@ -188,7 +188,7 @@
int mPesSizeLeft = 0;
vector<uint8_t> mPesOutput;
- const bool DEBUG_FILTER = false;
+ const bool DEBUG_DEMUX = false;
};
} // namespace implementation
diff --git a/tv/tuner/1.0/default/Dvr.cpp b/tv/tuner/1.0/default/Dvr.cpp
index 3088a9d..adb2635 100644
--- a/tv/tuner/1.0/default/Dvr.cpp
+++ b/tv/tuner/1.0/default/Dvr.cpp
@@ -71,13 +71,10 @@
}
// check if the attached filter is a record filter
-
mFilters[filterId] = filter;
- mIsRecordFilterAttached = true;
if (!mDemux->attachRecordFilter(filterId)) {
return Result::INVALID_ARGUMENT;
}
- mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached);
return Result::SUCCESS;
}
@@ -110,7 +107,6 @@
// If all the filters are detached, record can't be started
if (mFilters.empty()) {
mIsRecordFilterAttached = false;
- mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached);
}
return Result::SUCCESS;
@@ -132,8 +128,7 @@
pthread_setname_np(mDvrThread, "playback_waiting_loop");
} else if (mType == DvrType::RECORD) {
mRecordStatus = RecordStatus::DATA_READY;
- mIsRecordStarted = true;
- mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached);
+ mDemux->setIsRecording(mType == DvrType::RECORD);
}
// TODO start another thread to send filter status callback to the framework
@@ -149,7 +144,7 @@
std::lock_guard<std::mutex> lock(mDvrThreadLock);
mIsRecordStarted = false;
- mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached);
+ mDemux->setIsRecording(false);
return Result::SUCCESS;
}
@@ -175,7 +170,7 @@
std::unique_ptr<DvrMQ> tmpDvrMQ =
std::unique_ptr<DvrMQ>(new (std::nothrow) DvrMQ(mBufferSize, true));
if (!tmpDvrMQ->isValid()) {
- ALOGW("Failed to create FMQ of DVR");
+ ALOGW("[Dvr] Failed to create FMQ of DVR");
return false;
}
@@ -256,7 +251,6 @@
int playbackPacketSize = mDvrSettings.playback().packetSize;
vector<uint8_t> dataOutputBuffer;
dataOutputBuffer.resize(playbackPacketSize);
-
// Dispatch the packet to the PID matching filter output buffer
for (int i = 0; i < size / playbackPacketSize; i++) {
if (!mDvrMQ->read(dataOutputBuffer.data(), playbackPacketSize)) {
@@ -283,7 +277,6 @@
bool Dvr::startFilterDispatcher() {
std::map<uint32_t, sp<IFilter>>::iterator it;
-
// Handle the output data per filter type
for (it = mFilters.begin(); it != mFilters.end(); it++) {
if (mDemux->startFilterHandler(it->first) != Result::SUCCESS) {
@@ -296,7 +289,10 @@
bool Dvr::writeRecordFMQ(const std::vector<uint8_t>& data) {
std::lock_guard<std::mutex> lock(mWriteLock);
- ALOGW("[Dvr] write record FMQ");
+ if (mRecordStatus == RecordStatus::OVERFLOW) {
+ ALOGW("[Dvr] stops writing and wait for the client side flushing.");
+ return true;
+ }
if (mDvrMQ->write(data.data(), data.size())) {
mDvrEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
maySendRecordStatusCallback();
@@ -333,6 +329,27 @@
return mRecordStatus;
}
+bool Dvr::addPlaybackFilter(sp<IFilter> filter) {
+ uint32_t filterId;
+ Result status;
+
+ filter->getId([&](Result result, uint32_t id) {
+ filterId = id;
+ status = result;
+ });
+
+ if (status != Result::SUCCESS) {
+ return false;
+ }
+
+ mFilters[filterId] = filter;
+ return true;
+}
+
+DvrType Dvr::getType() {
+ return mType;
+}
+
} // namespace implementation
} // namespace V1_0
} // namespace tuner
diff --git a/tv/tuner/1.0/default/Dvr.h b/tv/tuner/1.0/default/Dvr.h
index f39d8db..08afd5d 100644
--- a/tv/tuner/1.0/default/Dvr.h
+++ b/tv/tuner/1.0/default/Dvr.h
@@ -81,6 +81,8 @@
bool createDvrMQ();
void sendBroadcastInputToDvrRecord(vector<uint8_t> byteBuffer);
bool writeRecordFMQ(const std::vector<uint8_t>& data);
+ DvrType getType();
+ bool addPlaybackFilter(sp<IFilter> filter);
private:
// Demux service
diff --git a/tv/tuner/1.0/default/Filter.cpp b/tv/tuner/1.0/default/Filter.cpp
index f610c60..fef7a35 100644
--- a/tv/tuner/1.0/default/Filter.cpp
+++ b/tv/tuner/1.0/default/Filter.cpp
@@ -73,18 +73,22 @@
switch (mType.mainType) {
case DemuxFilterMainType::TS:
mTpid = settings.ts().tpid;
+ if (mType.subType.tsFilterType() == DemuxTsFilterType::AUDIO ||
+ mType.subType.tsFilterType() == DemuxTsFilterType::VIDEO) {
+ mIsMediaFilter = true;
+ }
break;
case DemuxFilterMainType::MMTP:
- /*mmtpSettings*/
+ if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO ||
+ mType.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) {
+ mIsMediaFilter = true;
+ }
break;
case DemuxFilterMainType::IP:
- /*ipSettings*/
break;
case DemuxFilterMainType::TLV:
- /*tlvSettings*/
break;
case DemuxFilterMainType::ALP:
- /*alpSettings*/
break;
default:
break;
@@ -145,7 +149,7 @@
std::unique_ptr<FilterMQ> tmpFilterMQ =
std::unique_ptr<FilterMQ>(new (std::nothrow) FilterMQ(mBufferSize, true));
if (!tmpFilterMQ->isValid()) {
- ALOGW("Failed to create FMQ of filter with id: %d", mFilterId);
+ ALOGW("[Filter] Failed to create FMQ of filter with id: %d", mFilterId);
return false;
}
@@ -241,9 +245,7 @@
}
void Filter::freeAvHandle() {
- if (mType.mainType != DemuxFilterMainType::TS ||
- (mType.subType.tsFilterType() == DemuxTsFilterType::AUDIO &&
- mType.subType.tsFilterType() == DemuxTsFilterType::VIDEO)) {
+ if (!mIsMediaFilter) {
return;
}
for (int i = 0; i < mFilterEvent.events.size(); i++) {
@@ -288,13 +290,11 @@
void Filter::updateFilterOutput(vector<uint8_t> data) {
std::lock_guard<std::mutex> lock(mFilterOutputLock);
- ALOGD("[Filter] filter output updated");
mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end());
}
void Filter::updateRecordOutput(vector<uint8_t> data) {
std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
- ALOGD("[Filter] record filter output updated");
mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end());
}
@@ -436,7 +436,6 @@
if (mFilterOutput.empty()) {
return Result::SUCCESS;
}
-
for (int i = 0; i < mFilterOutput.size(); i += 188) {
if (mPesSizeLeft == 0) {
uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) |
diff --git a/tv/tuner/1.0/default/Filter.h b/tv/tuner/1.0/default/Filter.h
index afed98e..9b49ad8 100644
--- a/tv/tuner/1.0/default/Filter.h
+++ b/tv/tuner/1.0/default/Filter.h
@@ -103,6 +103,7 @@
uint32_t mFilterId;
uint32_t mBufferSize;
DemuxFilterType mType;
+ bool mIsMediaFilter = false;
DemuxFilterSettings mFilterSettings;
uint16_t mTpid;
diff --git a/tv/tuner/1.0/default/Frontend.cpp b/tv/tuner/1.0/default/Frontend.cpp
index 2cff9be..996b6ef 100644
--- a/tv/tuner/1.0/default/Frontend.cpp
+++ b/tv/tuner/1.0/default/Frontend.cpp
@@ -64,6 +64,7 @@
return Result::INVALID_STATE;
}
+ mTunerService->frontendStartTune(mId);
mCallback->onEvent(FrontendEventType::LOCKED);
mIsLocked = false;
return Result::SUCCESS;
@@ -81,6 +82,13 @@
Return<Result> Frontend::scan(const FrontendSettings& settings, FrontendScanType type) {
ALOGV("%s", __FUNCTION__);
+ if (mType == FrontendType::ATSC) {
+ FrontendScanMessage msg;
+ msg.isLocked(true);
+ mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg);
+ mIsLocked = true;
+ return Result::SUCCESS;
+ }
if (mType != FrontendType::DVBT) {
return Result::UNAVAILABLE;
}
@@ -131,6 +139,30 @@
status.snr(221);
break;
}
+ case FrontendStatusType::BER: {
+ status.ber(1);
+ break;
+ }
+ case FrontendStatusType::PER: {
+ status.per(2);
+ break;
+ }
+ case FrontendStatusType::PRE_BER: {
+ status.preBer(3);
+ break;
+ }
+ case FrontendStatusType::SIGNAL_QUALITY: {
+ status.signalQuality(4);
+ break;
+ }
+ case FrontendStatusType::SIGNAL_STRENGTH: {
+ status.signalStrength(5);
+ break;
+ }
+ case FrontendStatusType::SYMBOL_RATE: {
+ status.symbolRate(6);
+ break;
+ }
case FrontendStatusType::FEC: {
status.innerFec(FrontendInnerFec::FEC_2_9); // value = 1 << 7
break;
@@ -141,15 +173,51 @@
status.modulation(modulationStatus);
break;
}
+ case FrontendStatusType::SPECTRAL: {
+ status.inversion(FrontendDvbcSpectralInversion::NORMAL);
+ break;
+ }
+ case FrontendStatusType::LNB_VOLTAGE: {
+ status.lnbVoltage(LnbVoltage::VOLTAGE_5V);
+ break;
+ }
case FrontendStatusType::PLP_ID: {
status.plpId(101); // type uint8_t
break;
}
+ case FrontendStatusType::EWBS: {
+ status.isEWBS(false);
+ break;
+ }
+ case FrontendStatusType::AGC: {
+ status.agc(7);
+ break;
+ }
+ case FrontendStatusType::LNA: {
+ status.isLnaOn(false);
+ break;
+ }
case FrontendStatusType::LAYER_ERROR: {
vector<bool> v = {false, true, true};
status.isLayerError(v);
break;
}
+ case FrontendStatusType::MER: {
+ status.mer(8);
+ break;
+ }
+ case FrontendStatusType::FREQ_OFFSET: {
+ status.freqOffset(9);
+ break;
+ }
+ case FrontendStatusType::HIERARCHY: {
+ status.hierarchy(FrontendDvbtHierarchy::HIERARCHY_1_NATIVE);
+ break;
+ }
+ case FrontendStatusType::RF_LOCK: {
+ status.isRfLocked(false);
+ break;
+ }
case FrontendStatusType::ATSC3_PLP_INFO: {
vector<FrontendStatusAtsc3PlpInfo> v;
FrontendStatusAtsc3PlpInfo info1{
diff --git a/tv/tuner/1.0/default/Frontend.h b/tv/tuner/1.0/default/Frontend.h
index 8a30b91..65537d7 100644
--- a/tv/tuner/1.0/default/Frontend.h
+++ b/tv/tuner/1.0/default/Frontend.h
@@ -76,7 +76,7 @@
FrontendId mId = 0;
bool mIsLocked = false;
- const string FRONTEND_STREAM_FILE = "/vendor/etc/dumpTs3.ts";
+ const string FRONTEND_STREAM_FILE = "/vendor/etc/segment000000.ts";
std::ifstream mFrontendData;
};
diff --git a/tv/tuner/1.0/default/Lnb.cpp b/tv/tuner/1.0/default/Lnb.cpp
index 51931d6..6025339 100644
--- a/tv/tuner/1.0/default/Lnb.cpp
+++ b/tv/tuner/1.0/default/Lnb.cpp
@@ -27,6 +27,9 @@
namespace implementation {
Lnb::Lnb() {}
+Lnb::Lnb(int id) {
+ mId = id;
+}
Lnb::~Lnb() {}
@@ -66,9 +69,13 @@
return Result::SUCCESS;
}
+int Lnb::getId() {
+ return mId;
+}
+
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/tv/tuner/1.0/default/Lnb.h b/tv/tuner/1.0/default/Lnb.h
index f285cb9..1e97214 100644
--- a/tv/tuner/1.0/default/Lnb.h
+++ b/tv/tuner/1.0/default/Lnb.h
@@ -38,6 +38,7 @@
class Lnb : public ILnb {
public:
Lnb();
+ Lnb(int id);
virtual Return<Result> setCallback(const sp<ILnbCallback>& callback) override;
@@ -51,7 +52,10 @@
virtual Return<Result> close() override;
+ int getId();
+
private:
+ int mId;
virtual ~Lnb();
};
@@ -62,4 +66,4 @@
} // namespace hardware
} // namespace android
-#endif // ANDROID_HARDWARE_TV_TUNER_V1_0_LNB_H_
\ No newline at end of file
+#endif // ANDROID_HARDWARE_TV_TUNER_V1_0_LNB_H_
diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp
index e39333c..821d83f 100644
--- a/tv/tuner/1.0/default/Tuner.cpp
+++ b/tv/tuner/1.0/default/Tuner.cpp
@@ -88,6 +88,10 @@
caps = FrontendInfo::FrontendCapabilities();
caps.atscCaps(FrontendAtscCapabilities());
mFrontendCaps[7] = caps;
+
+ mLnbs.resize(2);
+ mLnbs[0] = new Lnb(0);
+ mLnbs[1] = new Lnb(1);
}
Tuner::~Tuner() {}
@@ -157,34 +161,27 @@
return Void();
}
- switch (mFrontends[frontendId]->getFrontendType()) {
- case FrontendType::DVBT:
- info.type = FrontendType::DVBT;
- break;
- default:
- vector<FrontendStatusType> statusCaps = {
- FrontendStatusType::DEMOD_LOCK,
- FrontendStatusType::SNR,
- FrontendStatusType::FEC,
- FrontendStatusType::MODULATION,
- FrontendStatusType::PLP_ID,
- FrontendStatusType::LAYER_ERROR,
- FrontendStatusType::ATSC3_PLP_INFO,
- };
- // assign randomly selected values for testing.
- info = {
- .type = mFrontends[frontendId]->getFrontendType(),
- .minFrequency = 139,
- .maxFrequency = 1139,
- .minSymbolRate = 45,
- .maxSymbolRate = 1145,
- .acquireRange = 30,
- .exclusiveGroupId = 57,
- .statusCaps = statusCaps,
- .frontendCaps = mFrontendCaps[frontendId],
- };
- break;
- }
+ vector<FrontendStatusType> statusCaps = {
+ FrontendStatusType::DEMOD_LOCK,
+ FrontendStatusType::SNR,
+ FrontendStatusType::FEC,
+ FrontendStatusType::MODULATION,
+ FrontendStatusType::PLP_ID,
+ FrontendStatusType::LAYER_ERROR,
+ FrontendStatusType::ATSC3_PLP_INFO,
+ };
+ // assign randomly selected values for testing.
+ info = {
+ .type = mFrontends[frontendId]->getFrontendType(),
+ .minFrequency = 139,
+ .maxFrequency = 1139,
+ .minSymbolRate = 45,
+ .maxSymbolRate = 1145,
+ .acquireRange = 30,
+ .exclusiveGroupId = 57,
+ .statusCaps = statusCaps,
+ .frontendCaps = mFrontendCaps[frontendId],
+ };
_hidl_cb(Result::SUCCESS, info);
return Void();
@@ -194,17 +191,24 @@
ALOGV("%s", __FUNCTION__);
vector<LnbId> lnbIds;
+ lnbIds.resize(mLnbs.size());
+ for (int i = 0; i < lnbIds.size(); i++) {
+ lnbIds[i] = mLnbs[i]->getId();
+ }
_hidl_cb(Result::SUCCESS, lnbIds);
return Void();
}
-Return<void> Tuner::openLnbById(LnbId /* lnbId */, openLnbById_cb _hidl_cb) {
+Return<void> Tuner::openLnbById(LnbId lnbId, openLnbById_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
- sp<ILnb> lnb = new Lnb();
+ if (lnbId >= mLnbs.size()) {
+ _hidl_cb(Result::INVALID_ARGUMENT, nullptr);
+ return Void();
+ }
- _hidl_cb(Result::SUCCESS, lnb);
+ _hidl_cb(Result::SUCCESS, mLnbs[lnbId]);
return Void();
}
@@ -236,6 +240,15 @@
}
}
+void Tuner::frontendStartTune(uint32_t frontendId) {
+ map<uint32_t, uint32_t>::iterator it = mFrontendToDemux.find(frontendId);
+ uint32_t demuxId;
+ if (it != mFrontendToDemux.end()) {
+ demuxId = it->second;
+ mDemuxes[demuxId]->startFrontendInputLoop();
+ }
+}
+
} // namespace implementation
} // namespace V1_0
} // namespace tuner
diff --git a/tv/tuner/1.0/default/Tuner.h b/tv/tuner/1.0/default/Tuner.h
index d17b5ae..5de568f 100644
--- a/tv/tuner/1.0/default/Tuner.h
+++ b/tv/tuner/1.0/default/Tuner.h
@@ -21,6 +21,7 @@
#include <map>
#include "Demux.h"
#include "Frontend.h"
+#include "Lnb.h"
using namespace std;
@@ -62,6 +63,7 @@
void setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId);
+ void frontendStartTune(uint32_t frontendId);
void frontendStopTune(uint32_t frontendId);
private:
@@ -76,6 +78,7 @@
// The last used demux id. Initial value is -1.
// First used id will be 0.
int mLastUsedId = -1;
+ vector<sp<Lnb>> mLnbs;
};
} // namespace implementation
diff --git a/tv/tuner/1.0/vts/functional/Android.bp b/tv/tuner/1.0/vts/functional/Android.bp
index 448575e..b152a29 100644
--- a/tv/tuner/1.0/vts/functional/Android.bp
+++ b/tv/tuner/1.0/vts/functional/Android.bp
@@ -22,6 +22,7 @@
"FrontendTests.cpp",
"DemuxTests.cpp",
"FilterTests.cpp",
+ "DvrTests.cpp",
],
static_libs: [
"android.hardware.tv.tuner@1.0",
diff --git a/tv/tuner/1.0/vts/functional/DemuxTests.h b/tv/tuner/1.0/vts/functional/DemuxTests.h
index a72c09f..6e1e395 100644
--- a/tv/tuner/1.0/vts/functional/DemuxTests.h
+++ b/tv/tuner/1.0/vts/functional/DemuxTests.h
@@ -38,8 +38,6 @@
class DemuxTests {
public:
- sp<ITuner> mService;
-
void setService(sp<ITuner> tuner) { mService = tuner; }
AssertionResult openDemux(sp<IDemux>& demux, uint32_t& demuxId);
@@ -51,5 +49,6 @@
static AssertionResult success() { return ::testing::AssertionSuccess(); }
+ sp<ITuner> mService;
sp<IDemux> mDemux;
};
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp
new file mode 100644
index 0000000..7e7f8e6
--- /dev/null
+++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp
@@ -0,0 +1,265 @@
+/*
+ * 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 "DvrTests.h"
+
+void DvrCallback::startPlaybackInputThread(string& dataInputFile, PlaybackSettings& settings,
+ MQDesc& playbackMQDescriptor) {
+ mInputDataFile = dataInputFile;
+ mPlaybackSettings = settings;
+ mPlaybackMQ = std::make_unique<FilterMQ>(playbackMQDescriptor, true /* resetPointers */);
+ EXPECT_TRUE(mPlaybackMQ);
+ pthread_create(&mPlaybackThread, NULL, __threadLoopPlayback, this);
+ pthread_setname_np(mPlaybackThread, "test_playback_input_loop");
+}
+
+void DvrCallback::stopPlaybackThread() {
+ mPlaybackThreadRunning = false;
+ mKeepWritingPlaybackFMQ = false;
+
+ android::Mutex::Autolock autoLock(mPlaybackThreadLock);
+}
+
+void* DvrCallback::__threadLoopPlayback(void* user) {
+ DvrCallback* const self = static_cast<DvrCallback*>(user);
+ self->playbackThreadLoop();
+ return 0;
+}
+
+void DvrCallback::playbackThreadLoop() {
+ android::Mutex::Autolock autoLock(mPlaybackThreadLock);
+ mPlaybackThreadRunning = true;
+
+ // Create the EventFlag that is used to signal the HAL impl that data have been
+ // written into the Playback FMQ
+ EventFlag* playbackMQEventFlag;
+ EXPECT_TRUE(EventFlag::createEventFlag(mPlaybackMQ->getEventFlagWord(), &playbackMQEventFlag) ==
+ android::OK);
+
+ // open the stream and get its length
+ std::ifstream inputData(mInputDataFile.c_str(), std::ifstream::binary);
+ int writeSize = mPlaybackSettings.packetSize * 6;
+ char* buffer = new char[writeSize];
+ ALOGW("[vts] playback thread loop start %s!", mInputDataFile.c_str());
+ if (!inputData.is_open()) {
+ mPlaybackThreadRunning = false;
+ ALOGW("[vts] Error %s", strerror(errno));
+ }
+
+ while (mPlaybackThreadRunning) {
+ // move the stream pointer for packet size * 6 every read until the end
+ while (mKeepWritingPlaybackFMQ) {
+ inputData.read(buffer, writeSize);
+ if (!inputData) {
+ int leftSize = inputData.gcount();
+ if (leftSize == 0) {
+ mPlaybackThreadRunning = false;
+ break;
+ }
+ inputData.clear();
+ inputData.read(buffer, leftSize);
+ // Write the left over of the input data and quit the thread
+ if (leftSize > 0) {
+ EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], leftSize));
+ playbackMQEventFlag->wake(
+ static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
+ }
+ mPlaybackThreadRunning = false;
+ break;
+ }
+ // Write input FMQ and notify the Tuner Implementation
+ EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], writeSize));
+ playbackMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
+ inputData.seekg(writeSize, inputData.cur);
+ sleep(1);
+ }
+ }
+
+ ALOGW("[vts] Playback thread end.");
+
+ delete[] buffer;
+ inputData.close();
+}
+
+void DvrCallback::testRecordOutput() {
+ android::Mutex::Autolock autoLock(mMsgLock);
+ while (mDataOutputBuffer.empty()) {
+ if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) {
+ EXPECT_TRUE(false) << "record output matching pid does not output within timeout";
+ stopRecordThread();
+ return;
+ }
+ }
+ stopRecordThread();
+ ALOGW("[vts] record pass and stop");
+}
+
+void DvrCallback::startRecordOutputThread(RecordSettings recordSettings,
+ MQDesc& recordMQDescriptor) {
+ mRecordMQ = std::make_unique<FilterMQ>(recordMQDescriptor, true /* resetPointers */);
+ EXPECT_TRUE(mRecordMQ);
+ struct RecordThreadArgs* threadArgs =
+ (struct RecordThreadArgs*)malloc(sizeof(struct RecordThreadArgs));
+ threadArgs->user = this;
+ threadArgs->recordSettings = &recordSettings;
+ threadArgs->keepReadingRecordFMQ = &mKeepReadingRecordFMQ;
+
+ pthread_create(&mRecordThread, NULL, __threadLoopRecord, (void*)threadArgs);
+ pthread_setname_np(mRecordThread, "test_record_input_loop");
+}
+
+void* DvrCallback::__threadLoopRecord(void* threadArgs) {
+ DvrCallback* const self =
+ static_cast<DvrCallback*>(((struct RecordThreadArgs*)threadArgs)->user);
+ self->recordThreadLoop(((struct RecordThreadArgs*)threadArgs)->recordSettings,
+ ((struct RecordThreadArgs*)threadArgs)->keepReadingRecordFMQ);
+ return 0;
+}
+
+void DvrCallback::recordThreadLoop(RecordSettings* /*recordSettings*/, bool* keepReadingRecordFMQ) {
+ ALOGD("[vts] DvrCallback record threadLoop start.");
+ android::Mutex::Autolock autoLock(mRecordThreadLock);
+ mRecordThreadRunning = true;
+ mKeepReadingRecordFMQ = true;
+
+ // Create the EventFlag that is used to signal the HAL impl that data have been
+ // read from the Record FMQ
+ EventFlag* recordMQEventFlag;
+ EXPECT_TRUE(EventFlag::createEventFlag(mRecordMQ->getEventFlagWord(), &recordMQEventFlag) ==
+ android::OK);
+
+ while (mRecordThreadRunning) {
+ while (*keepReadingRecordFMQ) {
+ uint32_t efState = 0;
+ android::status_t status = recordMQEventFlag->wait(
+ static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
+ true /* retry on spurious wake */);
+ if (status != android::OK) {
+ ALOGD("[vts] wait for data ready on the record FMQ");
+ continue;
+ }
+ // 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");
+ mRecordThreadRunning = false;
+ break;
+ }
+ }
+ }
+
+ mRecordThreadRunning = false;
+ ALOGD("[vts] record thread ended.");
+}
+
+bool DvrCallback::readRecordFMQ() {
+ android::Mutex::Autolock autoLock(mMsgLock);
+ bool result = false;
+ mDataOutputBuffer.clear();
+ mDataOutputBuffer.resize(mRecordMQ->availableToRead());
+ result = mRecordMQ->read(mDataOutputBuffer.data(), mRecordMQ->availableToRead());
+ EXPECT_TRUE(result) << "can't read from Record MQ";
+ mMsgCondition.signal();
+ return result;
+}
+
+void DvrCallback::stopRecordThread() {
+ mKeepReadingRecordFMQ = false;
+ mRecordThreadRunning = false;
+}
+
+AssertionResult DvrTests::openDvrInDemux(DvrType type, uint32_t bufferSize) {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+
+ // Create dvr callback
+ mDvrCallback = new DvrCallback();
+
+ mDemux->openDvr(type, bufferSize, mDvrCallback, [&](Result result, const sp<IDvr>& dvr) {
+ mDvr = dvr;
+ status = result;
+ });
+
+ if (status == Result::SUCCESS) {
+ mDvrCallback->setDvr(mDvr);
+ }
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::configDvr(DvrSettings setting) {
+ Result status = mDvr->configure(setting);
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::getDvrMQDescriptor() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+
+ mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) {
+ mDvrMQDescriptor = dvrMQDesc;
+ status = result;
+ });
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::attachFilterToDvr(sp<IFilter> filter) {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+
+ status = mDvr->attachFilter(filter);
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::detachFilterToDvr(sp<IFilter> filter) {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+
+ status = mDvr->detachFilter(filter);
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::startDvr() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+
+ status = mDvr->start();
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+AssertionResult DvrTests::stopDvr() {
+ Result status;
+ EXPECT_TRUE(mDemux) << "Test with openDemux first.";
+ EXPECT_TRUE(mDvr) << "Test with openDvr first.";
+
+ status = mDvr->stop();
+
+ return AssertionResult(status == Result::SUCCESS);
+}
+
+void DvrTests::closeDvr() {
+ ASSERT_TRUE(mDemux);
+ ASSERT_TRUE(mDvr);
+ ASSERT_TRUE(mDvr->close() == Result::SUCCESS);
+}
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.h b/tv/tuner/1.0/vts/functional/DvrTests.h
new file mode 100644
index 0000000..dd00c27
--- /dev/null
+++ b/tv/tuner/1.0/vts/functional/DvrTests.h
@@ -0,0 +1,185 @@
+/*
+ * 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 <VtsHalHidlTargetTestBase.h>
+#include <VtsHalHidlTargetTestEnvBase.h>
+#include <android-base/logging.h>
+#include <android/hardware/tv/tuner/1.0/IDvr.h>
+#include <android/hardware/tv/tuner/1.0/IDvrCallback.h>
+#include <android/hardware/tv/tuner/1.0/ITuner.h>
+#include <android/hardware/tv/tuner/1.0/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/Status.h>
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+#include <fstream>
+#include <iostream>
+#include <map>
+
+#include "FilterTests.h"
+
+using android::Condition;
+using android::Mutex;
+using android::sp;
+using android::hardware::EventFlag;
+using android::hardware::kSynchronizedReadWrite;
+using android::hardware::MessageQueue;
+using android::hardware::MQDescriptorSync;
+using android::hardware::Return;
+using android::hardware::Void;
+using android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
+using android::hardware::tv::tuner::V1_0::DvrSettings;
+using android::hardware::tv::tuner::V1_0::DvrType;
+using android::hardware::tv::tuner::V1_0::IDvr;
+using android::hardware::tv::tuner::V1_0::IDvrCallback;
+using android::hardware::tv::tuner::V1_0::ITuner;
+using android::hardware::tv::tuner::V1_0::PlaybackSettings;
+using android::hardware::tv::tuner::V1_0::PlaybackStatus;
+using android::hardware::tv::tuner::V1_0::RecordSettings;
+using android::hardware::tv::tuner::V1_0::RecordStatus;
+using android::hardware::tv::tuner::V1_0::Result;
+
+#define WAIT_TIMEOUT 3000000000
+
+class DvrCallback : public IDvrCallback {
+ public:
+ virtual Return<void> onRecordStatus(DemuxFilterStatus status) override {
+ ALOGD("[vts] record status %hhu", status);
+ switch (status) {
+ case DemuxFilterStatus::DATA_READY:
+ break;
+ case DemuxFilterStatus::LOW_WATER:
+ break;
+ case DemuxFilterStatus::HIGH_WATER:
+ case DemuxFilterStatus::OVERFLOW:
+ ALOGD("[vts] record overflow. Flushing.");
+ EXPECT_TRUE(mDvr) << "Dvr callback is not set with an IDvr";
+ if (mDvr) {
+ Result result = mDvr->flush();
+ ALOGD("[vts] Flushing result %d.", result);
+ }
+ break;
+ }
+ return Void();
+ }
+
+ virtual Return<void> onPlaybackStatus(PlaybackStatus status) override {
+ // android::Mutex::Autolock autoLock(mMsgLock);
+ ALOGD("[vts] playback status %d", status);
+ switch (status) {
+ case PlaybackStatus::SPACE_EMPTY:
+ case PlaybackStatus::SPACE_ALMOST_EMPTY:
+ ALOGD("[vts] keep playback inputing %d", status);
+ mKeepWritingPlaybackFMQ = true;
+ break;
+ case PlaybackStatus::SPACE_ALMOST_FULL:
+ case PlaybackStatus::SPACE_FULL:
+ ALOGD("[vts] stop playback inputing %d", status);
+ mKeepWritingPlaybackFMQ = false;
+ break;
+ }
+ return Void();
+ }
+
+ void stopPlaybackThread();
+ void testRecordOutput();
+ void stopRecordThread();
+
+ void startPlaybackInputThread(string& dataInputFile, PlaybackSettings& settings,
+ MQDesc& playbackMQDescriptor);
+ void startRecordOutputThread(RecordSettings recordSettings, MQDesc& recordMQDescriptor);
+ static void* __threadLoopPlayback(void* user);
+ static void* __threadLoopRecord(void* threadArgs);
+ void playbackThreadLoop();
+ void recordThreadLoop(RecordSettings* recordSetting, bool* keepWritingPlaybackFMQ);
+
+ bool readRecordFMQ();
+
+ void setDvr(sp<IDvr> dvr) { mDvr = dvr; }
+
+ private:
+ struct RecordThreadArgs {
+ DvrCallback* user;
+ RecordSettings* recordSettings;
+ bool* keepReadingRecordFMQ;
+ };
+ // uint16_t mDataLength = 0;
+ std::vector<uint8_t> mDataOutputBuffer;
+
+ std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterMQ;
+ std::unique_ptr<FilterMQ> mPlaybackMQ;
+ std::unique_ptr<FilterMQ> mRecordMQ;
+ std::map<uint32_t, EventFlag*> mFilterMQEventFlag;
+
+ android::Mutex mMsgLock;
+ android::Mutex mPlaybackThreadLock;
+ android::Mutex mRecordThreadLock;
+ android::Condition mMsgCondition;
+
+ bool mKeepWritingPlaybackFMQ = true;
+ bool mKeepReadingRecordFMQ = true;
+ bool mPlaybackThreadRunning;
+ bool mRecordThreadRunning;
+ pthread_t mPlaybackThread;
+ pthread_t mRecordThread;
+ string mInputDataFile;
+ PlaybackSettings mPlaybackSettings;
+
+ sp<IDvr> mDvr = nullptr;
+
+ // int mPidFilterOutputCount = 0;
+};
+
+class DvrTests {
+ public:
+ void setService(sp<ITuner> tuner) { mService = tuner; }
+ void setDemux(sp<IDemux> demux) { mDemux = demux; }
+
+ void startPlaybackInputThread(string& dataInputFile, PlaybackSettings& settings) {
+ mDvrCallback->startPlaybackInputThread(dataInputFile, settings, mDvrMQDescriptor);
+ };
+
+ void startRecordOutputThread(RecordSettings settings) {
+ mDvrCallback->startRecordOutputThread(settings, mDvrMQDescriptor);
+ };
+
+ void stopPlaybackThread() { mDvrCallback->stopPlaybackThread(); }
+ void testRecordOutput() { mDvrCallback->testRecordOutput(); }
+ void stopRecordThread() { mDvrCallback->stopPlaybackThread(); }
+
+ AssertionResult openDvrInDemux(DvrType type, uint32_t bufferSize);
+ AssertionResult configDvr(DvrSettings setting);
+ AssertionResult getDvrMQDescriptor();
+ AssertionResult attachFilterToDvr(sp<IFilter> filter);
+ AssertionResult detachFilterToDvr(sp<IFilter> filter);
+ AssertionResult stopDvr();
+ AssertionResult startDvr();
+ void closeDvr();
+
+ protected:
+ static AssertionResult failure() { return ::testing::AssertionFailure(); }
+
+ static AssertionResult success() { return ::testing::AssertionSuccess(); }
+
+ sp<ITuner> mService;
+ sp<IDvr> mDvr;
+ sp<IDemux> mDemux;
+ sp<DvrCallback> mDvrCallback;
+ MQDesc mDvrMQDescriptor;
+
+ pthread_t mPlaybackshread;
+ bool mPlaybackThreadRunning;
+};
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp
index 82e955d..4639e59 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp
@@ -128,7 +128,7 @@
return true;
}
-AssertionResult FilterTests::openFilterInDemux(DemuxFilterType type) {
+AssertionResult FilterTests::openFilterInDemux(DemuxFilterType type, uint32_t bufferSize) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
@@ -136,7 +136,7 @@
mFilterCallback = new FilterCallback();
// Add filter to the local demux
- mDemux->openFilter(type, FMQ_SIZE_16M, mFilterCallback,
+ mDemux->openFilter(type, bufferSize, mFilterCallback,
[&](Result result, const sp<IFilter>& filter) {
mFilter = filter;
status = result;
@@ -223,4 +223,4 @@
mFilters.erase(filterId);
}
return AssertionResult(status == Result::SUCCESS);
-}
\ No newline at end of file
+}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h
index 3cc06e5..71efce4 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.h
+++ b/tv/tuner/1.0/vts/functional/FilterTests.h
@@ -78,9 +78,6 @@
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
using MQDesc = MQDescriptorSync<uint8_t>;
-const uint32_t FMQ_SIZE_1M = 0x100000;
-const uint32_t FMQ_SIZE_16M = 0x1000000;
-
#define WAIT_TIMEOUT 3000000000
class FilterCallback : public IFilterCallback {
@@ -149,10 +146,11 @@
public:
void setService(sp<ITuner> tuner) { mService = tuner; }
void setDemux(sp<IDemux> demux) { mDemux = demux; }
+ sp<IFilter> getFilterById(uint32_t filterId) { return mFilters[filterId]; }
std::map<uint32_t, sp<FilterCallback>> getFilterCallbacks() { return mFilterCallbacks; }
- AssertionResult openFilterInDemux(DemuxFilterType type);
+ AssertionResult openFilterInDemux(DemuxFilterType type, uint32_t bufferSize);
AssertionResult getNewlyOpenedFilterId(uint32_t& filterId);
AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId);
AssertionResult getFilterMQDescriptor(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 d836c26..c44f77d 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -18,180 +18,7 @@
namespace {
-/******************************** Start DvrCallback **********************************/
-void DvrCallback::startPlaybackInputThread(PlaybackConf playbackConf,
- MQDesc& playbackMQDescriptor) {
- mPlaybackMQ = std::make_unique<FilterMQ>(playbackMQDescriptor, true /* resetPointers */);
- EXPECT_TRUE(mPlaybackMQ);
- struct PlaybackThreadArgs* threadArgs =
- (struct PlaybackThreadArgs*)malloc(sizeof(struct PlaybackThreadArgs));
- threadArgs->user = this;
- threadArgs->playbackConf = &playbackConf;
- threadArgs->keepWritingPlaybackFMQ = &mKeepWritingPlaybackFMQ;
-
- pthread_create(&mPlaybackThread, NULL, __threadLoopPlayback, (void*)threadArgs);
- pthread_setname_np(mPlaybackThread, "test_playback_input_loop");
-}
-
-void DvrCallback::stopPlaybackThread() {
- mPlaybackThreadRunning = false;
- mKeepWritingPlaybackFMQ = false;
-
- android::Mutex::Autolock autoLock(mPlaybackThreadLock);
-}
-
-void* DvrCallback::__threadLoopPlayback(void* threadArgs) {
- DvrCallback* const self =
- static_cast<DvrCallback*>(((struct PlaybackThreadArgs*)threadArgs)->user);
- self->playbackThreadLoop(((struct PlaybackThreadArgs*)threadArgs)->playbackConf,
- ((struct PlaybackThreadArgs*)threadArgs)->keepWritingPlaybackFMQ);
- return 0;
-}
-
-void DvrCallback::playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWritingPlaybackFMQ) {
- android::Mutex::Autolock autoLock(mPlaybackThreadLock);
- mPlaybackThreadRunning = true;
-
- // Create the EventFlag that is used to signal the HAL impl that data have been
- // written into the Playback FMQ
- EventFlag* playbackMQEventFlag;
- EXPECT_TRUE(EventFlag::createEventFlag(mPlaybackMQ->getEventFlagWord(), &playbackMQEventFlag) ==
- android::OK);
-
- // open the stream and get its length
- std::ifstream inputData(playbackConf->inputDataFile, std::ifstream::binary);
- int writeSize = playbackConf->setting.packetSize * 6;
- char* buffer = new char[writeSize];
- ALOGW("[vts] playback thread loop start %s", playbackConf->inputDataFile.c_str());
- if (!inputData.is_open()) {
- mPlaybackThreadRunning = false;
- ALOGW("[vts] Error %s", strerror(errno));
- }
-
- while (mPlaybackThreadRunning) {
- // move the stream pointer for packet size * 6 every read until the end
- while (*keepWritingPlaybackFMQ) {
- inputData.read(buffer, writeSize);
- if (!inputData) {
- int leftSize = inputData.gcount();
- if (leftSize == 0) {
- mPlaybackThreadRunning = false;
- break;
- }
- inputData.clear();
- inputData.read(buffer, leftSize);
- // Write the left over of the input data and quit the thread
- if (leftSize > 0) {
- EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], leftSize));
- playbackMQEventFlag->wake(
- static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
- }
- mPlaybackThreadRunning = false;
- break;
- }
- // Write input FMQ and notify the Tuner Implementation
- EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], writeSize));
- playbackMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
- inputData.seekg(writeSize, inputData.cur);
- sleep(1);
- }
- }
-
- ALOGW("[vts] Playback thread end.");
-
- delete[] buffer;
- inputData.close();
-}
-
-void DvrCallback::testRecordOutput() {
- android::Mutex::Autolock autoLock(mMsgLock);
- while (mDataOutputBuffer.empty()) {
- if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) {
- EXPECT_TRUE(false) << "record output matching pid does not output within timeout";
- return;
- }
- }
- stopRecordThread();
- ALOGW("[vts] record pass and stop");
-}
-
-void DvrCallback::startRecordOutputThread(RecordSettings recordSetting,
- MQDesc& recordMQDescriptor) {
- mRecordMQ = std::make_unique<FilterMQ>(recordMQDescriptor, true /* resetPointers */);
- EXPECT_TRUE(mRecordMQ);
- struct RecordThreadArgs* threadArgs =
- (struct RecordThreadArgs*)malloc(sizeof(struct RecordThreadArgs));
- threadArgs->user = this;
- threadArgs->recordSetting = &recordSetting;
- threadArgs->keepReadingRecordFMQ = &mKeepReadingRecordFMQ;
-
- pthread_create(&mRecordThread, NULL, __threadLoopRecord, (void*)threadArgs);
- pthread_setname_np(mRecordThread, "test_record_input_loop");
-}
-
-void* DvrCallback::__threadLoopRecord(void* threadArgs) {
- DvrCallback* const self =
- static_cast<DvrCallback*>(((struct RecordThreadArgs*)threadArgs)->user);
- self->recordThreadLoop(((struct RecordThreadArgs*)threadArgs)->recordSetting,
- ((struct RecordThreadArgs*)threadArgs)->keepReadingRecordFMQ);
- return 0;
-}
-
-void DvrCallback::recordThreadLoop(RecordSettings* /*recordSetting*/, bool* keepReadingRecordFMQ) {
- ALOGD("[vts] DvrCallback record threadLoop start.");
- android::Mutex::Autolock autoLock(mRecordThreadLock);
- mRecordThreadRunning = true;
-
- // Create the EventFlag that is used to signal the HAL impl that data have been
- // read from the Record FMQ
- EventFlag* recordMQEventFlag;
- EXPECT_TRUE(EventFlag::createEventFlag(mRecordMQ->getEventFlagWord(), &recordMQEventFlag) ==
- android::OK);
-
- while (mRecordThreadRunning) {
- while (*keepReadingRecordFMQ) {
- uint32_t efState = 0;
- android::status_t status = recordMQEventFlag->wait(
- static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
- true /* retry on spurious wake */);
- if (status != android::OK) {
- ALOGD("[vts] wait for data ready on the record FMQ");
- continue;
- }
- // 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");
- mRecordThreadRunning = false;
- break;
- }
- }
- }
-
- mRecordThreadRunning = false;
- ALOGD("[vts] record thread ended.");
-}
-
-bool DvrCallback::readRecordFMQ() {
- android::Mutex::Autolock autoLock(mMsgLock);
- bool result = false;
- mDataOutputBuffer.clear();
- mDataOutputBuffer.resize(mRecordMQ->availableToRead());
- result = mRecordMQ->read(mDataOutputBuffer.data(), mRecordMQ->availableToRead());
- EXPECT_TRUE(result) << "can't read from Record MQ";
- mMsgCondition.signal();
- return result;
-}
-
-void DvrCallback::stopRecordThread() {
- mKeepReadingRecordFMQ = false;
- mRecordThreadRunning = false;
- android::Mutex::Autolock autoLock(mRecordThreadLock);
-}
-/********************************** End DvrCallback ************************************/
-
-/*======================== Start Descrambler APIs Tests Implementation ========================*/
-AssertionResult TunerHidlTest::createDescrambler() {
+AssertionResult TunerHidlTest::createDescrambler(uint32_t demuxId) {
Result status;
mService->openDescrambler([&](Result result, const sp<IDescrambler>& descrambler) {
mDescrambler = descrambler;
@@ -201,64 +28,26 @@
return failure();
}
- status = mDescrambler->setDemuxSource(mDemuxId);
+ status = mDescrambler->setDemuxSource(demuxId);
if (status != Result::SUCCESS) {
return failure();
}
// Test if demux source can be set more than once.
- status = mDescrambler->setDemuxSource(mDemuxId);
+ status = mDescrambler->setDemuxSource(demuxId);
return AssertionResult(status == Result::INVALID_STATE);
}
AssertionResult TunerHidlTest::closeDescrambler() {
Result status;
- if (!mDescrambler && createDescrambler() == failure()) {
- return failure();
- }
+ EXPECT_TRUE(mDescrambler);
status = mDescrambler->close();
mDescrambler = nullptr;
return AssertionResult(status == Result::SUCCESS);
}
-/*========================= End Descrambler APIs Tests Implementation =========================*/
-/*============================ Start Dvr APIs Tests Implementation ============================*/
-AssertionResult TunerHidlTest::openDvrInDemux(DvrType type) {
- Result status;
-
- // Create dvr callback
- mDvrCallback = new DvrCallback();
-
- mDemux->openDvr(type, FMQ_SIZE_1M, mDvrCallback, [&](Result result, const sp<IDvr>& dvr) {
- mDvr = dvr;
- status = result;
- });
-
- return AssertionResult(status == Result::SUCCESS);
-}
-
-AssertionResult TunerHidlTest::configDvr(DvrSettings setting) {
- Result status = mDvr->configure(setting);
-
- return AssertionResult(status == Result::SUCCESS);
-}
-
-AssertionResult TunerHidlTest::getDvrMQDescriptor() {
- Result status;
- EXPECT_TRUE(mDvr) << "Test with openDvr first.";
-
- mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) {
- mDvrMQDescriptor = dvrMQDesc;
- status = result;
- });
-
- return AssertionResult(status == Result::SUCCESS);
-}
-/*============================ End Dvr APIs Tests Implementation ============================*/
-
-/*========================== Start Data Flow Tests Implementation ==========================*/
-AssertionResult TunerHidlTest::broadcastDataFlowTest(vector<string> /*goldenOutputFiles*/) {
+AssertionResult TunerBroadcastHidlTest::filterDataOutputTest(vector<string> /*goldenOutputFiles*/) {
// Data Verify Module
std::map<uint32_t, sp<FilterCallback>>::iterator it;
std::map<uint32_t, sp<FilterCallback>> filterCallbacks = mFilterTests.getFilterCallbacks();
@@ -268,155 +57,48 @@
return success();
}
-/*
- * TODO: re-enable the tests after finalizing the test refactoring.
- */
-/*AssertionResult TunerHidlTest::playbackDataFlowTest(
- vector<FilterConf> filterConf, PlaybackConf playbackConf,
- vector<string> \/\*goldenOutputFiles\*\/) {
- Result status;
- int filterIdsSize;
- // Filter Configuration Module
- for (int i = 0; i < filterConf.size(); i++) {
- if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) ==
- failure() ||
- // TODO use a map to save the FMQs/EvenFlags and pass to callback
- getFilterMQDescriptor() == failure()) {
- return failure();
- }
- filterIdsSize = mUsedFilterIds.size();
- mUsedFilterIds.resize(filterIdsSize + 1);
- mUsedFilterIds[filterIdsSize] = mFilterId;
- mFilters[mFilterId] = mFilter;
- mFilterCallbacks[mFilterId] = mFilterCallback;
- mFilterCallback->updateFilterMQ(mFilterMQDescriptor);
- // mDemuxCallback->updateGoldenOutputMap(goldenOutputFiles[i]);
- status = mFilter->start();
- if (status != Result::SUCCESS) {
- return failure();
- }
- }
-
- // Playback Input Module
- PlaybackSettings playbackSetting = playbackConf.setting;
- if (addPlaybackToDemux(playbackSetting) == failure() ||
- getPlaybackMQDescriptor() == failure()) {
- return failure();
- }
- for (int i = 0; i <= filterIdsSize; i++) {
- if (mDvr->attachFilter(mFilters[mUsedFilterIds[i]]) != Result::SUCCESS) {
- return failure();
- }
- }
- mDvrCallback->startPlaybackInputThread(playbackConf, mPlaybackMQDescriptor);
- status = mDvr->start();
- if (status != Result::SUCCESS) {
- return failure();
- }
-
+AssertionResult TunerPlaybackHidlTest::filterDataOutputTest(vector<string> /*goldenOutputFiles*/) {
// Data Verify Module
std::map<uint32_t, sp<FilterCallback>>::iterator it;
- for (it = mFilterCallbacks.begin(); it != mFilterCallbacks.end(); it++) {
+ std::map<uint32_t, sp<FilterCallback>> filterCallbacks = mFilterTests.getFilterCallbacks();
+ for (it = filterCallbacks.begin(); it != filterCallbacks.end(); it++) {
it->second->testFilterDataOutput();
}
- mDvrCallback->stopPlaybackThread();
-
- // Clean Up Module
- for (int i = 0; i <= filterIdsSize; i++) {
- if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) {
- return failure();
- }
- }
- if (mDvr->stop() != Result::SUCCESS) {
- return failure();
- }
- mUsedFilterIds.clear();
- mFilterCallbacks.clear();
- mFilters.clear();
- return closeDemux();
+ return success();
}
-AssertionResult TunerHidlTest::recordDataFlowTest(vector<FilterConf> filterConf,
- RecordSettings recordSetting,
- vector<string> goldenOutputFiles) {
- Result status;
- hidl_vec<FrontendId> feIds;
-
- mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) {
- status = result;
- feIds = frontendIds;
- });
-
- if (feIds.size() == 0) {
- ALOGW("[ WARN ] Frontend isn't available");
- return failure();
- }
-
- FrontendDvbtSettings dvbt{
- .frequency = 1000,
- };
- FrontendSettings settings;
- settings.dvbt(dvbt);
-
- int filterIdsSize;
- // Filter Configuration Module
- for (int i = 0; i < filterConf.size(); i++) {
- if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) ==
- failure() ||
- // TODO use a map to save the FMQs/EvenFlags and pass to callback
- getFilterMQDescriptor() == failure()) {
- return failure();
- }
- filterIdsSize = mUsedFilterIds.size();
- mUsedFilterIds.resize(filterIdsSize + 1);
- mUsedFilterIds[filterIdsSize] = mFilterId;
- mFilters[mFilterId] = mFilter;
- }
-
- // Record Config Module
- if (addRecordToDemux(recordSetting) == failure() ||
- getRecordMQDescriptor() == failure()) {
- return failure();
- }
- for (int i = 0; i <= filterIdsSize; i++) {
- if (mDvr->attachFilter(mFilters[mUsedFilterIds[i]]) != Result::SUCCESS) {
- return failure();
- }
- }
-
- mDvrCallback->startRecordOutputThread(recordSetting, mRecordMQDescriptor);
- status = mDvr->start();
- if (status != Result::SUCCESS) {
- return failure();
- }
-
- if (setDemuxFrontendDataSource(feIds[0]) != success()) {
- return failure();
- }
-
- // Data Verify Module
- mDvrCallback->testRecordOutput();
-
- // Clean Up Module
- for (int i = 0; i <= filterIdsSize; i++) {
- if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) {
- return failure();
- }
- }
- if (mFrontend->stopTune() != Result::SUCCESS) {
- return failure();
- }
- mUsedFilterIds.clear();
- mFilterCallbacks.clear();
- mFilters.clear();
- return closeDemux();
-}*/
-/*========================= End Data Flow Tests Implementation =========================*/
-
-/*================================= Start Test Module =================================*/
-void TunerHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
- FrontendConfig frontendConf) {
+void TunerFilterHidlTest::configSingleFilterInDemuxTest(FilterConfig filterConf,
+ FrontendConfig frontendConf) {
uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ uint32_t filterId;
+
+ mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+ ASSERT_TRUE(feId != INVALID_ID);
+ ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+ ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+ mFilterTests.setDemux(demux);
+ 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.startFilter(filterId));
+ ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ ASSERT_TRUE(mFilterTests.closeFilter(filterId));
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+ ASSERT_TRUE(mFrontendTests.closeFrontend());
+}
+
+void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
+ FrontendConfig frontendConf) {
+ uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ uint32_t filterId;
+
mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
if (feId == INVALID_ID) {
// TODO broadcast test on Cuttlefish needs licensed ts input,
@@ -426,29 +108,131 @@
}
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
- ASSERT_TRUE(mDemuxTests.openDemux(mDemux, mDemuxId));
- mFilterTests.setDemux(mDemux);
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
- ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type));
- uint32_t filterId;
+ mFilterTests.setDemux(demux);
+ ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
- ASSERT_TRUE(mFilterTests.configFilter(filterConf.setting, filterId));
+ ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
// tune test
ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf));
- // broadcast data flow test
- ASSERT_TRUE(broadcastDataFlowTest(goldenOutputFiles));
+ ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles));
ASSERT_TRUE(mFrontendTests.stopTuneFrontend());
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
-/*================================== End Test Module ==================================*/
-/***************************** End Test Implementation *****************************/
-/******************************** Start Test Entry **********************************/
+void TunerPlaybackHidlTest::playbackSingleFilterTest(FilterConfig filterConf, DvrConfig dvrConf) {
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ uint32_t filterId;
+
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ mFilterTests.setDemux(demux);
+ mDvrTests.setDemux(demux);
+ ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
+ ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ 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));
+ mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
+ ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mFilterTests.startFilter(filterId));
+ ASSERT_TRUE(filterDataOutputTest(goldenOutputFiles));
+ mDvrTests.stopPlaybackThread();
+ ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mFilterTests.closeFilter(filterId));
+ mDvrTests.closeDvr();
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+}
+
+void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf,
+ FrontendConfig frontendConf, DvrConfig dvrConf) {
+ uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ uint32_t filterId;
+ sp<IFilter> filter;
+
+ mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+ ASSERT_TRUE(feId != INVALID_ID);
+ ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+ ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+ mFilterTests.setDemux(demux);
+ mDvrTests.setDemux(demux);
+ ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
+ ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ 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));
+ filter = mFilterTests.getFilterById(filterId);
+ ASSERT_TRUE(filter != nullptr);
+ mDvrTests.startRecordOutputThread(dvrConf.settings.record());
+ ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
+ ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mFilterTests.startFilter(filterId));
+ ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf));
+ mDvrTests.testRecordOutput();
+ mDvrTests.stopRecordThread();
+ ASSERT_TRUE(mFrontendTests.stopTuneFrontend());
+ ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mDvrTests.detachFilterToDvr(filter));
+ ASSERT_TRUE(mFilterTests.closeFilter(filterId));
+ mDvrTests.closeDvr();
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+ ASSERT_TRUE(mFrontendTests.closeFrontend());
+}
+
+void TunerRecordHidlTest::attachSingleFilterToRecordDvrTest(FilterConfig filterConf,
+ FrontendConfig frontendConf,
+ DvrConfig dvrConf) {
+ uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ uint32_t filterId;
+ sp<IFilter> filter;
+
+ mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+ ASSERT_TRUE(feId != INVALID_ID);
+ ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+ ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+ mFilterTests.setDemux(demux);
+ mDvrTests.setDemux(demux);
+ ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
+ ASSERT_TRUE(mDvrTests.configDvr(dvrConf.settings));
+ ASSERT_TRUE(mDvrTests.getDvrMQDescriptor());
+ 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));
+ filter = mFilterTests.getFilterById(filterId);
+ ASSERT_TRUE(filter != nullptr);
+ ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
+ ASSERT_TRUE(mDvrTests.startDvr());
+ ASSERT_TRUE(mFilterTests.startFilter(filterId));
+ ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ ASSERT_TRUE(mDvrTests.stopDvr());
+ ASSERT_TRUE(mDvrTests.detachFilterToDvr(filter));
+ ASSERT_TRUE(mFilterTests.closeFilter(filterId));
+ mDvrTests.closeDvr();
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+ ASSERT_TRUE(mFrontendTests.closeFrontend());
+}
+
TEST_P(TunerFrontendHidlTest, TuneFrontend) {
description("Tune one Frontend with specific setting and check Lock event");
mFrontendTests.tuneTest(frontendArray[DVBT]);
@@ -467,173 +251,84 @@
TEST_P(TunerDemuxHidlTest, openDemux) {
description("Open and close a Demux.");
uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
- ASSERT_TRUE(mDemuxTests.openDemux(mDemux, mDemuxId));
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
ASSERT_TRUE(mDemuxTests.closeDemux());
-}
-
-TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
- description("Open and start a filter in Demux.");
- uint32_t feId;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
- ASSERT_TRUE(feId != INVALID_ID);
- ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
- ASSERT_TRUE(mFrontendTests.setFrontendCallback());
- ASSERT_TRUE(mDemuxTests.openDemux(mDemux, mDemuxId));
- mFilterTests.setDemux(mDemux);
- ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
- ASSERT_TRUE(mFilterTests.openFilterInDemux(filterArray[TS_VIDEO0].type));
- uint32_t filterId;
- ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
- ASSERT_TRUE(mFilterTests.configFilter(filterArray[TS_VIDEO0].setting, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
- ASSERT_TRUE(mFilterTests.startFilter(filterId));
- ASSERT_TRUE(mFilterTests.stopFilter(filterId));
- ASSERT_TRUE(mFilterTests.closeFilter(filterId));
- ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
-/*============================ Start Descrambler Tests ============================*/
-/*
- * TODO: re-enable the tests after finalizing the test refactoring.
- */
-/*TEST_P(TunerHidlTest, CreateDescrambler) {
- description("Create Descrambler");
- ASSERT_TRUE(createDescrambler());
+TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
+ description("Open and start a filter in Demux.");
+ // TODO use paramterized tests
+ configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
}
-TEST_P(TunerHidlTest, CloseDescrambler) {
- description("Close Descrambler");
- ASSERT_TRUE(closeDescrambler());
-}*/
-/*============================== End Descrambler Tests ==============================*/
-
-/*============================== Start Data Flow Tests ==============================*/
-TEST_P(TunerHidlTest, BroadcastDataFlowVideoFilterTest) {
+TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
description("Test Video Filter functionality in Broadcast use case.");
broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBS]);
}
-TEST_P(TunerHidlTest, BroadcastDataFlowAudioFilterTest) {
+TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) {
description("Test Audio Filter functionality in Broadcast use case.");
broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[DVBS]);
}
-TEST_P(TunerHidlTest, BroadcastDataFlowTsFilterTest) {
- description("Test TS Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_TS0], frontendArray[DVBS]);
-}
-
-TEST_P(TunerHidlTest, BroadcastDataFlowSectionFilterTest) {
+TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) {
description("Test Section Filter functionality in Broadcast use case.");
broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[DVBS]);
}
-TEST_P(TunerHidlTest, IonBufferTest) {
+TEST_P(TunerBroadcastHidlTest, IonBufferTest) {
description("Test the av filter data bufferring.");
broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBS]);
}
-/*
- * TODO: re-enable the tests after finalizing the testing stream.
- */
-/*TEST_P(TunerHidlTest, PlaybackDataFlowWithSectionFilterTest) {
- description("Feed ts data from playback and configure pes filter to get output");
- // todo modulize the filter conf parser
- vector<FilterConf> filterConf;
- filterConf.resize(1);
-
- DemuxFilterSettings filterSetting;
- DemuxTsFilterSettings tsFilterSetting{
- .tpid = 18,
- };
- DemuxFilterSectionSettings sectionFilterSetting;
- tsFilterSetting.filterSettings.section(sectionFilterSetting);
- filterSetting.ts(tsFilterSetting);
-
- DemuxFilterType type{
- .mainType = DemuxFilterMainType::TS,
- };
- type.subType.tsFilterType(DemuxTsFilterType::SECTION);
- FilterConf sectionFilterConf{
- .type = type,
- .setting = filterSetting,
- };
- filterConf[0] = sectionFilterConf;
-
- PlaybackSettings playbackSetting{
- .statusMask = 0xf,
- .lowThreshold = 0x1000,
- .highThreshold = 0x07fff,
- .dataFormat = DataFormat::TS,
- .packetSize = 188,
- };
-
- PlaybackConf playbackConf{
- .inputDataFile = "/vendor/etc/test1.ts",
- .setting = playbackSetting,
- };
-
- vector<string> goldenOutputFiles;
-
- ASSERT_TRUE(playbackDataFlowTest(filterConf, playbackConf, goldenOutputFiles));
+TEST_P(TunerPlaybackHidlTest, PlaybackDataFlowWithTsSectionFilterTest) {
+ description("Feed ts data from playback and configure Ts section filter to get output");
+ playbackSingleFilterTest(filterArray[TS_SECTION0], dvrArray[DVR_PLAYBACK0]);
}
-TEST_P(TunerHidlTest, RecordDataFlowWithTsRecordFilterTest) {
+TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) {
+ description("Attach a single filter to the record dvr test.");
+ // TODO use paramterized tests
+ attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[DVBT],
+ 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]);
+}
- // todo modulize the filter conf parser
- vector<FilterConf> filterConf;
- filterConf.resize(1);
+TEST_P(TunerHidlTest, CreateDescrambler) {
+ description("Create Descrambler");
+ uint32_t feId;
+ uint32_t demuxId;
+ sp<IDemux> demux;
+ mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ ASSERT_TRUE(feId != INVALID_ID);
+ ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+ ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+ ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+ ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+ ASSERT_TRUE(createDescrambler(demuxId));
+ ASSERT_TRUE(closeDescrambler());
+ ASSERT_TRUE(mDemuxTests.closeDemux());
+ ASSERT_TRUE(mFrontendTests.closeFrontend());
+}
- DemuxFilterSettings filterSetting;
- DemuxTsFilterSettings tsFilterSetting{
- .tpid = 119,
- };
- DemuxFilterRecordSettings recordFilterSetting;
- tsFilterSetting.filterSettings.record(recordFilterSetting);
- filterSetting.ts(tsFilterSetting);
-
- DemuxFilterType type{
- .mainType = DemuxFilterMainType::TS,
- };
- type.subType.tsFilterType(DemuxTsFilterType::RECORD);
- FilterConf recordFilterConf{
- .type = type,
- .setting = filterSetting,
- };
- filterConf[0] = recordFilterConf;
-
- RecordSettings recordSetting{
- .statusMask = 0xf,
- .lowThreshold = 0x1000,
- .highThreshold = 0x07fff,
- .dataFormat = DataFormat::TS,
- .packetSize = 188,
- };
-
- vector<string> goldenOutputFiles;
-
- ASSERT_TRUE(recordDataFlowTest(filterConf, recordSetting, goldenOutputFiles));
-}*/
-/*============================== End Data Flow Tests ==============================*/
-/******************************** End Test Entry **********************************/
INSTANTIATE_TEST_SUITE_P(
PerInstance, TunerFrontendHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
android::hardware::PrintInstanceNameToString);
INSTANTIATE_TEST_SUITE_P(
- PerInstance, TunerHidlTest,
- testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
- android::hardware::PrintInstanceNameToString);
-
-INSTANTIATE_TEST_SUITE_P(
PerInstance, TunerDemuxHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
android::hardware::PrintInstanceNameToString);
@@ -642,4 +337,24 @@
PerInstance, TunerFilterHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
android::hardware::PrintInstanceNameToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, TunerBroadcastHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, TunerPlaybackHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, TunerRecordHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, TunerHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
+ android::hardware::PrintInstanceNameToString);
} // namespace
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
index f177047..21a9855 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.h
@@ -15,30 +15,13 @@
*/
#include <android/hardware/tv/tuner/1.0/IDescrambler.h>
-#include <android/hardware/tv/tuner/1.0/IDvr.h>
-#include <android/hardware/tv/tuner/1.0/IDvrCallback.h>
-#include <fstream>
-#include <iostream>
#include "DemuxTests.h"
-#include "FilterTests.h"
+#include "DvrTests.h"
#include "FrontendTests.h"
using android::hardware::tv::tuner::V1_0::DataFormat;
-using android::hardware::tv::tuner::V1_0::DvrSettings;
-using android::hardware::tv::tuner::V1_0::DvrType;
using android::hardware::tv::tuner::V1_0::IDescrambler;
-using android::hardware::tv::tuner::V1_0::IDvr;
-using android::hardware::tv::tuner::V1_0::IDvrCallback;
-using android::hardware::tv::tuner::V1_0::PlaybackSettings;
-using android::hardware::tv::tuner::V1_0::PlaybackStatus;
-using android::hardware::tv::tuner::V1_0::RecordSettings;
-using android::hardware::tv::tuner::V1_0::RecordStatus;
-
-struct PlaybackConf {
- string inputDataFile;
- PlaybackSettings setting;
-};
static AssertionResult failure() {
return ::testing::AssertionFailure();
@@ -50,96 +33,19 @@
namespace {
-class DvrCallback : public IDvrCallback {
- public:
- virtual Return<void> onRecordStatus(DemuxFilterStatus status) override {
- ALOGW("[vts] record status %hhu", status);
- switch (status) {
- case DemuxFilterStatus::DATA_READY:
- break;
- case DemuxFilterStatus::LOW_WATER:
- break;
- case DemuxFilterStatus::HIGH_WATER:
- case DemuxFilterStatus::OVERFLOW:
- ALOGW("[vts] record overflow. Flushing");
- break;
- }
- return Void();
- }
-
- virtual Return<void> onPlaybackStatus(PlaybackStatus status) override {
- // android::Mutex::Autolock autoLock(mMsgLock);
- ALOGW("[vts] playback status %d", status);
- switch (status) {
- case PlaybackStatus::SPACE_EMPTY:
- case PlaybackStatus::SPACE_ALMOST_EMPTY:
- ALOGW("[vts] keep playback inputing %d", status);
- mKeepWritingPlaybackFMQ = true;
- break;
- case PlaybackStatus::SPACE_ALMOST_FULL:
- case PlaybackStatus::SPACE_FULL:
- ALOGW("[vts] stop playback inputing %d", status);
- mKeepWritingPlaybackFMQ = false;
- break;
- }
- return Void();
- }
-
- void testFilterDataOutput();
- void stopPlaybackThread();
- void testRecordOutput();
- void stopRecordThread();
-
- void startPlaybackInputThread(PlaybackConf playbackConf, MQDesc& playbackMQDescriptor);
- void startRecordOutputThread(RecordSettings recordSetting, MQDesc& recordMQDescriptor);
- static void* __threadLoopPlayback(void* threadArgs);
- static void* __threadLoopRecord(void* threadArgs);
- void playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWritingPlaybackFMQ);
- void recordThreadLoop(RecordSettings* recordSetting, bool* keepWritingPlaybackFMQ);
-
- bool readRecordFMQ();
-
- private:
- struct PlaybackThreadArgs {
- DvrCallback* user;
- PlaybackConf* playbackConf;
- bool* keepWritingPlaybackFMQ;
- };
- struct RecordThreadArgs {
- DvrCallback* user;
- RecordSettings* recordSetting;
- bool* keepReadingRecordFMQ;
- };
- uint16_t mDataLength = 0;
- std::vector<uint8_t> mDataOutputBuffer;
-
- std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterMQ;
- std::unique_ptr<FilterMQ> mPlaybackMQ;
- std::unique_ptr<FilterMQ> mRecordMQ;
- std::map<uint32_t, EventFlag*> mFilterMQEventFlag;
-
- android::Mutex mMsgLock;
- android::Mutex mPlaybackThreadLock;
- android::Mutex mRecordThreadLock;
- android::Condition mMsgCondition;
-
- bool mKeepWritingPlaybackFMQ = true;
- bool mKeepReadingRecordFMQ = true;
- bool mPlaybackThreadRunning;
- bool mRecordThreadRunning;
- pthread_t mPlaybackThread;
- pthread_t mRecordThread;
-
- int mPidFilterOutputCount = 0;
-};
+void initConfiguration() {
+ initFrontendConfig();
+ initFrontendScanConfig();
+ initFilterConfig();
+ initDvrConfig();
+}
class TunerFrontendHidlTest : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
mService = ITuner::getService(GetParam());
ASSERT_NE(mService, nullptr);
- initFrontendConfig();
- initFrontendScanConfig();
+ initConfiguration();
mFrontendTests.setService(mService);
}
@@ -158,9 +64,7 @@
virtual void SetUp() override {
mService = ITuner::getService(GetParam());
ASSERT_NE(mService, nullptr);
- initFrontendConfig();
- initFrontendScanConfig();
- initFilterConfig();
+ initConfiguration();
mFrontendTests.setService(mService);
mDemuxTests.setService(mService);
@@ -174,8 +78,6 @@
sp<ITuner> mService;
FrontendTests mFrontendTests;
DemuxTests mDemuxTests;
- sp<IDemux> mDemux;
- uint32_t mDemuxId;
};
class TunerFilterHidlTest : public testing::TestWithParam<std::string> {
@@ -183,9 +85,32 @@
virtual void SetUp() override {
mService = ITuner::getService(GetParam());
ASSERT_NE(mService, nullptr);
- initFrontendConfig();
- initFrontendScanConfig();
- initFilterConfig();
+ initConfiguration();
+
+ mFrontendTests.setService(mService);
+ mDemuxTests.setService(mService);
+ mFilterTests.setService(mService);
+ }
+
+ protected:
+ static void description(const std::string& description) {
+ RecordProperty("description", description);
+ }
+
+ void configSingleFilterInDemuxTest(FilterConfig filterConf, FrontendConfig frontendConf);
+
+ sp<ITuner> mService;
+ FrontendTests mFrontendTests;
+ DemuxTests mDemuxTests;
+ FilterTests mFilterTests;
+};
+
+class TunerBroadcastHidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ mService = ITuner::getService(GetParam());
+ ASSERT_NE(mService, nullptr);
+ initConfiguration();
mFrontendTests.setService(mService);
mDemuxTests.setService(mService);
@@ -201,27 +126,80 @@
FrontendTests mFrontendTests;
DemuxTests mDemuxTests;
FilterTests mFilterTests;
- sp<IDemux> mDemux;
- uint32_t mDemuxId;
+
+ AssertionResult filterDataOutputTest(vector<string> goldenOutputFiles);
+
+ void broadcastSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf);
+};
+
+class TunerPlaybackHidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ mService = ITuner::getService(GetParam());
+ ASSERT_NE(mService, nullptr);
+ initConfiguration();
+
+ mFrontendTests.setService(mService);
+ mDemuxTests.setService(mService);
+ mFilterTests.setService(mService);
+ mDvrTests.setService(mService);
+ }
+
+ protected:
+ static void description(const std::string& description) {
+ RecordProperty("description", description);
+ }
+
+ sp<ITuner> mService;
+ FrontendTests mFrontendTests;
+ DemuxTests mDemuxTests;
+ FilterTests mFilterTests;
+ DvrTests mDvrTests;
+
+ AssertionResult filterDataOutputTest(vector<string> goldenOutputFiles);
+
+ void playbackSingleFilterTest(FilterConfig filterConf, DvrConfig dvrConf);
+};
+
+class TunerRecordHidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ mService = ITuner::getService(GetParam());
+ ASSERT_NE(mService, nullptr);
+ initConfiguration();
+
+ mFrontendTests.setService(mService);
+ mDemuxTests.setService(mService);
+ mFilterTests.setService(mService);
+ mDvrTests.setService(mService);
+ }
+
+ protected:
+ static void description(const std::string& description) {
+ RecordProperty("description", description);
+ }
+
+ void attachSingleFilterToRecordDvrTest(FilterConfig filterConf, FrontendConfig frontendConf,
+ DvrConfig dvrConf);
+ void recordSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf,
+ DvrConfig dvrConf);
+
+ sp<ITuner> mService;
+ FrontendTests mFrontendTests;
+ DemuxTests mDemuxTests;
+ FilterTests mFilterTests;
+ DvrTests mDvrTests;
};
class TunerHidlTest : public testing::TestWithParam<std::string> {
public:
- sp<ITuner> mService;
- FrontendTests mFrontendTests;
- DemuxTests mDemuxTests;
- FilterTests mFilterTests;
-
virtual void SetUp() override {
mService = ITuner::getService(GetParam());
ASSERT_NE(mService, nullptr);
- initFrontendConfig();
- initFrontendScanConfig();
- initFilterConfig();
+ initConfiguration();
mFrontendTests.setService(mService);
mDemuxTests.setService(mService);
- mFilterTests.setService(mService);
}
protected:
@@ -229,32 +207,13 @@
RecordProperty("description", description);
}
+ sp<ITuner> mService;
+ FrontendTests mFrontendTests;
+ DemuxTests mDemuxTests;
+
sp<IDescrambler> mDescrambler;
- sp<IDvr> mDvr;
- sp<IDemux> mDemux;
- uint32_t mDemuxId;
- sp<DvrCallback> mDvrCallback;
- MQDesc mDvrMQDescriptor;
- MQDesc mRecordMQDescriptor;
-
- pthread_t mPlaybackshread;
- bool mPlaybackThreadRunning;
-
- AssertionResult openDvrInDemux(DvrType type);
- AssertionResult configDvr(DvrSettings setting);
- AssertionResult getDvrMQDescriptor();
-
- AssertionResult createDescrambler();
+ AssertionResult createDescrambler(uint32_t demuxId);
AssertionResult closeDescrambler();
-
- AssertionResult playbackDataFlowTest(vector<FilterConfig> filterConf, PlaybackConf playbackConf,
- vector<string> goldenOutputFiles);
- AssertionResult recordDataFlowTest(vector<FilterConfig> filterConf,
- RecordSettings recordSetting,
- vector<string> goldenOutputFiles);
- AssertionResult broadcastDataFlowTest(vector<string> goldenOutputFiles);
-
- void broadcastSingleFilterTest(FilterConfig filterConf, FrontendConfig frontendConf);
};
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index 538773a..b84013b 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -21,12 +21,16 @@
#include <hidl/Status.h>
#include <hidlmemory/FrameworkUtils.h>
+using android::hardware::tv::tuner::V1_0::DataFormat;
using android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
using android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
using android::hardware::tv::tuner::V1_0::DemuxFilterType;
+using android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType;
using android::hardware::tv::tuner::V1_0::DemuxTpid;
using android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using android::hardware::tv::tuner::V1_0::DvrSettings;
+using android::hardware::tv::tuner::V1_0::DvrType;
using android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth;
using android::hardware::tv::tuner::V1_0::FrontendDvbtCoderate;
using android::hardware::tv::tuner::V1_0::FrontendDvbtConstellation;
@@ -37,9 +41,15 @@
using android::hardware::tv::tuner::V1_0::FrontendDvbtTransmissionMode;
using android::hardware::tv::tuner::V1_0::FrontendSettings;
using android::hardware::tv::tuner::V1_0::FrontendType;
+using android::hardware::tv::tuner::V1_0::PlaybackSettings;
+using android::hardware::tv::tuner::V1_0::RecordSettings;
using namespace std;
+const uint32_t FMQ_SIZE_1M = 0x100000;
+const uint32_t FMQ_SIZE_4M = 0x400000;
+const uint32_t FMQ_SIZE_16M = 0x1000000;
+
typedef enum {
TS_VIDEO0,
TS_VIDEO1,
@@ -48,6 +58,7 @@
TS_PCR0,
TS_SECTION0,
TS_TS0,
+ TS_RECORD0,
FILTER_MAX,
} Filter;
@@ -62,9 +73,16 @@
SCAN_MAX,
} FrontendScan;
+typedef enum {
+ DVR_RECORD0,
+ DVR_PLAYBACK0,
+ DVR_MAX,
+} Dvr;
+
struct FilterConfig {
+ uint32_t bufferSize;
DemuxFilterType type;
- DemuxFilterSettings setting;
+ DemuxFilterSettings settings;
};
struct FrontendConfig {
@@ -80,10 +98,18 @@
DemuxTpid audioPid;
};
+struct DvrConfig {
+ DvrType type;
+ uint32_t bufferSize;
+ DvrSettings settings;
+ string playbackInputFile;
+};
+
static FrontendConfig frontendArray[FILTER_MAX];
static FrontendConfig frontendScanArray[SCAN_MAX];
static ChannelConfig channelArray[FRONTEND_MAX];
static FilterConfig filterArray[FILTER_MAX];
+static DvrConfig dvrArray[DVR_MAX];
static vector<string> goldenOutputFiles;
/** Configuration array for the frontend tune test */
@@ -126,40 +152,79 @@
// TS VIDEO filter setting for default implementation testing
filterArray[TS_VIDEO0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_VIDEO0].type.subType.tsFilterType(DemuxTsFilterType::VIDEO);
- filterArray[TS_VIDEO0].setting.ts().tpid = 119;
- filterArray[TS_VIDEO0].setting.ts().filterSettings.av({.isPassthrough = false});
+ filterArray[TS_VIDEO0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_VIDEO0].settings.ts().tpid = 256;
+ filterArray[TS_VIDEO0].settings.ts().filterSettings.av({.isPassthrough = false});
filterArray[TS_VIDEO1].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_VIDEO1].type.subType.tsFilterType(DemuxTsFilterType::VIDEO);
- filterArray[TS_VIDEO1].setting.ts().tpid = 81;
- filterArray[TS_VIDEO1].setting.ts().filterSettings.av({.isPassthrough = false});
+ filterArray[TS_VIDEO1].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_VIDEO1].settings.ts().tpid = 256;
+ filterArray[TS_VIDEO1].settings.ts().filterSettings.av({.isPassthrough = false});
// TS AUDIO filter setting
filterArray[TS_AUDIO0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_AUDIO0].type.subType.tsFilterType(DemuxTsFilterType::AUDIO);
- filterArray[TS_AUDIO0].setting.ts().tpid = 84;
- filterArray[TS_AUDIO0].setting.ts().filterSettings.av({.isPassthrough = false});
+ filterArray[TS_AUDIO0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_AUDIO0].settings.ts().tpid = 256;
+ filterArray[TS_AUDIO0].settings.ts().filterSettings.av({.isPassthrough = false});
// TS PES filter setting
filterArray[TS_PES0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PES0].type.subType.tsFilterType(DemuxTsFilterType::PES);
- filterArray[TS_PES0].setting.ts().tpid = 256;
- filterArray[TS_PES0].setting.ts().filterSettings.pesData({
+ filterArray[TS_PES0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_PES0].settings.ts().tpid = 256;
+ filterArray[TS_PES0].settings.ts().filterSettings.pesData({
.isRaw = false,
.streamId = 0xbd,
});
// TS PCR filter setting
filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR);
- filterArray[TS_PCR0].setting.ts().tpid = 81;
- filterArray[TS_PCR0].setting.ts().filterSettings.noinit();
+ filterArray[TS_PCR0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_PCR0].settings.ts().tpid = 256;
+ filterArray[TS_PCR0].settings.ts().filterSettings.noinit();
// TS filter setting
filterArray[TS_TS0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_TS0].type.subType.tsFilterType(DemuxTsFilterType::TS);
- filterArray[TS_TS0].setting.ts().tpid = 48;
- filterArray[TS_TS0].setting.ts().filterSettings.noinit();
+ filterArray[TS_TS0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_TS0].settings.ts().tpid = 256;
+ filterArray[TS_TS0].settings.ts().filterSettings.noinit();
// TS SECTION filter setting
filterArray[TS_SECTION0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_SECTION0].type.subType.tsFilterType(DemuxTsFilterType::SECTION);
- filterArray[TS_SECTION0].setting.ts().tpid = 48;
- filterArray[TS_SECTION0].setting.ts().filterSettings.section({
+ filterArray[TS_SECTION0].bufferSize = FMQ_SIZE_16M;
+ filterArray[TS_SECTION0].settings.ts().tpid = 256;
+ filterArray[TS_SECTION0].settings.ts().filterSettings.section({
.isRaw = false,
});
+ // TS RECORD filter setting
+ filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS;
+ filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD);
+ filterArray[TS_RECORD0].settings.ts().tpid = 81;
+ filterArray[TS_RECORD0].settings.ts().filterSettings.record({
+ .scIndexType = DemuxRecordScIndexType::NONE,
+ });
+};
+
+/** Configuration array for the dvr test */
+inline void initDvrConfig() {
+ RecordSettings recordSettings{
+ .statusMask = 0xf,
+ .lowThreshold = 0x1000,
+ .highThreshold = 0x07fff,
+ .dataFormat = DataFormat::TS,
+ .packetSize = 188,
+ };
+ dvrArray[DVR_RECORD0].type = DvrType::RECORD;
+ dvrArray[DVR_RECORD0].bufferSize = FMQ_SIZE_4M;
+ dvrArray[DVR_RECORD0].settings.record(recordSettings);
+ PlaybackSettings playbackSettings{
+ .statusMask = 0xf,
+ .lowThreshold = 0x1000,
+ .highThreshold = 0x07fff,
+ .dataFormat = DataFormat::TS,
+ .packetSize = 188,
+ };
+ dvrArray[DVR_PLAYBACK0].type = DvrType::PLAYBACK;
+ dvrArray[DVR_PLAYBACK0].playbackInputFile = "/vendor/etc/segment000000.ts";
+ dvrArray[DVR_PLAYBACK0].bufferSize = FMQ_SIZE_4M;
+ dvrArray[DVR_PLAYBACK0].settings.playback(playbackSettings);
};
diff --git a/wifi/1.0/vts/functional/wifi_sta_iface_hidl_test.cpp b/wifi/1.0/vts/functional/wifi_sta_iface_hidl_test.cpp
index 7db0526..e311c84 100644
--- a/wifi/1.0/vts/functional/wifi_sta_iface_hidl_test.cpp
+++ b/wifi/1.0/vts/functional/wifi_sta_iface_hidl_test.cpp
@@ -260,8 +260,8 @@
/*
* SetScanningMacOui:
- * Ensures that calls to set scanning MAC OUI will return a success status
- * code.
+ * Ensures that calls to set scanning MAC OUI will return a NOT_SUPPORTED
+ * code since it is now deprecated.
*/
TEST_P(WifiStaIfaceHidlTest, SetScanningMacOui) {
if (!isCapabilitySupported(
@@ -271,7 +271,7 @@
}
const android::hardware::hidl_array<uint8_t, 3> kOui{
std::array<uint8_t, 3>{{0x10, 0x22, 0x33}}};
- EXPECT_EQ(WifiStatusCode::SUCCESS,
+ EXPECT_EQ(WifiStatusCode::ERROR_NOT_SUPPORTED,
HIDL_INVOKE(wifi_sta_iface_, setScanningMacOui, kOui).code);
}
diff --git a/wifi/1.4/default/Android.mk b/wifi/1.4/default/Android.mk
index 8573e8e..6be7dad 100644
--- a/wifi/1.4/default/Android.mk
+++ b/wifi/1.4/default/Android.mk
@@ -36,6 +36,9 @@
ifdef WIFI_HIDL_FEATURE_DISABLE_AP_MAC_RANDOMIZATION
LOCAL_CPPFLAGS += -DWIFI_HIDL_FEATURE_DISABLE_AP_MAC_RANDOMIZATION
endif
+ifdef WIFI_AVOID_IFACE_RESET_MAC_CHANGE
+LOCAL_CPPFLAGS += -DWIFI_AVOID_IFACE_RESET_MAC_CHANGE
+endif
# Allow implicit fallthroughs in wifi_legacy_hal.cpp until they are fixed.
LOCAL_CFLAGS += -Wno-error=implicit-fallthrough
LOCAL_SRC_FILES := \
diff --git a/wifi/1.4/default/wifi_chip.cpp b/wifi/1.4/default/wifi_chip.cpp
index 23dd13b..61912a5 100644
--- a/wifi/1.4/default/wifi_chip.cpp
+++ b/wifi/1.4/default/wifi_chip.cpp
@@ -101,6 +101,16 @@
return "wlan" + std::to_string(idx);
}
+// Returns the dedicated iface name if one is defined.
+std::string getApIfaceName() {
+ std::array<char, PROPERTY_VALUE_MAX> buffer;
+ if (property_get("ro.vendor.wifi.sap.interface", buffer.data(), nullptr) ==
+ 0) {
+ return {};
+ }
+ return buffer.data();
+}
+
std::string getP2pIfaceName() {
std::array<char, PROPERTY_VALUE_MAX> buffer;
property_get("wifi.direct.interface", buffer.data(), "p2p0");
@@ -1582,6 +1592,11 @@
// AP iface names start with idx 1 for modes supporting
// concurrent STA and not dual AP, else start with idx 0.
std::string WifiChip::allocateApIfaceName() {
+ // Check if we have a dedicated iface for AP.
+ std::string ifname = getApIfaceName();
+ if (!ifname.empty()) {
+ return ifname;
+ }
return allocateApOrStaIfaceName((isStaApConcurrencyAllowedInCurrentMode() &&
!isDualApAllowedInCurrentMode())
? 1
diff --git a/wifi/1.4/default/wifi_iface_util.cpp b/wifi/1.4/default/wifi_iface_util.cpp
index 036c97b..13ba022 100644
--- a/wifi/1.4/default/wifi_iface_util.cpp
+++ b/wifi/1.4/default/wifi_iface_util.cpp
@@ -52,18 +52,22 @@
bool WifiIfaceUtil::setMacAddress(const std::string& iface_name,
const std::array<uint8_t, 6>& mac) {
+#ifndef WIFI_AVOID_IFACE_RESET_MAC_CHANGE
if (!iface_tool_.lock()->SetUpState(iface_name.c_str(), false)) {
LOG(ERROR) << "SetUpState(false) failed.";
return false;
}
+#endif
if (!iface_tool_.lock()->SetMacAddress(iface_name.c_str(), mac)) {
LOG(ERROR) << "SetMacAddress failed.";
return false;
}
+#ifndef WIFI_AVOID_IFACE_RESET_MAC_CHANGE
if (!iface_tool_.lock()->SetUpState(iface_name.c_str(), true)) {
LOG(ERROR) << "SetUpState(true) failed.";
return false;
}
+#endif
IfaceEventHandlers event_handlers = {};
const auto it = event_handlers_map_.find(iface_name);
if (it != event_handlers_map_.end()) {
diff --git a/wifi/1.4/default/wifi_legacy_hal.cpp b/wifi/1.4/default/wifi_legacy_hal.cpp
index f596195..29123bf 100644
--- a/wifi/1.4/default/wifi_legacy_hal.cpp
+++ b/wifi/1.4/default/wifi_legacy_hal.cpp
@@ -801,13 +801,6 @@
cmd_id, getIfaceHandle(iface_name));
}
-wifi_error WifiLegacyHal::setScanningMacOui(const std::string& iface_name,
- const std::array<uint8_t, 3>& oui) {
- std::vector<uint8_t> oui_internal(oui.data(), oui.data() + oui.size());
- return global_func_table_.wifi_set_scanning_mac_oui(
- getIfaceHandle(iface_name), oui_internal.data());
-}
-
wifi_error WifiLegacyHal::selectTxPowerScenario(const std::string& iface_name,
wifi_power_scenario scenario) {
return global_func_table_.wifi_select_tx_power_scenario(
diff --git a/wifi/1.4/default/wifi_legacy_hal.h b/wifi/1.4/default/wifi_legacy_hal.h
index c697ff9..9964460 100644
--- a/wifi/1.4/default/wifi_legacy_hal.h
+++ b/wifi/1.4/default/wifi_legacy_hal.h
@@ -252,8 +252,6 @@
const std::array<uint8_t, 6>& dst_address, uint32_t period_in_ms);
wifi_error stopSendingOffloadedPacket(const std::string& iface_name,
uint32_t cmd_id);
- wifi_error setScanningMacOui(const std::string& iface_name,
- const std::array<uint8_t, 3>& oui);
virtual wifi_error selectTxPowerScenario(const std::string& iface_name,
wifi_power_scenario scenario);
virtual wifi_error resetTxPowerScenario(const std::string& iface_name);
diff --git a/wifi/1.4/default/wifi_sta_iface.cpp b/wifi/1.4/default/wifi_sta_iface.cpp
index e2ea6e4..49f383a 100644
--- a/wifi/1.4/default/wifi_sta_iface.cpp
+++ b/wifi/1.4/default/wifi_sta_iface.cpp
@@ -578,10 +578,9 @@
}
WifiStatus WifiStaIface::setScanningMacOuiInternal(
- const std::array<uint8_t, 3>& oui) {
- legacy_hal::wifi_error legacy_status =
- legacy_hal_.lock()->setScanningMacOui(ifname_, oui);
- return createWifiStatusFromLegacyError(legacy_status);
+ const std::array<uint8_t, 3>& /* oui */) {
+ // deprecated.
+ return createWifiStatus(WifiStatusCode::ERROR_NOT_SUPPORTED);
}
WifiStatus WifiStaIface::startDebugPacketFateMonitoringInternal() {
diff --git a/wifi/1.4/vts/functional/wifi_rtt_controller_hidl_test.cpp b/wifi/1.4/vts/functional/wifi_rtt_controller_hidl_test.cpp
index 295c86e..4035fb8 100644
--- a/wifi/1.4/vts/functional/wifi_rtt_controller_hidl_test.cpp
+++ b/wifi/1.4/vts/functional/wifi_rtt_controller_hidl_test.cpp
@@ -64,6 +64,14 @@
wifi_rtt_controller_ = getWifiRttController();
ASSERT_NE(nullptr, wifi_rtt_controller_.get());
+
+ // Check RTT support before we run the test.
+ std::pair<WifiStatus, RttCapabilities> status_and_caps;
+ status_and_caps =
+ HIDL_INVOKE(wifi_rtt_controller_, getCapabilities_1_4);
+ if (status_and_caps.first.code == WifiStatusCode::ERROR_NOT_SUPPORTED) {
+ GTEST_SKIP() << "Skipping this test since RTT is not supported.";
+ }
}
virtual void TearDown() override { stopWifi(GetInstanceName()); }
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp
index 3754520..4020298 100644
--- a/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp
@@ -64,6 +64,7 @@
isP2pOn_ =
testing::deviceSupportsFeature("android.hardware.wifi.direct");
+ stopSupplicant(wifi_v1_0_instance_name_);
startSupplicantAndWaitForHidlService(wifi_v1_0_instance_name_,
supplicant_v1_3_instance_name_);
supplicant_ =
diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
index 9c40de1..25091a5 100644
--- a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
+++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp
@@ -51,6 +51,8 @@
supplicant_v1_3_instance_name_ = std::get<1>(GetParam());
isP2pOn_ =
testing::deviceSupportsFeature("android.hardware.wifi.direct");
+
+ stopSupplicant(wifi_v1_0_instance_name_);
startSupplicantAndWaitForHidlService(wifi_v1_0_instance_name_,
supplicant_v1_3_instance_name_);
supplicant_ =
@@ -286,6 +288,49 @@
});
}
}
+
+/*
+ * SetGetWapiPsk
+ */
+TEST_P(SupplicantStaNetworkHidlTest, SetGetWapiPsk) {
+ uint32_t keyMgmt = (uint32_t)ISupplicantStaNetwork::KeyMgmtMask::WAPI_PSK;
+ char kTestPskPassphrase[] = "\"123456780abcdef0123456780abcdef0deadbeef\"";
+ char kTestPskHex[] = "12345678";
+
+ if (!isWapiSupported()) {
+ GTEST_SKIP() << "Skipping test since WAPI is not supported.";
+ }
+
+ sta_network_->setKeyMgmt_1_3(keyMgmt, [](const SupplicantStatus &status) {
+ if (SupplicantStatusCode::SUCCESS != status.code) {
+ // for unsupport case
+ EXPECT_EQ(SupplicantStatusCode::FAILURE_UNKNOWN, status.code);
+ }
+ });
+
+ sta_network_->setPskPassphrase(
+ kTestPskPassphrase, [](const SupplicantStatus &status) {
+ EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+ });
+
+ sta_network_->getPskPassphrase(
+ [&](const SupplicantStatus &status, const hidl_string &psk) {
+ EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+ EXPECT_EQ(kTestPskPassphrase, std::string(psk.c_str()));
+ });
+
+ sta_network_->setPskPassphrase(
+ kTestPskHex, [](const SupplicantStatus &status) {
+ EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+ });
+
+ sta_network_->getPskPassphrase(
+ [&](const SupplicantStatus &status, const hidl_string &psk) {
+ EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code);
+ EXPECT_EQ(kTestPskHex, std::string(psk.c_str()));
+ });
+}
+
/*
* SetEapErp
*/