chromium/chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/public/cpp/system_tray.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/phonehub/phone_hub_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/phonehub/camera_roll_item.h"
#include "chromeos/ash/components/phonehub/fake_phone_hub_manager.h"
#include "chromeos/ash/components/phonehub/notification.h"
#include "chromeos/ash/components/phonehub/pref_names.h"
#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h"
#include "components/prefs/pref_service.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"

namespace ash::multidevice {

namespace {

const int kIconSize = 16;
const int kContactImageSize = 80;
const int kSharedImageSize = 400;
const int kCameraRollThumbnailSize = 96;

// Fake image types used for fields that require gfx::Image().
enum class ImageType {
  kPink = 1,
  kRed = 2,
  kGreen = 3,
  kBlue = 4,
  kYellow = 5,
};

const SkBitmap ImageTypeToBitmap(ImageType image_type_num, int size) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(size, size);
  switch (image_type_num) {
    case ImageType::kPink:
      bitmap.eraseARGB(255, 255, 192, 203);
      break;
    case ImageType::kRed:
      bitmap.eraseARGB(255, 255, 0, 0);
      break;
    case ImageType::kGreen:
      bitmap.eraseARGB(255, 0, 255, 0);
      break;
    case ImageType::kBlue:
      bitmap.eraseARGB(255, 0, 0, 255);
      break;
    case ImageType::kYellow:
      bitmap.eraseARGB(255, 255, 255, 0);
      break;
    default:
      break;
  }
  return bitmap;
}

phonehub::Notification::AppMetadata DictToAppMetadata(
    const base::Value::Dict* app_metadata_dict) {
  const std::string* visible_app_name_ptr =
      app_metadata_dict->FindString("visibleAppName");
  CHECK(visible_app_name_ptr);
  std::u16string visible_app_name = base::UTF8ToUTF16(*visible_app_name_ptr);

  const std::string* package_name =
      app_metadata_dict->FindString("packageName");
  CHECK(package_name);

  std::optional<int> icon_image_type_as_int =
      app_metadata_dict->FindInt("icon");
  CHECK(icon_image_type_as_int);

  auto icon_image_type = static_cast<ImageType>(*icon_image_type_as_int);
  gfx::Image icon = gfx::Image::CreateFrom1xBitmap(
      ImageTypeToBitmap(icon_image_type, kIconSize));

  int user_id = app_metadata_dict->FindInt("userId").value_or(0);

  return phonehub::Notification::AppMetadata(
      visible_app_name, *package_name, /* color_icon= */ icon,
      /* monochrome_icon_mask= */ std::nullopt,
      /* icon_color= */ std::nullopt,
      /* icon_is_monochrome= */ false, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
}

void TryAddingMetadata(
    const std::string& key,
    const base::Value::Dict* browser_tab_status_dict,
    std::vector<phonehub::BrowserTabsModel::BrowserTabMetadata>& metadatas) {
  const base::Value::Dict* browser_tab_metadata =
      browser_tab_status_dict->FindDict(key);

  if (!browser_tab_metadata)
    return;

  const std::string* url = browser_tab_metadata->FindString("url");
  if (!url || url->empty())
    return;

  const std::string* title = browser_tab_metadata->FindString("title");
  if (!title)
    return;

  // JavaScript time stamps don't fit in int.
  std::optional<double> last_accessed_timestamp =
      browser_tab_metadata->FindDouble("lastAccessedTimeStamp");
  if (!last_accessed_timestamp)
    return;

  int favicon_image_type_as_int =
      browser_tab_metadata->FindInt("favicon").value_or(0);
  if (!favicon_image_type_as_int)
    return;

  auto favicon_image_type = static_cast<ImageType>(favicon_image_type_as_int);
  gfx::Image favicon = gfx::Image::CreateFrom1xBitmap(
      ImageTypeToBitmap(favicon_image_type, kIconSize));

  auto metadata = phonehub::BrowserTabsModel::BrowserTabMetadata(
      GURL(*url), base::UTF8ToUTF16(*title),
      base::Time::FromMillisecondsSinceUnixEpoch(*last_accessed_timestamp),
      favicon);

  metadatas.push_back(metadata);
}

const SkBitmap RGB_Bitmap(U8CPU r, U8CPU g, U8CPU b, int size) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(size, size);
  bitmap.eraseARGB(255, r, g, b);
  return bitmap;
}

}  // namespace

MultidevicePhoneHubHandler::MultidevicePhoneHubHandler() = default;

MultidevicePhoneHubHandler::~MultidevicePhoneHubHandler() {
  if (fake_phone_hub_manager_)
    EnableRealPhoneHubManager();
}

void MultidevicePhoneHubHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "setFakePhoneHubManagerEnabled",
      base::BindRepeating(
          &MultidevicePhoneHubHandler::HandleEnableFakePhoneHubManager,
          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setFeatureStatus",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFeatureStatus,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setShowOnboardingFlow",
      base::BindRepeating(
          &MultidevicePhoneHubHandler::HandleSetShowOnboardingFlow,
          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setFakePhoneName",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakePhoneName,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setFakePhoneStatus",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakePhoneStatus,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setBrowserTabs",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetBrowserTabs,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setNotification",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetNotification,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "removeNotification",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleRemoveNotification,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "enableDnd",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleEnableDnd,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setFindMyDeviceStatus",
      base::BindRepeating(
          &MultidevicePhoneHubHandler::HandleSetFindMyDeviceStatus,
          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setTetherStatus",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetTetherStatus,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "resetShouldShowOnboardingUi",
      base::BindRepeating(
          &MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi,
          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "resetHasMultideviceFeatureSetupUiBeenDismissed",
      base::BindRepeating(
          &MultidevicePhoneHubHandler::
              HandleResetHasMultideviceFeatureSetupUiBeenDismissed,
          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setFakeCameraRoll",
      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakeCameraRoll,
                          base::Unretained(this)));
}

void MultidevicePhoneHubHandler::OnJavascriptDisallowed() {
  RemoveObservers();
}

void MultidevicePhoneHubHandler::AddObservers() {
  notification_manager_observation_.Observe(
      fake_phone_hub_manager_->fake_notification_manager());
  do_not_disturb_controller_observation_.Observe(
      fake_phone_hub_manager_->fake_do_not_disturb_controller());
  find_my_device_controller_observation_.Observe(
      fake_phone_hub_manager_->fake_find_my_device_controller());
  tether_controller_observation_.Observe(
      fake_phone_hub_manager_->fake_tether_controller());
  onboarding_ui_tracker_observation_.Observe(
      fake_phone_hub_manager_->fake_onboarding_ui_tracker());
  camera_roll_manager_observation_.Observe(
      fake_phone_hub_manager_->fake_camera_roll_manager());
}

void MultidevicePhoneHubHandler::RemoveObservers() {
  notification_manager_observation_.Reset();
  do_not_disturb_controller_observation_.Reset();
  find_my_device_controller_observation_.Reset();
  tether_controller_observation_.Reset();
  onboarding_ui_tracker_observation_.Reset();
  camera_roll_manager_observation_.Reset();
}

void MultidevicePhoneHubHandler::OnNotificationsRemoved(
    const base::flat_set<int64_t>& notification_ids) {
  base::Value::List removed_notification_id_js_list;
  for (const int64_t& id : notification_ids) {
    removed_notification_id_js_list.Append(static_cast<double>(id));
  }
  FireWebUIListener("removed-notification-ids",
                    removed_notification_id_js_list);
}

void MultidevicePhoneHubHandler::OnDndStateChanged() {
  bool is_dnd_enabled =
      fake_phone_hub_manager_->fake_do_not_disturb_controller()->IsDndEnabled();
  FireWebUIListener("is-dnd-enabled-changed", base::Value(is_dnd_enabled));
}

void MultidevicePhoneHubHandler::OnPhoneRingingStateChanged() {
  phonehub::FindMyDeviceController::Status ringing_status =
      fake_phone_hub_manager_->fake_find_my_device_controller()
          ->GetPhoneRingingStatus();

  FireWebUIListener("find-my-device-status-changed",
                    base::Value(static_cast<int>(ringing_status)));
}

void MultidevicePhoneHubHandler::OnTetherStatusChanged() {
  int status_as_int = static_cast<int>(
      fake_phone_hub_manager_->fake_tether_controller()->GetStatus());
  FireWebUIListener("tether-status-changed", base::Value(status_as_int));
}

void MultidevicePhoneHubHandler::OnShouldShowOnboardingUiChanged() {
  bool should_show_onboarding_ui =
      fake_phone_hub_manager_->fake_onboarding_ui_tracker()
          ->ShouldShowOnboardingUi();
  FireWebUIListener("should-show-onboarding-ui-changed",
                    base::Value(should_show_onboarding_ui));
}

void MultidevicePhoneHubHandler::OnCameraRollViewUiStateUpdated() {
  base::Value::Dict camera_roll_dict;
  camera_roll_dict.Set("isCameraRollEnabled",
                       fake_phone_hub_manager_->fake_camera_roll_manager()
                           ->is_camera_roll_enabled());
  FireWebUIListener("camera-roll-ui-view-state-updated", camera_roll_dict);
}

void MultidevicePhoneHubHandler::HandleEnableDnd(
    const base::Value::List& list) {
  CHECK(!list.empty());
  const bool enabled = list[0].GetBool();
  PA_LOG(VERBOSE) << "Setting Do Not Disturb state to " << enabled;
  fake_phone_hub_manager_->fake_do_not_disturb_controller()
      ->SetDoNotDisturbStateInternal(enabled,
                                     /*can_request_new_dnd_state=*/true);
}

void MultidevicePhoneHubHandler::HandleSetFindMyDeviceStatus(
    const base::Value::List& list) {
  CHECK_GE(list.size(), 1u);
  int status_as_int = list[0].GetInt();

  auto status =
      static_cast<phonehub::FindMyDeviceController::Status>(status_as_int);
  PA_LOG(VERBOSE) << "Setting phone ringing status to " << status;
  fake_phone_hub_manager_->fake_find_my_device_controller()
      ->SetPhoneRingingState(status);
}

void MultidevicePhoneHubHandler::HandleSetTetherStatus(
    const base::Value::List& list) {
  CHECK_GE(list.size(), 1u);
  int status_as_int = list[0].GetInt();

  auto status = static_cast<phonehub::TetherController::Status>(status_as_int);
  PA_LOG(VERBOSE) << "Setting tether status to " << status;
  fake_phone_hub_manager_->fake_tether_controller()->SetStatus(status);
}

void MultidevicePhoneHubHandler::EnableRealPhoneHubManager() {
  // If no FakePhoneHubManager is active, return early. This ensures that we
  // don't unnecessarily re-initialize the Phone Hub UI.
  if (!fake_phone_hub_manager_)
    return;

  PA_LOG(VERBOSE) << "Setting real Phone Hub Manager";
  Profile* profile = Profile::FromWebUI(web_ui());
  auto* phone_hub_manager =
      phonehub::PhoneHubManagerFactory::GetForProfile(profile);
  SystemTray::Get()->SetPhoneHubManager(phone_hub_manager);

  RemoveObservers();
  fake_phone_hub_manager_.reset();
}

void MultidevicePhoneHubHandler::EnableFakePhoneHubManager() {
  // Don't create FakePhoneHubManager if it already exists to prevent UAF.
  if (fake_phone_hub_manager_)
    return;

  PA_LOG(VERBOSE) << "Setting fake Phone Hub Manager";
  fake_phone_hub_manager_ = std::make_unique<phonehub::FakePhoneHubManager>();
  SystemTray::Get()->SetPhoneHubManager(fake_phone_hub_manager_.get());
  AddObservers();
}

void MultidevicePhoneHubHandler::HandleEnableFakePhoneHubManager(
    const base::Value::List& list) {
  AllowJavascript();
  CHECK(!list.empty());
  const bool enabled = list[0].GetBool();
  if (enabled) {
    EnableFakePhoneHubManager();
    return;
  }
  EnableRealPhoneHubManager();
}

void MultidevicePhoneHubHandler::HandleSetFeatureStatus(
    const base::Value::List& list) {
  CHECK_GE(list.size(), 1u);
  int feature_as_int = list[0].GetInt();

  auto feature = static_cast<phonehub::FeatureStatus>(feature_as_int);
  PA_LOG(VERBOSE) << "Setting feature status to " << feature;
  fake_phone_hub_manager_->fake_feature_status_provider()->SetStatus(feature);
}

void MultidevicePhoneHubHandler::HandleSetShowOnboardingFlow(
    const base::Value::List& list) {
  CHECK(!list.empty());
  const bool show_onboarding_flow = list[0].GetBool();
  PA_LOG(VERBOSE) << "Setting show onboarding flow to " << show_onboarding_flow;
  fake_phone_hub_manager_->fake_onboarding_ui_tracker()
      ->SetShouldShowOnboardingUi(show_onboarding_flow);
}

void MultidevicePhoneHubHandler::HandleSetFakePhoneName(
    const base::Value::List& args_list) {
  CHECK_GE(args_list.size(), 1u);
  std::u16string phone_name = base::UTF8ToUTF16(args_list[0].GetString());
  fake_phone_hub_manager_->mutable_phone_model()->SetPhoneName(phone_name);
  PA_LOG(VERBOSE) << "Set phone name to " << phone_name;
}

void MultidevicePhoneHubHandler::HandleSetFakePhoneStatus(
    const base::Value::List& args) {
  CHECK(args[0].is_dict());
  const base::Value::Dict& phones_status_value = args[0].GetDict();

  std::optional<int> mobile_status_as_int =
      phones_status_value.FindInt("mobileStatus");
  CHECK(mobile_status_as_int);
  auto mobile_status = static_cast<phonehub::PhoneStatusModel::MobileStatus>(
      *mobile_status_as_int);

  std::optional<int> signal_strength_as_int =
      phones_status_value.FindInt("signalStrength");
  CHECK(signal_strength_as_int);

  auto signal_strength =
      static_cast<phonehub::PhoneStatusModel::SignalStrength>(
          *signal_strength_as_int);

  const std::string* mobile_provider_ptr =
      phones_status_value.FindString("mobileProvider");
  CHECK(mobile_provider_ptr);
  std::u16string mobile_provider = base::UTF8ToUTF16(*mobile_provider_ptr);

  std::optional<int> charging_state_as_int =
      phones_status_value.FindInt("chargingState");
  CHECK(charging_state_as_int);
  auto charging_state = static_cast<phonehub::PhoneStatusModel::ChargingState>(
      *charging_state_as_int);

  std::optional<int> battery_saver_state_as_int =
      phones_status_value.FindInt("batterySaverState");
  CHECK(battery_saver_state_as_int);
  auto battery_saver_state =
      static_cast<phonehub::PhoneStatusModel::BatterySaverState>(
          *battery_saver_state_as_int);

  std::optional<int> battery_percentage =
      phones_status_value.FindInt("batteryPercentage");
  CHECK(battery_percentage);

  phonehub::PhoneStatusModel::MobileConnectionMetadata connection_metadata = {
      .signal_strength = signal_strength,
      .mobile_provider = mobile_provider,
  };
  auto phone_status = phonehub::PhoneStatusModel(
      mobile_status, connection_metadata, charging_state, battery_saver_state,
      *battery_percentage);
  fake_phone_hub_manager_->mutable_phone_model()->SetPhoneStatusModel(
      phone_status);

  PA_LOG(VERBOSE) << "Set phone status to -"
                  << "\n  mobile status: " << mobile_status
                  << "\n  signal strength: " << signal_strength
                  << "\n  mobile provider: " << mobile_provider
                  << "\n  charging state: " << charging_state
                  << "\n  battery saver state: " << battery_saver_state
                  << "\n  battery percentage: " << *battery_percentage;
}

void MultidevicePhoneHubHandler::HandleSetBrowserTabs(
    const base::Value::List& args) {
  CHECK(args[0].is_dict());
  const base::Value::Dict& browser_tab_status_dict = args[0].GetDict();
  bool is_tab_sync_enabled =
      browser_tab_status_dict.FindBool("isTabSyncEnabled").value();

  if (!is_tab_sync_enabled) {
    fake_phone_hub_manager_->mutable_phone_model()->SetBrowserTabsModel(
        phonehub::BrowserTabsModel(is_tab_sync_enabled));
    PA_LOG(VERBOSE) << "Tab sync off; cleared browser tab metadata";
    return;
  }

  std::vector<phonehub::BrowserTabsModel::BrowserTabMetadata> metadatas;
  TryAddingMetadata("browserTabOneMetadata", &browser_tab_status_dict,
                    metadatas);
  TryAddingMetadata("browserTabTwoMetadata", &browser_tab_status_dict,
                    metadatas);

  fake_phone_hub_manager_->mutable_phone_model()->SetBrowserTabsModel(
      phonehub::BrowserTabsModel(is_tab_sync_enabled, metadatas));

  auto browser_tabs_model =
      fake_phone_hub_manager_->mutable_phone_model()->browser_tabs_model();
  CHECK(browser_tabs_model.has_value());

  // Log the most recently visited browser tab (at index 0) last.
  for (size_t i = metadatas.size(); i-- > 0;) {
    PA_LOG(VERBOSE) << "Set most recent browser tab number " << i
                    << " to: " << browser_tabs_model->most_recent_tabs()[i];
  }
}

void MultidevicePhoneHubHandler::HandleSetNotification(
    const base::Value::List& args) {
  CHECK(args[0].is_dict());
  const base::Value::Dict& notification_data_value = args[0].GetDict();

  std::optional<int> id = notification_data_value.FindInt("id");
  CHECK(id);

  const base::Value::Dict* app_metadata_dict =
      notification_data_value.FindDict("appMetadata");
  CHECK(app_metadata_dict);
  phonehub::Notification::AppMetadata app_metadata =
      DictToAppMetadata(app_metadata_dict);

  // JavaScript time stamps don't fit in int.
  std::optional<double> js_timestamp =
      notification_data_value.FindDouble("timestamp");
  CHECK(js_timestamp);
  auto timestamp = base::Time::FromMillisecondsSinceUnixEpoch(*js_timestamp);

  std::optional<int> importance_as_int =
      notification_data_value.FindInt("importance");
  CHECK(importance_as_int);
  auto importance =
      static_cast<phonehub::Notification::Importance>(*importance_as_int);

  std::optional<int> inline_reply_id =
      notification_data_value.FindInt("inlineReplyId");
  CHECK(inline_reply_id);

  std::optional<std::u16string> opt_title;
  const std::string* title = notification_data_value.FindString("title");
  if (title && !title->empty())
    opt_title = base::UTF8ToUTF16(*title);

  std::optional<std::u16string> opt_text_content;
  if (const std::string* text_content =
          notification_data_value.FindString("textContent")) {
    if (!text_content->empty())
      opt_text_content = base::UTF8ToUTF16(*text_content);
  }

  std::optional<gfx::Image> opt_shared_image;
  int shared_image_type_as_int =
      notification_data_value.FindInt("sharedImage").value_or(0);
  if (shared_image_type_as_int) {
    auto shared_image_type = static_cast<ImageType>(shared_image_type_as_int);
    opt_shared_image = gfx::Image::CreateFrom1xBitmap(
        ImageTypeToBitmap(shared_image_type, kSharedImageSize));
  }

  std::optional<gfx::Image> opt_contact_image;
  int contact_image_type_as_int =
      notification_data_value.FindInt("contactImage").value_or(0);
  if (contact_image_type_as_int) {
    auto shared_contact_image_type =
        static_cast<ImageType>(contact_image_type_as_int);
    opt_contact_image = gfx::Image::CreateFrom1xBitmap(
        ImageTypeToBitmap(shared_contact_image_type, kContactImageSize));
  }

  auto notification = phonehub::Notification(
      *id, app_metadata, timestamp, importance,
      phonehub::Notification::Category::kConversation,
      {{phonehub::Notification::ActionType::kInlineReply, *inline_reply_id}},
      phonehub::Notification::InteractionBehavior::kNone, opt_title,
      opt_text_content, opt_shared_image, opt_contact_image);

  PA_LOG(VERBOSE) << "Set notification" << notification;
  fake_phone_hub_manager_->fake_notification_manager()->SetNotification(
      std::move(notification));
}

void MultidevicePhoneHubHandler::HandleRemoveNotification(
    const base::Value::List& list) {
  CHECK_GE(list.size(), 1u);
  int notification_id = list[0].GetInt();
  fake_phone_hub_manager_->fake_notification_manager()->RemoveNotification(
      notification_id);
  PA_LOG(VERBOSE) << "Removed notification with id " << notification_id;
}

void MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi(
    const base::Value::List& list) {
  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
  prefs->SetBoolean(phonehub::prefs::kHideOnboardingUi, false);
  PA_LOG(VERBOSE) << "Reset kHideOnboardingUi pref";
}

void MultidevicePhoneHubHandler::
    HandleResetHasMultideviceFeatureSetupUiBeenDismissed(
        const base::Value::List& list) {
  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
  prefs->SetBoolean(phonehub::prefs::kHasDismissedSetupRequiredUi, false);
  PA_LOG(VERBOSE) << "Reset kHasDismissedSetupRequiredUi pref";
}

void MultidevicePhoneHubHandler::HandleSetFakeCameraRoll(
    const base::Value::List& args) {
  const base::Value& camera_roll_dict = args[0];
  CHECK(camera_roll_dict.is_dict());

  std::optional<bool> is_camera_roll_enabled =
      camera_roll_dict.GetDict().FindBool("isCameraRollEnabled");
  CHECK(is_camera_roll_enabled);

  fake_phone_hub_manager_->fake_camera_roll_manager()
      ->SetIsCameraRollAvailableToBeEnabled(!*is_camera_roll_enabled);

  std::optional<bool> is_file_access_granted =
      camera_roll_dict.GetDict().FindBool("isFileAccessGranted");
  CHECK(is_file_access_granted);

  fake_phone_hub_manager_->fake_camera_roll_manager()
      ->SetIsAndroidStorageGranted(*is_file_access_granted);

  std::optional<int> number_of_thumbnails =
      camera_roll_dict.GetDict().FindInt("numberOfThumbnails");
  CHECK(number_of_thumbnails);

  std::optional<int> file_type_as_int =
      camera_roll_dict.GetDict().FindInt("fileType");
  CHECK(file_type_as_int);
  const char* file_type;
  const char* file_ext;
  if (*file_type_as_int == 0) {
    file_type = "image/jpeg";
    file_ext = ".jpg";
  } else {
    file_type = "video/mp4";
    file_ext = ".mp4";
  }

  if (*number_of_thumbnails == 0) {
    fake_phone_hub_manager_->fake_camera_roll_manager()->ClearCurrentItems();
  } else {
    std::vector<phonehub::CameraRollItem> items;
    // Create items in descending key order
    for (int i = *number_of_thumbnails; i > 0; --i) {
      phonehub::proto::CameraRollItemMetadata metadata;
      metadata.set_key(base::NumberToString(i));
      metadata.set_mime_type(file_type);
      metadata.set_last_modified_millis(1577865600 + i);
      metadata.set_file_size_bytes(123456);
      metadata.set_file_name("fake_file_" + base::NumberToString(i) + file_ext);

      gfx::Image thumbnail = gfx::Image::CreateFrom1xBitmap(RGB_Bitmap(
          255 - i * 192 / *number_of_thumbnails,
          63 + i * 192 / *number_of_thumbnails, 255, kCameraRollThumbnailSize));

      items.emplace_back(metadata, thumbnail);
    }
    fake_phone_hub_manager_->fake_camera_roll_manager()->SetCurrentItems(items);
  }

  std::optional<int> download_result_as_int =
      camera_roll_dict.GetDict().FindInt("downloadResult");
  CHECK(download_result_as_int);
  const char* download_result;
  if (*download_result_as_int == 0) {
    download_result = "Download Success";
    fake_phone_hub_manager_->fake_camera_roll_manager()
        ->SetSimulatedDownloadError(false);
  } else {
    phonehub::CameraRollManager::Observer::DownloadErrorType error_type;
    switch (*download_result_as_int) {
      case 1:
        download_result = "Generic Error";
        error_type = phonehub::CameraRollManager::Observer::DownloadErrorType::
            kGenericError;
        break;
      case 2:
        download_result = "Storage Error";
        error_type = phonehub::CameraRollManager::Observer::DownloadErrorType::
            kInsufficientStorage;
        break;
      case 3:
      default:
        download_result = "Network Error";
        error_type = phonehub::CameraRollManager::Observer::DownloadErrorType::
            kNetworkConnection;
        break;
    }
    fake_phone_hub_manager_->fake_camera_roll_manager()
        ->SetSimulatedDownloadError(true);
    fake_phone_hub_manager_->fake_camera_roll_manager()->SetSimulatedErrorType(
        error_type);
  }

  PA_LOG(VERBOSE) << "Setting fake Camera Roll to:\n  Feature enabled: "
                  << *is_camera_roll_enabled
                  << "\n  Access granted: " << *is_file_access_granted
                  << "\n  Number of thumbnails: " << *number_of_thumbnails
                  << "\n  File type: " << file_type
                  << "\n  Download result: " << download_result;
}

}  // namespace ash::multidevice