[M87 merge] ambient: close ui when images fail to load

After 3 consecutive failures to load an image, notify observers of
failure.

BUG=b:169591750
TEST=ash_unittests --gtest_filter=AmbientBackendModelTest*

(cherry picked from commit 8bbd512e3960ef6fd3c9150adc632035ada37e7b)

Change-Id: I7a9302e65fc58d3272a0da31f13c6a44efe1cc58
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2449937
Commit-Queue: Jeffrey Young <[email protected]>
Reviewed-by: Xiaohui Chen <[email protected]>
Reviewed-by: Tao Wu <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#816733}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2468500
Cr-Commit-Position: refs/branch-heads/4280@{#348}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/ash/ambient/ambient_constants.h b/ash/ambient/ambient_constants.h
index 0b82785..56b56dc 100644
--- a/ash/ambient/ambient_constants.h
+++ b/ash/ambient/ambient_constants.h
@@ -45,6 +45,10 @@
 
 constexpr int kMaxReservedAvailableDiskSpaceByte = 200 * 1024 * 1024;
 
+// The maximum number of consecutive failures in downloading or reading an image
+// from disk.
+constexpr int kMaxConsecutiveReadPhotoFailures = 3;
+
 constexpr char kPhotoFileExt[] = ".img";
 constexpr char kPhotoDetailsFileExt[] = ".txt";
 
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index f1b739ac..099ca532 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -297,6 +297,10 @@
     return;
   }
 
+  // Reset image failures to allow retrying ambient mode after lock state
+  // changes.
+  GetAmbientBackendModel()->ResetImageFailures();
+
   // We have 3 options to manage the token for lock screen. Here use option 1.
   // 1. Request only one time after entering lock screen. We will use it once
   //    to request all the image links and no more requests.
@@ -374,6 +378,11 @@
   if (!is_screen_off_)
     return;
   is_screen_off_ = false;
+
+  // Reset image failures to allow retrying ambient mode because screen has
+  // turned back on.
+  GetAmbientBackendModel()->ResetImageFailures();
+
   // If screen is back on, turn on ambient mode for lock screen.
   if (LockScreen::HasInstance())
     ShowHiddenUi();
@@ -394,6 +403,17 @@
   if (!idle_state.dimmed())
     return;
 
+  // Do not show the UI if lockscreen is active. The inactivity monitor should
+  // have activated ambient mode.
+  if (LockScreen::HasInstance())
+    return;
+
+  // Do not show UI if loading images was unsuccessful.
+  if (GetAmbientBackendModel()->ImageLoadingFailed()) {
+    VLOG(1) << "Skipping ambient mode activation due to prior failure";
+    return;
+  }
+
   ShowUi();
 }
 
@@ -537,6 +557,11 @@
   CreateAndShowWidget();
 }
 
+void AmbientController::OnImagesFailed() {
+  LOG(ERROR) << "Ambient mode failed to start";
+  ambient_ui_model_.SetUiVisibility(AmbientUiVisibility::kClosed);
+}
+
 std::unique_ptr<AmbientContainerView> AmbientController::CreateContainerView() {
   DCHECK(!container_view_);
 
diff --git a/ash/ambient/ambient_controller.h b/ash/ambient/ambient_controller.h
index 0b38734..70ea1fa 100644
--- a/ash/ambient/ambient_controller.h
+++ b/ash/ambient/ambient_controller.h
@@ -127,6 +127,7 @@
 
   // AmbientBackendModelObserver overrides:
   void OnImagesReady() override;
+  void OnImagesFailed() override;
 
   // Initializes the |container_view_|. Called in |CreateWidget()| to create the
   // contents view.
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index 7321caa..e3956f0 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -62,8 +62,8 @@
 };
 
 constexpr net::BackoffEntry::Policy kResumeFetchImageBackoffPolicy = {
-    0,              // Number of initial errors to ignore.
-    500,            // Initial delay in ms.
+    kMaxConsecutiveReadPhotoFailures,  // Number of initial errors to ignore.
+    500,                               // Initial delay in ms.
     2.0,            // Factor by which the waiting time will be multiplied.
     0.2,            // Fuzzing percentage.
     8 * 60 * 1000,  // Maximum delay in ms.
@@ -538,7 +538,12 @@
   if (retries_to_read_from_cache_ == 0) {
     if (backup_retries_to_read_from_cache_ == 0) {
       LOG(WARNING) << "Failed to read from cache";
-      if (topic_index_ == ambient_backend_model_.topics().size()) {
+      ambient_backend_model_.AddImageFailure();
+      // Do not refresh image if image loading has failed repeatedly, or there
+      // are no more topics to retry.
+      if (ambient_backend_model_.ImageLoadingFailed() ||
+          topic_index_ == ambient_backend_model_.topics().size()) {
+        LOG(WARNING) << "Not attempting image refresh";
         image_refresh_started_ = false;
         return;
       }
diff --git a/ash/ambient/model/ambient_backend_model.cc b/ash/ambient/model/ambient_backend_model.cc
index 3e3b0f34..18cb899b 100644
--- a/ash/ambient/model/ambient_backend_model.cc
+++ b/ash/ambient/model/ambient_backend_model.cc
@@ -8,6 +8,7 @@
 
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/model/ambient_backend_model_observer.h"
+#include "base/logging.h"
 
 namespace ash {
 
@@ -62,6 +63,7 @@
 
 void AmbientBackendModel::AddNextImage(
     const PhotoWithDetails& photo_with_details) {
+  ResetImageFailures();
   if (current_image_.IsNull()) {
     current_image_ = photo_with_details;
   } else if (next_image_.IsNull()) {
@@ -79,6 +81,23 @@
   return GetNextImage().hash == hash;
 }
 
+void AmbientBackendModel::AddImageFailure() {
+  failures_++;
+  if (ImageLoadingFailed()) {
+    DVLOG(3) << "image loading failed";
+    for (auto& observer : observers_)
+      observer.OnImagesFailed();
+  }
+}
+
+void AmbientBackendModel::ResetImageFailures() {
+  failures_ = 0;
+}
+
+bool AmbientBackendModel::ImageLoadingFailed() {
+  return !ImagesReady() && failures_ >= kMaxConsecutiveReadPhotoFailures;
+}
+
 base::TimeDelta AmbientBackendModel::GetPhotoRefreshInterval() {
   if (!ImagesReady())
     return base::TimeDelta();
diff --git a/ash/ambient/model/ambient_backend_model.h b/ash/ambient/model/ambient_backend_model.h
index 27ae662..d373491 100644
--- a/ash/ambient/model/ambient_backend_model.h
+++ b/ash/ambient/model/ambient_backend_model.h
@@ -65,6 +65,13 @@
   // If the hash matches the hash of the next image to be displayed.
   bool HashMatchesNextImage(const std::string& hash) const;
 
+  // Record that fetching an image has failed.
+  void AddImageFailure();
+
+  void ResetImageFailures();
+
+  bool ImageLoadingFailed();
+
   // Get/Set the photo refresh interval.
   base::TimeDelta GetPhotoRefreshInterval();
   void SetPhotoRefreshInterval(base::TimeDelta interval);
@@ -98,6 +105,7 @@
 
  private:
   friend class AmbientBackendModelTest;
+  friend class AmbientAshTestBase;
 
   void NotifyTopicsChanged();
   void NotifyImagesChanged();
@@ -115,6 +123,9 @@
   float temperature_fahrenheit_ = 0.0f;
   bool show_celsius_ = false;
 
+  // The number of consecutive failures to load the next image.
+  int failures_ = 0;
+
   // The interval to refresh photos.
   base::TimeDelta photo_refresh_interval_;
 
diff --git a/ash/ambient/model/ambient_backend_model_observer.h b/ash/ambient/model/ambient_backend_model_observer.h
index 2468235..fcac8a9b 100644
--- a/ash/ambient/model/ambient_backend_model_observer.h
+++ b/ash/ambient/model/ambient_backend_model_observer.h
@@ -24,6 +24,10 @@
   // Invoked when enough images are loaded in memory to start ambient mode.
   virtual void OnImagesReady() {}
 
+  // Invoked when fetching images has failed and not enough images are present
+  // to start ambient mode.
+  virtual void OnImagesFailed() {}
+
   // Invoked when the weather info (condition icon or temperature) stored in the
   // model has been updated.
   virtual void OnWeatherInfoUpdated() {}
diff --git a/ash/ambient/model/ambient_backend_model_unittest.cc b/ash/ambient/model/ambient_backend_model_unittest.cc
index 55b5e4a..4d00b9e 100644
--- a/ash/ambient/model/ambient_backend_model_unittest.cc
+++ b/ash/ambient/model/ambient_backend_model_unittest.cc
@@ -10,12 +10,25 @@
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/model/ambient_backend_model_observer.h"
 #include "ash/test/ash_test_base.h"
+#include "base/scoped_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/views/controls/image_view.h"
 
 namespace ash {
 
+namespace {
+class MockAmbientBackendModelObserver : public AmbientBackendModelObserver {
+ public:
+  MockAmbientBackendModelObserver() = default;
+  ~MockAmbientBackendModelObserver() override = default;
+
+  MOCK_METHOD(void, OnImagesFailed, (), (override));
+};
+
+}  // namespace
+
 class AmbientBackendModelTest : public AshTestBase {
  public:
   AmbientBackendModelTest() = default;
@@ -75,6 +88,8 @@
     return ambient_backend_model_->GetNextImage();
   }
 
+  int failure_count() { return ambient_backend_model_->failures_; }
+
  private:
   std::unique_ptr<AmbientBackendModel> ambient_backend_model_;
 };
@@ -116,4 +131,39 @@
   EXPECT_EQ(GetPhotoRefreshInterval(), interval);
 }
 
+TEST_F(AmbientBackendModelTest, ShouldNotifyObserversIfImagesFailed) {
+  ambient_backend_model()->Clear();
+  testing::NiceMock<MockAmbientBackendModelObserver> observer;
+  ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver> scoped_obs{
+      &observer};
+
+  scoped_obs.Add(ambient_backend_model());
+
+  EXPECT_CALL(observer, OnImagesFailed).Times(1);
+
+  for (int i = 0; i < kMaxConsecutiveReadPhotoFailures; i++) {
+    ambient_backend_model()->AddImageFailure();
+  }
+}
+
+TEST_F(AmbientBackendModelTest, ShouldResetFailuresOnAddImage) {
+  testing::NiceMock<MockAmbientBackendModelObserver> observer;
+  ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver> scoped_obs{
+      &observer};
+
+  scoped_obs.Add(ambient_backend_model());
+
+  EXPECT_CALL(observer, OnImagesFailed).Times(0);
+
+  for (int i = 0; i < kMaxConsecutiveReadPhotoFailures - 1; i++) {
+    ambient_backend_model()->AddImageFailure();
+  }
+
+  EXPECT_EQ(failure_count(), kMaxConsecutiveReadPhotoFailures - 1);
+
+  AddNTestImages(1);
+
+  EXPECT_EQ(failure_count(), 0);
+}
+
 }  // namespace ash