[Impeller] null check drawable. (#47488)

Speculative fix for the first crash noted in https://github.com/flutter/flutter/issues/136628#issue-1944779964

nextDrawble can return null if the method times out.

Fixes https://github.com/flutter/flutter/issues/136525
diff --git a/BUILD.gn b/BUILD.gn
index 3a09a91..8a5bea6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -196,6 +196,7 @@
     if (is_mac) {
       public_deps += [
         "//flutter/impeller/golden_tests:impeller_golden_tests",
+        "//flutter/shell/gpu:gpu_surface_metal_unittests",
         "//flutter/shell/platform/darwin/common:framework_common_unittests",
         "//flutter/third_party/spring_animation:spring_animation_unittests",
       ]
diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files
index 18a25c8..4593402 100644
--- a/ci/licenses_golden/excluded_files
+++ b/ci/licenses_golden/excluded_files
@@ -256,6 +256,7 @@
 ../../../flutter/shell/common/switches_unittests.cc
 ../../../flutter/shell/common/variable_refresh_rate_display_unittests.cc
 ../../../flutter/shell/common/vsync_waiter_unittests.cc
+../../../flutter/shell/gpu/gpu_surface_metal_impeller_unittests.mm
 ../../../flutter/shell/platform/android/.gitignore
 ../../../flutter/shell/platform/android/android_context_gl_impeller_unittests.cc
 ../../../flutter/shell/platform/android/android_context_gl_unittests.cc
diff --git a/shell/gpu/BUILD.gn b/shell/gpu/BUILD.gn
index 7440ff1..c7ccf87 100644
--- a/shell/gpu/BUILD.gn
+++ b/shell/gpu/BUILD.gn
@@ -88,3 +88,27 @@
     public_deps += [ "//flutter/impeller" ]
   }
 }
+
+if (is_mac) {
+  impeller_component("gpu_surface_metal_unittests") {
+    testonly = true
+    target_type = "executable"
+    sources = [ "gpu_surface_metal_impeller_unittests.mm" ]
+
+    frameworks = [
+      "AppKit.framework",
+      "QuartzCore.framework",
+    ]
+
+    deps = [
+             "//flutter/testing",
+             ":gpu_surface_metal",
+             "//flutter/impeller/fixtures",
+             "//flutter/fml",
+             "//flutter/fml/dart",
+             "//flutter/runtime",
+             "//flutter/runtime:libdart",
+             "//flutter/testing",
+           ] + gpu_common_deps
+  }
+}
diff --git a/shell/gpu/gpu_surface_metal_impeller.mm b/shell/gpu/gpu_surface_metal_impeller.mm
index d76b138..967da21 100644
--- a/shell/gpu/gpu_surface_metal_impeller.mm
+++ b/shell/gpu/gpu_surface_metal_impeller.mm
@@ -51,7 +51,7 @@
 
 // |Surface|
 bool GPUSurfaceMetalImpeller::IsValid() {
-  return !!aiks_context_;
+  return !!aiks_context_ && aiks_context_->IsValid();
 }
 
 // |Surface|
@@ -99,6 +99,9 @@
 
   auto drawable = impeller::SurfaceMTL::GetMetalDrawableAndValidate(
       impeller_renderer_->GetContext(), mtl_layer);
+  if (!drawable) {
+    return nullptr;
+  }
   if (Settings::kSurfaceDataAccessible) {
     last_texture_.reset([drawable.texture retain]);
   }
diff --git a/shell/gpu/gpu_surface_metal_impeller_unittests.mm b/shell/gpu/gpu_surface_metal_impeller_unittests.mm
new file mode 100644
index 0000000..5d56f7f
--- /dev/null
+++ b/shell/gpu/gpu_surface_metal_impeller_unittests.mm
@@ -0,0 +1,81 @@
+// 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 <Foundation/Foundation.h>
+#include <QuartzCore/QuartzCore.h>
+
+#include "flutter/shell/gpu/gpu_surface_metal_impeller.h"
+#include "gtest/gtest.h"
+#include "impeller/entity/mtl/entity_shaders.h"
+#include "impeller/entity/mtl/framebuffer_blend_shaders.h"
+#include "impeller/entity/mtl/modern_shaders.h"
+#include "impeller/renderer/backend/metal/context_mtl.h"
+
+namespace flutter {
+namespace testing {
+
+class TestGPUSurfaceMetalDelegate : public GPUSurfaceMetalDelegate {
+ public:
+  TestGPUSurfaceMetalDelegate() : GPUSurfaceMetalDelegate(MTLRenderTargetType::kCAMetalLayer) {
+    layer_ = [[CAMetalLayer alloc] init];
+  }
+
+  ~TestGPUSurfaceMetalDelegate() = default;
+
+  GPUCAMetalLayerHandle GetCAMetalLayer(const SkISize& frame_info) const override {
+    return (__bridge GPUCAMetalLayerHandle)(layer_);
+  }
+
+  bool PresentDrawable(GrMTLHandle drawable) const override { return true; }
+
+  GPUMTLTextureInfo GetMTLTexture(const SkISize& frame_info) const override { return {}; }
+
+  bool PresentTexture(GPUMTLTextureInfo texture) const override { return true; }
+
+  bool AllowsDrawingWhenGpuDisabled() const override { return true; }
+
+ private:
+  CAMetalLayer* layer_ = nil;
+};
+
+static std::shared_ptr<impeller::ContextMTL> CreateImpellerContext() {
+  std::vector<std::shared_ptr<fml::Mapping>> shader_mappings = {
+      std::make_shared<fml::NonOwnedMapping>(impeller_entity_shaders_data,
+                                             impeller_entity_shaders_length),
+      std::make_shared<fml::NonOwnedMapping>(impeller_modern_shaders_data,
+                                             impeller_modern_shaders_length),
+      std::make_shared<fml::NonOwnedMapping>(impeller_framebuffer_blend_shaders_data,
+                                             impeller_framebuffer_blend_shaders_length),
+  };
+  auto sync_switch = std::make_shared<fml::SyncSwitch>(false);
+  return impeller::ContextMTL::Create(shader_mappings, sync_switch, "Impeller Library");
+}
+
+TEST(GPUSurfaceMetalImpeller, InvalidImpellerContextCreatesCausesSurfaceToBeInvalid) {
+  auto delegate = std::make_shared<TestGPUSurfaceMetalDelegate>();
+  auto surface = std::make_shared<GPUSurfaceMetalImpeller>(delegate.get(), nullptr);
+
+  ASSERT_FALSE(surface->IsValid());
+}
+
+TEST(GPUSurfaceMetalImpeller, CanCreateValidSurface) {
+  auto delegate = std::make_shared<TestGPUSurfaceMetalDelegate>();
+  auto surface = std::make_shared<GPUSurfaceMetalImpeller>(delegate.get(), CreateImpellerContext());
+
+  ASSERT_TRUE(surface->IsValid());
+}
+
+TEST(GPUSurfaceMetalImpeller, AcquireFrameFromCAMetalLayerNullChecksDrawable) {
+  auto delegate = std::make_shared<TestGPUSurfaceMetalDelegate>();
+  std::shared_ptr<Surface> surface =
+      std::make_shared<GPUSurfaceMetalImpeller>(delegate.get(), CreateImpellerContext());
+
+  ASSERT_TRUE(surface->IsValid());
+
+  auto frame = surface->AcquireFrame(SkISize::Make(100, 100));
+  ASSERT_EQ(frame, nullptr);
+}
+
+}  // namespace testing
+}  // namespace flutter
diff --git a/testing/run_tests.py b/testing/run_tests.py
index 8a139f9..dbeee78 100755
--- a/testing/run_tests.py
+++ b/testing/run_tests.py
@@ -437,6 +437,7 @@
         make_test('accessibility_unittests'),
         make_test('framework_common_unittests'),
         make_test('spring_animation_unittests'),
+        make_test('gpu_surface_metal_unittests'),
     ]
 
   if is_linux():