Make NNAPI Version more structured -- hal

Prior to this topic, NNAPI Versions were linear and represented by an
enumeration. However, this did not properly account for the
non-linearity of runtime-specific features such as a control flow
operations with operands of dynamic sizes. This topic alters Version to
be a struct containing a feature level enumeration as well as a boolean
which indicates whether there are runtime-specific features.

Bug: 206975939
Test: mma
Test: NeuralNetworksTests_static
Change-Id: I78c54ef597bf269b137f2835332bdedac49883d4
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
index 5c1480e..ed7c3e7 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
@@ -28,7 +28,7 @@
 
 namespace android::hardware::neuralnetworks::V1_0::utils {
 
-constexpr auto kVersion = nn::Version::ANDROID_OC_MR1;
+const auto kVersion = nn::Version::ANDROID_OC_MR1;
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -51,7 +51,7 @@
 template <typename Type>
 nn::Result<void> compliantVersion(const Type& canonical) {
     const auto version = NN_TRY(nn::validate(canonical));
-    if (version > kVersion) {
+    if (!nn::isCompliantVersion(version, kVersion)) {
         return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
     }
     return {};
diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
index 4660ff7..3aebb93 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
@@ -30,7 +30,7 @@
 namespace android::hardware::neuralnetworks::V1_1::utils {
 
 constexpr auto kDefaultExecutionPreference = ExecutionPreference::FAST_SINGLE_ANSWER;
-constexpr auto kVersion = nn::Version::ANDROID_P;
+const auto kVersion = nn::Version::ANDROID_P;
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -53,7 +53,7 @@
 template <typename Type>
 nn::Result<void> compliantVersion(const Type& canonical) {
     const auto version = NN_TRY(nn::validate(canonical));
-    if (version > kVersion) {
+    if (!nn::isCompliantVersion(version, kVersion)) {
         return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
     }
     return {};
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
index 23e336a..7ec32e8 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
@@ -39,7 +39,7 @@
 constexpr auto kDefaultMesaureTiming = MeasureTiming::NO;
 constexpr auto kNoTiming = Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
                                   .timeInDriver = std::numeric_limits<uint64_t>::max()};
-constexpr auto kVersion = nn::Version::ANDROID_Q;
+const auto kVersion = nn::Version::ANDROID_Q;
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -62,7 +62,7 @@
 template <typename Type>
 nn::Result<void> compliantVersion(const Type& canonical) {
     const auto version = NN_TRY(nn::validate(canonical));
-    if (version > kVersion) {
+    if (!nn::isCompliantVersion(version, kVersion)) {
         return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
     }
     return {};
diff --git a/neuralnetworks/1.2/utils/src/Burst.cpp b/neuralnetworks/1.2/utils/src/Burst.cpp
index e0a23f1..911fbfa 100644
--- a/neuralnetworks/1.2/utils/src/Burst.cpp
+++ b/neuralnetworks/1.2/utils/src/Burst.cpp
@@ -315,7 +315,7 @@
 
     // if the request is valid but of a higher version than what's supported in burst execution,
     // fall back to another execution path
-    if (const auto version = NN_TRY(nn::validate(request)); version > nn::Version::ANDROID_Q) {
+    if (!compliantVersion(request).ok()) {
         // fallback to another execution path if the packet could not be sent
         return kPreparedModel->execute(request, measure, deadline, loopTimeoutDuration);
     }
@@ -359,7 +359,7 @@
 
     // if the request is valid but of a higher version than what's supported in burst execution,
     // fall back to another execution path
-    if (const auto version = NN_TRY(nn::validate(request)); version > nn::Version::ANDROID_Q) {
+    if (!compliantVersion(request).ok()) {
         // fallback to another execution path if the packet could not be sent
         return kPreparedModel->createReusableExecution(request, measure, loopTimeoutDuration);
     }
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
index 2812db2..0ea3f29 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
@@ -39,7 +39,7 @@
 using V1_2::utils::kNoTiming;
 
 constexpr auto kDefaultPriority = Priority::MEDIUM;
-constexpr auto kVersion = nn::Version::ANDROID_R;
+const auto kVersion = nn::Version::ANDROID_R;
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -62,7 +62,7 @@
 template <typename Type>
 nn::Result<void> compliantVersion(const Type& canonical) {
     const auto version = NN_TRY(nn::validate(canonical));
-    if (version > kVersion) {
+    if (!nn::isCompliantVersion(version, kVersion)) {
         return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
     }
     return {};
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
index 1fb694b..4262ef8 100644
--- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
@@ -30,7 +30,7 @@
 
 constexpr auto kDefaultPriority = Priority::MEDIUM;
 
-constexpr std::optional<nn::Version> aidlVersionToCanonicalVersion(int aidlVersion) {
+inline std::optional<nn::Version> aidlVersionToCanonicalVersion(int aidlVersion) {
     switch (aidlVersion) {
         case 1:
             return nn::Version::ANDROID_S;
@@ -41,7 +41,7 @@
     }
 }
 
-constexpr auto kVersion = aidlVersionToCanonicalVersion(IDevice::version).value();
+const auto kVersion = aidlVersionToCanonicalVersion(IDevice::version).value();
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -64,7 +64,7 @@
 template <typename Type>
 nn::Result<void> compliantVersion(const Type& canonical) {
     const auto version = NN_TRY(nn::validate(canonical));
-    if (version > kVersion) {
+    if (!nn::isCompliantVersion(version, kVersion)) {
         return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
     }
     return {};
diff --git a/neuralnetworks/aidl/utils/test/DeviceTest.cpp b/neuralnetworks/aidl/utils/test/DeviceTest.cpp
index 79abe1b..19d2314 100644
--- a/neuralnetworks/aidl/utils/test/DeviceTest.cpp
+++ b/neuralnetworks/aidl/utils/test/DeviceTest.cpp
@@ -152,13 +152,15 @@
 };
 
 std::string printDeviceTest(const testing::TestParamInfo<nn::Version>& info) {
-    switch (info.param) {
-        case nn::Version::ANDROID_S:
+    const nn::Version version = info.param;
+    CHECK(!version.runtimeOnlyFeatures);
+    switch (version.level) {
+        case nn::Version::Level::ANDROID_S:
             return "v1";
-        case nn::Version::FEATURE_LEVEL_6:
+        case nn::Version::Level::FEATURE_LEVEL_6:
             return "v2";
         default:
-            LOG(FATAL) << "Invalid AIDL version: " << info.param;
+            LOG(FATAL) << "Invalid AIDL version: " << version;
             return "invalid";
     }
 }
diff --git a/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
index 3abd724..2e93a11 100644
--- a/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
+++ b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
@@ -28,7 +28,6 @@
 namespace {
 
 using ::testing::_;
-using ::testing::InvokeWithoutArgs;
 using ::testing::Return;
 
 using SharedMockDevice = std::shared_ptr<const nn::MockDevice>;
@@ -54,7 +53,7 @@
     // Setup default actions for each relevant call.
     constexpr auto getName_ret = []() -> const std::string& { return kName; };
     constexpr auto getVersionString_ret = []() -> const std::string& { return kVersionString; };
-    constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+    const auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
     constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR;
     constexpr auto getSupportedExtensions_ret = []() -> const std::vector<nn::Extension>& {
         return kExtensions;
@@ -142,7 +141,7 @@
 TEST(ResilientDeviceTest, getFeatureLevel) {
     // setup call
     const auto [mockDevice, mockDeviceFactory, device] = setup();
-    constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+    const auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
     EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(1).WillOnce(Return(kFeatureLevel));
 
     // run test