chromium/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.cc

// Copyright 2022 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/office_fallback/office_fallback_dialog.h"

#include <cstddef>
#include <string>
#include <utility>

#include "base/functional/callback_forward.h"
#include "base/json/json_writer.h"
#include "base/notreached.h"
#include "chrome/browser/ash/file_manager/office_file_tasks.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"

namespace {
// A "-1" resource ID is used to indicate that the fallback dialog will not
// display a "fallback reason" message.
const int kNoReasonMessageResourceId = -1;

// Width of the Fallback dialog as found with the inspector tool.
const int kWidth = 512;

// Exact height of the Fallback dialogs required for different texts (in
// English) as found with the inspector tool.
const int kOfflineHeight = 244;
const int kDisableDrivePreferenceSetHeight = 268;
const int kDriveUnavailableHeight = 268;
const int kDriveDisabledForAccountType = 268;
const int kMeteredHeight = 268;
const int kWaitingForUploadHeight = 228;
const int kAndroidOneDriveUnsupportedLocationHeight = 244;

// Height of a line of text as found with the inspector tool.
const int kLineHeight = 20;

// Get the text ids for the `fallback_reason` specific translated strings that
// will be displayed in dialog. Store them in the out parameters `title_id`,
// `reason_message_id` and `instructions_message_id`. Get the corresponding
// width and height needed to display these strings in the dialog. Store them in
// the out parameters `width` and `height`.
void GetDialogTextIdsAndSize(
    const ash::office_fallback::FallbackReason fallback_reason,
    int& title_id,
    int& reason_message_id,
    bool& include_task_in_reason_message,
    int& instructions_message_id,
    bool& enable_retry_option,
    bool& enable_quick_office_option,
    int& width,
    int& height) {
  width = kWidth;
  include_task_in_reason_message = false;
  switch (fallback_reason) {
    case ash::office_fallback::FallbackReason::kOffline:
    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
      title_id = IDS_OFFICE_FALLBACK_TITLE_OFFLINE;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_OFFLINE;
      include_task_in_reason_message = true;
      instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS_OFFLINE;
      enable_retry_option = true;
      enable_quick_office_option = true;
      height = kOfflineHeight;
      break;
    case ash::office_fallback::FallbackReason::kDisableDrivePreferenceSet:
      title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE;
      include_task_in_reason_message = true;
      instructions_message_id =
          IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE;
      enable_retry_option = true;
      enable_quick_office_option = true;
      height = kDisableDrivePreferenceSetHeight;
      break;
    case ash::office_fallback::FallbackReason::kDriveDisabledForAccountType:
      title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT;
      instructions_message_id =
          IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT;
      enable_retry_option = true;
      enable_quick_office_option = true;
      height = kDriveDisabledForAccountType;
      break;
    case ash::office_fallback::FallbackReason::kMeteredConnection:
      title_id = IDS_OFFICE_FALLBACK_TITLE_METERED;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_METERED;
      instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED;
      enable_retry_option = true;
      enable_quick_office_option = true;
      height = kMeteredHeight;
      break;
    case ash::office_fallback::FallbackReason::kDriveDisabled:
    case ash::office_fallback::FallbackReason::kNoDriveService:
    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
      title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE;
      include_task_in_reason_message = true;
      instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS;
      enable_retry_option = true;
      enable_quick_office_option = true;
      height = kDriveUnavailableHeight;
      break;
    case ash::office_fallback::FallbackReason::kWaitingForUpload:
      title_id = IDS_OFFICE_FALLBACK_TITLE_WAITING_FOR_UPLOAD;
      reason_message_id = IDS_OFFICE_FALLBACK_REASON_WAITING_FOR_UPLOAD;
      instructions_message_id =
          IDS_OFFICE_FALLBACK_INSTRUCTIONS_WAITING_FOR_UPLOAD;
      enable_retry_option = false;
      enable_quick_office_option = false;
      height = kWaitingForUploadHeight;
      break;
    case ash::office_fallback::FallbackReason::
        kAndroidOneDriveUnsupportedLocation:
      title_id =
          IDS_OFFICE_FALLBACK_TITLE_ANDROID_ONE_DRIVE_LOCATION_NOT_SUPPORTED;
      reason_message_id = kNoReasonMessageResourceId;
      instructions_message_id =
          IDS_OFFICE_FALLBACK_INSTRUCTIONS_ANDROID_ONE_DRIVE_LOCATION_NOT_SUPPORTED;
      enable_retry_option = false;
      enable_quick_office_option = true;
      height = kAndroidOneDriveUnsupportedLocationHeight;
      break;
  }
  // Add extra height to account for translations.
  height += kLineHeight;
}
}  // namespace

namespace ash::office_fallback {

// static
bool OfficeFallbackDialog::Show(
    const std::vector<storage::FileSystemURL>& file_urls,
    FallbackReason fallback_reason,
    const std::string& task_title,
    DialogChoiceCallback callback) {
  // Allow no more than one office fallback dialog at a time. In the case of
  // multiple dialog requests, they should either be handled simultaneously or
  // queued.
  if (SystemWebDialogDelegate::HasInstance(
          GURL(chrome::kChromeUIOfficeFallbackURL))) {
    LOG(WARNING) << "Another fallback dialog is already being shown";
    std::move(callback).Run(std::nullopt);
    return false;
  }

  DCHECK(!file_urls.empty());
  if (file_urls.empty()) {
    LOG(ERROR) << "No file urls";
    std::move(callback).Run(std::nullopt);
    return false;
  }

  // TODO(b/242685536) When multi-file selection is defined, display file names
  // appropriately. Currently, file_urls_ is just a singleton array.
  // TODO(cassycc): Handle long file name(s).
  // Get file name to display.
  const std::u16string file_name(
      file_urls.front().path().BaseName().LossyDisplayName());

  if (task_title.empty()) {
    LOG(WARNING) << "task_title was empty";
    std::move(callback).Run(std::nullopt);
    return false;
  }

  // Get failure specific text to display in dialog.
  int title_id;
  int reason_message_id;
  bool include_task_in_reason_message;
  int instructions_message_id;
  bool enable_retry_option;
  bool enable_quick_office_option;
  int width;
  int height;
  GetDialogTextIdsAndSize(fallback_reason, title_id, reason_message_id,
                          include_task_in_reason_message,
                          instructions_message_id, enable_retry_option,
                          enable_quick_office_option, width, height);
  // TODO(cassycc): Figure out how to add the web_drive to the placeholder in
  // IDS_OFFICE_FALLBACK_TITLE_WEB_DRIVE_UNAVAILABLE.
  const std::string title_text = l10n_util::GetStringFUTF8(title_id, file_name);
  std::string reason_message = "";
  if (reason_message_id != kNoReasonMessageResourceId) {
    reason_message = include_task_in_reason_message
                         ? l10n_util::GetStringFUTF8(
                               reason_message_id, base::UTF8ToUTF16(task_title))
                         : l10n_util::GetStringUTF8(reason_message_id);
  }
  const std::string instructions_message =
      l10n_util::GetStringUTF8(instructions_message_id);

  // The pointer is managed by an instance of `views::WebDialogView` and removed
  // in `SystemWebDialogDelegate::OnDialogClosed`.
  OfficeFallbackDialog* dialog = new OfficeFallbackDialog(
      file_urls, title_text, reason_message, instructions_message,
      enable_retry_option, enable_quick_office_option, width, height,
      std::move(callback));

  dialog->ShowSystemDialog();
  return true;
}

void OfficeFallbackDialog::OnDialogClosed(const std::string& choice) {
  // Save callback as local variable before member variables are deleted during
  // dialog close.
  DialogChoiceCallback callback = std::move(callback_);
  // Delete class.
  SystemWebDialogDelegate::OnDialogClosed(choice);
  // Run callback after dialog closed.
  if (callback)
    std::move(callback).Run(choice);
}

OfficeFallbackDialog::OfficeFallbackDialog(
    const std::vector<storage::FileSystemURL>& file_urls,
    const std::string& title_text,
    const std::string& reason_message,
    const std::string& instructions_message,
    const bool& enable_retry_option,
    const bool& enable_quick_office_option,
    const int& width,
    const int& height,
    DialogChoiceCallback callback)
    : SystemWebDialogDelegate(GURL(chrome::kChromeUIOfficeFallbackURL),
                              std::u16string() /* title */),
      file_urls_(file_urls),
      title_text_(title_text),
      reason_message_(reason_message),
      instructions_message_(instructions_message),
      enable_retry_option_(enable_retry_option),
      enable_quick_office_option_(enable_quick_office_option),
      width_(width),
      height_(height),
      callback_(std::move(callback)) {}

OfficeFallbackDialog::~OfficeFallbackDialog() = default;

std::string OfficeFallbackDialog::GetDialogArgs() const {
  base::Value::Dict args;
  args.Set("titleText", title_text_);
  args.Set("reasonMessage", reason_message_);
  args.Set("instructionsMessage", instructions_message_);
  args.Set("enableRetryOption", enable_retry_option_);
  args.Set("enableQuickOfficeOption", enable_quick_office_option_);
  std::string json;
  base::JSONWriter::Write(args, &json);
  return json;
}

void OfficeFallbackDialog::GetDialogSize(gfx::Size* size) const {
  size->SetSize(width_, height_);
}

bool OfficeFallbackDialog::ShouldCloseDialogOnEscape() const {
  return false;
}

bool OfficeFallbackDialog::ShouldShowCloseButton() const {
  return false;
}

}  // namespace ash::office_fallback