[Impeller] Fixes to YUV imports on Android, Incomplete read of pipeline cache data, missing enabled extensions. (#164744)

- Handle textures that require a YUV import but aren't an undefined
format.
- INCOMPLETE is actually a success case for the pipeline cache. CERTAIN
drivers ALWAYS return incomplete, even when they wrote all the data.
Probably an off by one or something like that...
- Ensures Optional AndroidExtensions are enabled
- Only creates a YUV conversion if necessary
diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml
index 732fe85..9de03af 100644
--- a/engine/src/flutter/.ci.yaml
+++ b/engine/src/flutter/.ci.yaml
@@ -67,13 +67,6 @@
           {"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
         ]
     timeout: 90
-    runIf:
-      - DEPS
-      - engine/src/flutter/.ci.yaml
-      - engine/src/flutter/ci/builders/linux_android_emulator.json
-      - engine/src/flutter/lib/ui/**
-      - engine/src/flutter/shell/platform/android/**
-      - engine/src/flutter/testing/skia_gold_client/**
 
   - name: Linux builder_cache
     enabled_branches:
diff --git a/engine/src/flutter/BUILD.gn b/engine/src/flutter/BUILD.gn
index 8a854b2..12d69c6 100644
--- a/engine/src/flutter/BUILD.gn
+++ b/engine/src/flutter/BUILD.gn
@@ -170,6 +170,8 @@
   public_deps = []
   if (is_android) {
     public_deps += [
+      "//flutter/impeller/renderer/backend/vulkan:vulkan_android_apk_unittests",
+      "//flutter/impeller/renderer/backend/vulkan:vulkan_android_unittests",
       "//flutter/impeller/toolkit/android:apk_unittests",
       "//flutter/impeller/toolkit/android:unittests",
       "//flutter/shell/platform/android:flutter_shell_native_unittests",
diff --git a/engine/src/flutter/ci/builders/linux_android_emulator.json b/engine/src/flutter/ci/builders/linux_android_emulator.json
index 2ff2383..53a4c90 100644
--- a/engine/src/flutter/ci/builders/linux_android_emulator.json
+++ b/engine/src/flutter/ci/builders/linux_android_emulator.json
@@ -30,6 +30,8 @@
             "ninja": {
                 "config": "ci/android_emulator_debug_x64",
                 "targets": [
+                    "flutter/impeller/renderer/backend/vulkan:vulkan_android_unittests",
+                    "flutter/impeller/renderer/backend/vulkan:vulkan_android_apk_unittests",
                     "flutter/impeller/toolkit/android:unittests",
                     "flutter/shell/platform/android:flutter_shell_native_unittests"
                 ]
@@ -97,6 +99,8 @@
             "ninja": {
                 "config": "ci/android_emulator_debug_x86",
                 "targets": [
+                    "flutter/impeller/renderer/backend/vulkan:vulkan_android_unittests",
+                    "flutter/impeller/renderer/backend/vulkan:vulkan_android_apk_unittests",
                     "flutter/impeller/toolkit/android:unittests",
                     "flutter/shell/platform/android:flutter_shell_native_unittests"
                 ]
diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files
index 6e43c10..3a2ce74 100644
--- a/engine/src/flutter/ci/licenses_golden/excluded_files
+++ b/engine/src/flutter/ci/licenses_golden/excluded_files
@@ -192,6 +192,7 @@
 ../../../flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm
 ../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm
 ../../../flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc
+../../../flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/command_encoder_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/context_vk_unittests.cc
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
index 6e8b85e..cc800da 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//flutter/testing/android/native_activity/native_activity.gni")
 import("//flutter/vulkan/config.gni")
 import("../../../tools/impeller.gni")
 
@@ -161,3 +162,50 @@
     public_deps += [ "../../../toolkit/android" ]
   }
 }
+
+if (is_android) {
+  config("public_android_config") {
+    defines = [ "__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__" ]
+  }
+
+  test_fixtures("unittests_fixtures") {
+    fixtures = []
+  }
+
+  source_set("unittests_lib") {
+    visibility = [ ":*" ]
+
+    testonly = true
+
+    sources = [ "android/ahb_texture_source_vk_unittests.cc" ]
+
+    deps = [
+      ":unittests_fixtures",
+      ":vulkan",
+      "//flutter/testing",
+    ]
+
+    defines = [ "TESTING" ]
+  }
+
+  executable("vulkan_android_unittests") {
+    assert(is_android)
+
+    testonly = true
+
+    output_name = "impeller_vulkan_android_unittests"
+
+    deps = [ ":unittests_lib" ]
+  }
+
+  native_activity_apk("vulkan_android_apk_unittests") {
+    apk_name = "impeller_vulkan_android_unittests"
+
+    testonly = true
+
+    deps = [
+      ":unittests_lib",
+      "//flutter/testing/android/native_activity:gtest_activity",
+    ]
+  }
+}
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.cc
index 9234eec..39663ca 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.cc
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.cc
@@ -11,13 +11,30 @@
 
 namespace impeller {
 
+namespace {
+
+bool RequiresYCBCRConversion(vk::Format format) {
+  switch (format) {
+    case vk::Format::eG8B8R83Plane420Unorm:
+    case vk::Format::eG8B8R82Plane420Unorm:
+    case vk::Format::eG8B8R83Plane422Unorm:
+    case vk::Format::eG8B8R82Plane422Unorm:
+    case vk::Format::eG8B8R83Plane444Unorm:
+      return true;
+    default:
+      // NOTE: NOT EXHAUSTIVE.
+      break;
+  }
+  return false;
+}
+
 using AHBProperties = vk::StructureChain<
     // For VK_ANDROID_external_memory_android_hardware_buffer
     vk::AndroidHardwareBufferPropertiesANDROID,
     // For VK_ANDROID_external_memory_android_hardware_buffer
     vk::AndroidHardwareBufferFormatPropertiesANDROID>;
 
-static vk::UniqueImage CreateVKImageWrapperForAndroidHarwareBuffer(
+vk::UniqueImage CreateVKImageWrapperForAndroidHarwareBuffer(
     const vk::Device& device,
     const AHBProperties& ahb_props,
     const AHardwareBuffer_Desc& ahb_desc) {
@@ -94,7 +111,7 @@
   return std::move(image.value);
 }
 
-static vk::UniqueDeviceMemory ImportVKDeviceMemoryFromAndroidHarwareBuffer(
+vk::UniqueDeviceMemory ImportVKDeviceMemoryFromAndroidHarwareBuffer(
     const vk::Device& device,
     const vk::PhysicalDevice& physical_device,
     const vk::Image& image,
@@ -138,7 +155,7 @@
   return std::move(device_memory.value);
 }
 
-static std::shared_ptr<YUVConversionVK> CreateYUVConversion(
+std::shared_ptr<YUVConversionVK> CreateYUVConversion(
     const ContextVK& context,
     const AHBProperties& ahb_props) {
   YUVConversionDescriptorVK conversion_chain;
@@ -176,10 +193,10 @@
   return context.GetYUVConversionLibrary()->GetConversion(conversion_chain);
 }
 
-static vk::UniqueImageView CreateVKImageView(
+vk::UniqueImageView CreateVKImageView(
     const vk::Device& device,
     const vk::Image& image,
-    const vk::SamplerYcbcrConversion& yuv_conversion,
+    const std::shared_ptr<YUVConversionVK>& yuv_conversion_wrapper,
     const AHBProperties& ahb_props,
     const AHardwareBuffer_Desc& ahb_desc) {
   const auto& ahb_format =
@@ -205,9 +222,10 @@
   view_info.subresourceRange.layerCount = ahb_desc.layers;
 
   // We need a custom YUV conversion only if we don't recognize the format.
-  if (view_info.format == vk::Format::eUndefined) {
+  if (view_info.format == vk::Format::eUndefined ||
+      RequiresYCBCRConversion(view_info.format)) {
     view_chain.get<vk::SamplerYcbcrConversionInfo>().conversion =
-        yuv_conversion;
+        yuv_conversion_wrapper->GetConversion();
   } else {
     view_chain.unlink<vk::SamplerYcbcrConversionInfo>();
   }
@@ -222,7 +240,7 @@
   return std::move(image_view.value);
 }
 
-static PixelFormat ToPixelFormat(AHardwareBuffer_Format format) {
+PixelFormat ToPixelFormat(AHardwareBuffer_Format format) {
   switch (format) {
     case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
       return PixelFormat::kR8G8B8A8UNormInt;
@@ -256,7 +274,7 @@
   return PixelFormat::kR8G8B8A8UNormInt;
 }
 
-static TextureType ToTextureType(const AHardwareBuffer_Desc& ahb_desc) {
+TextureType ToTextureType(const AHardwareBuffer_Desc& ahb_desc) {
   if (ahb_desc.layers == 1u) {
     return TextureType::kTexture2D;
   }
@@ -269,8 +287,7 @@
   return TextureType::kTexture2D;
 }
 
-static TextureDescriptor ToTextureDescriptor(
-    const AHardwareBuffer_Desc& ahb_desc) {
+TextureDescriptor ToTextureDescriptor(const AHardwareBuffer_Desc& ahb_desc) {
   const auto ahb_size = ISize{ahb_desc.width, ahb_desc.height};
   TextureDescriptor desc;
   // We are not going to touch hardware buffers on the CPU or use them as
@@ -290,6 +307,7 @@
   }
   return desc;
 }
+}  // namespace
 
 AHBTextureSourceVK::AHBTextureSourceVK(
     const std::shared_ptr<Context>& p_context,
@@ -339,23 +357,27 @@
   }
 
   // Figure out how to perform YUV conversions.
-  auto yuv_conversion = CreateYUVConversion(context, ahb_props);
-  if (!yuv_conversion || !yuv_conversion->IsValid()) {
-    return;
+  needs_yuv_conversion_ = ahb_format.format == vk::Format::eUndefined ||
+                          RequiresYCBCRConversion(ahb_format.format);
+  std::shared_ptr<YUVConversionVK> yuv_conversion;
+  if (needs_yuv_conversion_) {
+    yuv_conversion = CreateYUVConversion(context, ahb_props);
+    if (!yuv_conversion || !yuv_conversion->IsValid()) {
+      return;
+    }
   }
 
   // Create image view for the newly created image.
-  auto image_view = CreateVKImageView(device,                           //
-                                      image.get(),                      //
-                                      yuv_conversion->GetConversion(),  //
-                                      ahb_props,                        //
-                                      ahb_desc                          //
+  auto image_view = CreateVKImageView(device,          //
+                                      image.get(),     //
+                                      yuv_conversion,  //
+                                      ahb_props,       //
+                                      ahb_desc         //
   );
   if (!image_view) {
     return;
   }
 
-  needs_yuv_conversion_ = ahb_format.format == vk::Format::eUndefined;
   device_memory_ = std::move(device_memory);
   image_ = std::move(image);
   yuv_conversion_ = std::move(yuv_conversion);
@@ -364,7 +386,10 @@
 #ifdef IMPELLER_DEBUG
   context.SetDebugName(device_memory_.get(), "AHB Device Memory");
   context.SetDebugName(image_.get(), "AHB Image");
-  context.SetDebugName(yuv_conversion_->GetConversion(), "AHB YUV Conversion");
+  if (yuv_conversion_) {
+    context.SetDebugName(yuv_conversion_->GetConversion(),
+                         "AHB YUV Conversion");
+  }
   context.SetDebugName(image_view_.get(), "AHB ImageView");
 #endif  // IMPELLER_DEBUG
 
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk_unittests.cc
new file mode 100644
index 0000000..4e40da0
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk_unittests.cc
@@ -0,0 +1,97 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <android/hardware_buffer.h>
+#include <memory>
+
+#include "flutter/testing/testing.h"
+#include "gtest/gtest.h"
+#include "impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h"
+#include "impeller/renderer/backend/vulkan/context_vk.h"
+#include "impeller/renderer/backend/vulkan/surface_context_vk.h"
+#include "impeller/toolkit/android/hardware_buffer.h"
+#include "impeller/toolkit/android/surface_transaction.h"
+
+namespace impeller::android::testing {
+
+// Set up context.
+std::shared_ptr<Context> CreateContext() {
+  auto vulkan_dylib = fml::NativeLibrary::Create("libvulkan.so");
+  auto instance_proc_addr =
+      vulkan_dylib->ResolveFunction<PFN_vkGetInstanceProcAddr>(
+          "vkGetInstanceProcAddr");
+
+  if (!instance_proc_addr.has_value()) {
+    VALIDATION_LOG << "Could not setup Vulkan proc table.";
+    return nullptr;
+  }
+
+  impeller::ContextVK::Settings settings;
+  settings.proc_address_callback = instance_proc_addr.value();
+  settings.shader_libraries_data = {};
+  settings.enable_validation = false;
+  settings.enable_gpu_tracing = false;
+  settings.enable_surface_control = false;
+
+  return ContextVK::Create(std::move(settings));
+}
+
+TEST(AndroidVulkanTest, CanImportRGBA) {
+  if (!HardwareBuffer::IsAvailableOnPlatform()) {
+    GTEST_SKIP() << "Hardware buffers are not supported on this platform.";
+  }
+
+  HardwareBufferDescriptor desc;
+  desc.size = ISize{1, 1};
+  desc.format = HardwareBufferFormat::kR8G8B8A8UNormInt;
+  desc.usage = HardwareBufferUsageFlags::kSampledImage;
+
+  auto ahb = std::make_unique<HardwareBuffer>(desc);
+  ASSERT_TRUE(ahb);
+  auto context_vk = CreateContext();
+  ASSERT_TRUE(context_vk);
+
+  AHBTextureSourceVK source(context_vk, std::move(ahb),
+                            /*is_swapchain_image=*/false);
+
+  EXPECT_TRUE(source.IsValid());
+  EXPECT_EQ(source.GetYUVConversion(), nullptr);
+
+  context_vk->Shutdown();
+}
+
+TEST(AndroidVulkanTest, CanImportWithYUB) {
+  if (!HardwareBuffer::IsAvailableOnPlatform()) {
+    GTEST_SKIP() << "Hardware buffers are not supported on this platform.";
+  }
+
+  AHardwareBuffer_Desc desc;
+  desc.width = 16;
+  desc.height = 16;
+  desc.format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420;
+  desc.stride = 0;
+  desc.layers = 1;
+  desc.rfu0 = 0;
+  desc.rfu1 = 0;
+  desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
+               AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK |
+               AHARDWAREBUFFER_USAGE_CPU_READ_MASK;
+
+  EXPECT_EQ(AHardwareBuffer_isSupported(&desc), 1);
+
+  AHardwareBuffer* buffer = nullptr;
+  ASSERT_EQ(AHardwareBuffer_allocate(&desc, &buffer), 0);
+
+  auto context_vk = CreateContext();
+  ASSERT_TRUE(context_vk);
+
+  AHBTextureSourceVK source(context_vk, buffer, desc);
+
+  EXPECT_TRUE(source.IsValid());
+  EXPECT_NE(source.GetYUVConversion(), nullptr);
+
+  context_vk->Shutdown();
+}
+
+}  // namespace impeller::android::testing
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc
index 6e59d9f..7cd75c7 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc
@@ -298,6 +298,17 @@
     return true;
   };
 
+  auto for_each_optional_android_extension =
+      [&](OptionalAndroidDeviceExtensionVK ext) {
+#ifdef FML_OS_ANDROID
+        auto name = GetExtensionName(ext);
+        if (exts.find(name) != exts.end()) {
+          enabled.push_back(name);
+        }
+#endif  //  FML_OS_ANDROID
+        return true;
+      };
+
   auto for_each_optional_extension = [&](OptionalDeviceExtensionVK ext) {
     auto name = GetExtensionName(ext);
     if (exts.find(name) != exts.end()) {
@@ -311,7 +322,10 @@
           for_each_common_extension) &&
       IterateExtensions<RequiredAndroidDeviceExtensionVK>(
           for_each_android_extension) &&
-      IterateExtensions<OptionalDeviceExtensionVK>(for_each_optional_extension);
+      IterateExtensions<OptionalDeviceExtensionVK>(
+          for_each_optional_extension) &&
+      IterateExtensions<OptionalAndroidDeviceExtensionVK>(
+          for_each_optional_android_extension);
 
   if (!iterate_extensions) {
     VALIDATION_LOG << "Device not suitable since required extensions are not "
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc
index 9cdd199..ecb46b7 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc
@@ -36,9 +36,13 @@
   }
   const auto header = PipelineCacheHeaderVK{props, data_size};
   std::memcpy(allocation->GetBuffer(), &header, sizeof(header));
-  if (cache.getOwner().getPipelineCacheData(
-          *cache, &data_size, allocation->GetBuffer() + sizeof(header)) !=
-      vk::Result::eSuccess) {
+  vk::Result lookup_result = cache.getOwner().getPipelineCacheData(
+      *cache, &data_size, allocation->GetBuffer() + sizeof(header));
+
+  // Some drivers may return incomplete erroneously, but this is not an
+  // error condition as some/all data was still written.
+  if (lookup_result != vk::Result::eSuccess &&
+      lookup_result != vk::Result::eIncomplete) {
     VALIDATION_LOG << "Could not copy pipeline cache data.";
     return false;
   }
diff --git a/engine/src/flutter/testing/run_tests.py b/engine/src/flutter/testing/run_tests.py
index 062b4b0..8cd8776 100755
--- a/engine/src/flutter/testing/run_tests.py
+++ b/engine/src/flutter/testing/run_tests.py
@@ -765,6 +765,7 @@
 
   run_android_unittest('flutter_shell_native_unittests', android_variant, adb_path)
   run_android_unittest('impeller_toolkit_android_unittests', android_variant, adb_path)
+  run_android_unittest('impeller_vulkan_android_unittests', android_variant, adb_path)
 
 
 def run_objc_tests(ios_variant='ios_debug_sim_unopt', test_filter=None):