No CPU fallback will be provided when using introspection API
ANeuralNetworksCompilation_createForDevices.

Bug: 120796109
Bug: 120443043
Test: mm
Test: NeuralNetworksTest_static
Change-Id: I5088caac03cabde63a5447934364172882e6a16c
diff --git a/runtime/ModelBuilder.cpp b/runtime/ModelBuilder.cpp
index c82aa3f..6d3373e 100644
--- a/runtime/ModelBuilder.cpp
+++ b/runtime/ModelBuilder.cpp
@@ -20,6 +20,7 @@
 
 #include "CompilationBuilder.h"
 #include "GraphDump.h"
+#include "Manager.h"
 #include "Utils.h"
 #include "ValidateHal.h"
 
@@ -459,13 +460,17 @@
 }
 
 int ModelBuilder::createCompilation(CompilationBuilder** compilation,
-                                    const std::vector<std::shared_ptr<Device>>& devices) {
+                                    const std::vector<std::shared_ptr<Device>>& devices,
+                                    bool forceNoFallback) {
     if (!mCompletedModel || mInvalidModel) {
         LOG(ERROR) << "ANeuralNetworksCompilation_create passed an unfinished or invalid model";
         *compilation = nullptr;
         return ANEURALNETWORKS_BAD_STATE;
     }
     *compilation = new (std::nothrow) CompilationBuilder(this, devices);
+    if (forceNoFallback) {
+        (*compilation)->setPartitioning(DeviceManager::kPartitioningWithoutFallback);
+    }
     return (*compilation ? ANEURALNETWORKS_NO_ERROR : ANEURALNETWORKS_OUT_OF_MEMORY);
 }
 
diff --git a/runtime/ModelBuilder.h b/runtime/ModelBuilder.h
index 3a8e0c1..e5cd468 100644
--- a/runtime/ModelBuilder.h
+++ b/runtime/ModelBuilder.h
@@ -65,7 +65,8 @@
     bool hasExtensionOperation() const { return mHasExtensionOperation; }
 
     int createCompilation(CompilationBuilder** compilation,
-                          const std::vector<std::shared_ptr<Device>>& devices);
+                          const std::vector<std::shared_ptr<Device>>& devices,
+                          bool forceNoFallback = false);
 
     void setHidlModel(Model* model) const;
 
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index d244935..a85c6e8 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -496,7 +496,8 @@
     }
     ModelBuilder* m = reinterpret_cast<ModelBuilder*>(model);
     CompilationBuilder* c = nullptr;
-    int result = m->createCompilation(&c, selectedDevices);
+    // No CPU fallback when user specifies the list of devices manually.
+    int result = m->createCompilation(&c, selectedDevices, /* forceNoFallback */ true);
     *compilation = reinterpret_cast<ANeuralNetworksCompilation*>(c);
     return result;
 }
diff --git a/runtime/include/NeuralNetworks.h b/runtime/include/NeuralNetworks.h
index ffa059e..3024392 100644
--- a/runtime/include/NeuralNetworks.h
+++ b/runtime/include/NeuralNetworks.h
@@ -5094,6 +5094,11 @@
  * ANeuralNetworksModel_getSupportedOperationsForDevices() must have returned true for every
  * operation for that model/devices pair.
  *
+ * The user must handle all compilation and execution failures from the
+ * specified set of devices. This is in contrast to a use of {@link
+ * ANeuralNetworksCompilation_create}, where the runtime will attempt to recover
+ * from such failures.
+ *
  * @param model The {@link ANeuralNetworksModel} to be compiled.
  * @param devices The set of devices. Must not contain duplicates.
  * @param numDevices The number of devices in the set.
diff --git a/runtime/test/TestExecution.cpp b/runtime/test/TestExecution.cpp
index 92e5e58..c4f1575 100644
--- a/runtime/test/TestExecution.cpp
+++ b/runtime/test/TestExecution.cpp
@@ -335,21 +335,69 @@
     }
 };
 
-template<class DriverClass>
-class ExecutionTestTemplate :
-            public ::testing::TestWithParam<std::tuple<ErrorStatus, Result>> {
-public:
-    ExecutionTestTemplate() :
-            kName(toString(std::get<0>(GetParam()))),
-            kForceErrorStatus(std::get<0>(GetParam())),
-            kExpectResult(std::get<1>(GetParam())),
-            mModel(makeModel()),
-            mCompilation(&mModel, kName, kForceErrorStatus) {}
+// This class has roughly the same functionality as TestCompilation class.
+// The major difference is that Introspection API is used to select the device.
+template <typename DriverClass>
+class TestIntrospectionCompilation : public WrapperCompilation {
+   public:
+    TestIntrospectionCompilation(const WrapperModel* model, const std::string& deviceName) {
+        std::vector<ANeuralNetworksDevice*> mDevices;
+        uint32_t numDevices = 0;
+        EXPECT_EQ(ANeuralNetworks_getDeviceCount(&numDevices), ANEURALNETWORKS_NO_ERROR);
+        EXPECT_GE(numDevices, (uint32_t)1);
 
-protected:
+        for (uint32_t i = 0; i < numDevices; i++) {
+            ANeuralNetworksDevice* device = nullptr;
+            EXPECT_EQ(ANeuralNetworks_getDevice(i, &device), ANEURALNETWORKS_NO_ERROR);
+            const char* buffer = nullptr;
+            int result = ANeuralNetworksDevice_getName(device, &buffer);
+            if (result == ANEURALNETWORKS_NO_ERROR && deviceName.compare(buffer) == 0) {
+                mDevices.push_back(device);
+            }
+        }
+        // In CPU only mode, DeviceManager::getDrivers() will not be able to
+        // provide the actual device list. We will not be able to find the test
+        // driver with specified deviceName.
+        if (!DeviceManager::get()->getUseCpuOnly()) {
+            EXPECT_EQ(mDevices.size(), (uint32_t)1);
+
+            int result = ANeuralNetworksCompilation_createForDevices(
+                    model->getHandle(), mDevices.data(), mDevices.size(), &mCompilation);
+            EXPECT_EQ(result, ANEURALNETWORKS_NO_ERROR);
+        }
+    }
+};
+
+template <class DriverClass>
+class ExecutionTestTemplate
+    : public ::testing::TestWithParam<std::tuple<ErrorStatus, Result, bool>> {
+   public:
+    ExecutionTestTemplate()
+        : kName(toString(std::get<0>(GetParam()))),
+          kForceErrorStatus(std::get<0>(GetParam())),
+          kExpectResult(std::get<1>(GetParam())),
+          kUseIntrospectionAPI(std::get<2>(GetParam())),
+          mModel(makeModel()) {
+        if (kUseIntrospectionAPI) {
+            DeviceManager::get()->forTest_registerDevice(kName.c_str(),
+                                                         new DriverClass(kName, kForceErrorStatus));
+            mCompilation = TestIntrospectionCompilation<DriverClass>(&mModel, kName);
+        } else {
+            mCompilation = TestCompilation<DriverClass>(&mModel, kName, kForceErrorStatus);
+        }
+    }
+
+   protected:
     // Unit test method
     void TestWait();
 
+    virtual void TearDown() {
+        // Reinitialize the device list since Introspection API path altered it.
+        if (kUseIntrospectionAPI) {
+            DeviceManager::get()->forTest_reInitializeDeviceList();
+        }
+    }
+
     const std::string kName;
 
     // Allow dummying up the error status for execution.  If
@@ -363,8 +411,11 @@
     // equivalent of kForceErrorStatus.)
     const Result kExpectResult;
 
+    // Whether mCompilation is created via Introspection API or not.
+    const bool kUseIntrospectionAPI;
+
     WrapperModel mModel;
-    TestCompilation<DriverClass> mCompilation;
+    WrapperCompilation mCompilation;
 
     void setInputOutput(WrapperExecution* execution) {
         mInputBuffer = kInputBuffer;
@@ -397,6 +448,11 @@
 
 template<class DriverClass> void ExecutionTestTemplate<DriverClass>::TestWait() {
     SCOPED_TRACE(kName);
+    // Skip Introspection API tests when CPU only flag is forced on.
+    if (kUseIntrospectionAPI && DeviceManager::get()->getUseCpuOnly()) {
+        GTEST_SKIP();
+    }
+
     ASSERT_EQ(mCompilation.finish(), Result::NO_ERROR);
 
     {
@@ -446,11 +502,15 @@
 }
 
 auto kTestValues = ::testing::Values(
-        std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR),
-        std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE),
-        std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED),
-        std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE),
-        std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA));
+        std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR, /* kUseIntrospectionAPI */ false),
+        std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE,
+                        /* kUseIntrospectionAPI */ false),
+        std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED,
+                        /* kUseIntrospectionAPI */ false),
+        std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE,
+                        /* kUseIntrospectionAPI */ false),
+        std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA,
+                        /* kUseIntrospectionAPI */ false));
 
 class ExecutionTest12 : public ExecutionTestTemplate<TestDriver12> {};
 TEST_P(ExecutionTest12, Wait) {
@@ -472,5 +532,18 @@
 }
 INSTANTIATE_TEST_CASE_P(Flavor, ExecutionTest10, kTestValues);
 
+auto kIntrospectionTestValues = ::testing::Values(
+        std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR, /* kUseIntrospectionAPI */ true),
+        std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE,
+                        /* kUseIntrospectionAPI */ true),
+        std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED,
+                        /* kUseIntrospectionAPI */ true),
+        std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE,
+                        /* kUseIntrospectionAPI */ true),
+        std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA,
+                        /* kUseIntrospectionAPI */ true));
+
+INSTANTIATE_TEST_CASE_P(IntrospectionFlavor, ExecutionTest12, kIntrospectionTestValues);
+
 }  // namespace
 }  // namespace android
diff --git a/runtime/test/TestIntrospectionControl.cpp b/runtime/test/TestIntrospectionControl.cpp
index 03ab21e..3bf5e77 100644
--- a/runtime/test/TestIntrospectionControl.cpp
+++ b/runtime/test/TestIntrospectionControl.cpp
@@ -264,6 +264,8 @@
     ASSERT_TRUE(model->isValid());
 }
 
+// TODO(miaowang): add a test to make sure ANNCompilation_create() has CPU
+// fallback.
 // This test verifies that a device that could only handle ADD would correctly report that an
 // ADD->MUL model could not be fully supported.
 TEST_F(IntrospectionControlTest, PartialModelNotSupported) {
@@ -287,6 +289,14 @@
 
     EXPECT_TRUE(selectDeviceByName(addOnlyDriver));
     EXPECT_TRUE(isSupportedOpListExpected({true, false}));
+
+    ANeuralNetworksModel* modelHandle = mModel.getHandle();
+    EXPECT_EQ(ANeuralNetworksCompilation_createForDevices(modelHandle, mDevices.data(),
+                                                          mDevices.size(), &mCompilation),
+              ANEURALNETWORKS_NO_ERROR);
+    // The compilation must fail as there is no fallback when using
+    // Introspection API.
+    EXPECT_NE(ANeuralNetworksCompilation_finish(mCompilation), ANEURALNETWORKS_NO_ERROR);
 }
 
 // This test verifies that a device that could only handle ADD would correctly report that an
@@ -364,5 +374,4 @@
     EXPECT_EQ(output[0], kSimpleMultiplier * (input1[0] + input2[0]));
     EXPECT_EQ(output[1], kSimpleMultiplier * (input1[1] + input2[1]));
 }
-
 }  // namespace