chromium/chrome/browser/ui/views/arc_app_dialog_view.cc

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

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h"
#include "chrome/browser/ash/app_list/arc/arc_app_dialog.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/arc/arc_usb_host_permission_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"

namespace arc {

namespace {

const int kArcAppIconSize = 64;
// Currenty ARC apps only support 48*48 native icon.
const int kIconSourceSize = 48;

using ArcAppConfirmCallback = base::OnceCallback<void(bool accept)>;

class ArcAppDialogView : public views::DialogDelegateView,
                         public AppIconLoaderDelegate {
  METADATA_HEADER(ArcAppDialogView, views::DialogDelegateView)

 public:
  ArcAppDialogView(Profile* profile,
                   AppListControllerDelegate* controller,
                   const std::string& app_id,
                   const std::u16string& window_title,
                   const std::u16string& heading_text,
                   const std::u16string& subheading_text,
                   const std::u16string& confirm_button_text,
                   const std::u16string& cancel_button_text,
                   ArcAppConfirmCallback confirm_callback);
  ArcAppDialogView(const ArcAppDialogView&) = delete;
  ArcAppDialogView& operator=(const ArcAppDialogView&) = delete;
  ~ArcAppDialogView() override;

  // Public method used for test only.
  void ConfirmOrCancelForTest(bool confirm);

 private:
  // AppIconLoaderDelegate:
  void OnAppImageUpdated(
      const std::string& app_id,
      const gfx::ImageSkia& image,
      bool is_placeholder_icon,
      const std::optional<gfx::ImageSkia>& badge_image) override;

  void AddMultiLineLabel(views::View* parent, const std::u16string& label_text);

  void OnDialogAccepted();
  void OnDialogCancelled();

  raw_ptr<views::ImageView> icon_view_ = nullptr;

  std::unique_ptr<AppServiceAppIconLoader> icon_loader_;

  const raw_ptr<Profile> profile_;

  const std::string app_id_;
  ArcAppConfirmCallback confirm_callback_;
};

// Browsertest use only. Global pointer of currently shown ArcAppDialogView.
ArcAppDialogView* g_current_arc_app_dialog_view = nullptr;

ArcAppDialogView::ArcAppDialogView(Profile* profile,
                                   AppListControllerDelegate* controller,
                                   const std::string& app_id,
                                   const std::u16string& window_title,
                                   const std::u16string& heading_text,
                                   const std::u16string& subheading_text,
                                   const std::u16string& confirm_button_text,
                                   const std::u16string& cancel_button_text,
                                   ArcAppConfirmCallback confirm_callback)
    : profile_(profile),
      app_id_(app_id),
      confirm_callback_(std::move(confirm_callback)) {
  SetTitle(window_title);
  SetButtonLabel(ui::mojom::DialogButton::kOk, confirm_button_text);
  SetButtonLabel(ui::mojom::DialogButton::kCancel, cancel_button_text);
  SetAcceptCallback(base::BindOnce(&ArcAppDialogView::OnDialogAccepted,
                                   base::Unretained(this)));
  SetCancelCallback(base::BindOnce(&ArcAppDialogView::OnDialogCancelled,
                                   base::Unretained(this)));

  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();

  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal,
      provider->GetDialogInsetsForContentType(views::DialogContentType::kText,
                                              views::DialogContentType::kText),
      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));

  SetModalType(ui::mojom::ModalType::kWindow);
  SetShowCloseButton(false);
  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
      views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));

  auto icon_view = std::make_unique<views::ImageView>();
  icon_view->SetPreferredSize(gfx::Size(kArcAppIconSize, kArcAppIconSize));
  icon_view_ = AddChildView(std::move(icon_view));

  auto text_container = std::make_unique<views::View>();
  auto text_container_layout = std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical);
  text_container_layout->set_main_axis_alignment(
      views::BoxLayout::MainAxisAlignment::kCenter);
  text_container_layout->set_cross_axis_alignment(
      views::BoxLayout::CrossAxisAlignment::kStart);
  text_container->SetLayoutManager(std::move(text_container_layout));

  auto* text_container_ptr = AddChildView(std::move(text_container));
  DCHECK(!heading_text.empty());
  AddMultiLineLabel(text_container_ptr, heading_text);
  if (!subheading_text.empty()) {
    AddMultiLineLabel(text_container_ptr, subheading_text);
  }

  // The icon should be loaded asynchronously.
  icon_loader_ = std::make_unique<AppServiceAppIconLoader>(
      profile_, kIconSourceSize, this);
  icon_loader_->FetchImage(app_id_);

  g_current_arc_app_dialog_view = this;
  gfx::NativeWindow parent =
      controller ? controller->GetAppListWindow() : nullptr;
  constrained_window::CreateBrowserModalDialogViews(this, parent)->Show();
}

ArcAppDialogView::~ArcAppDialogView() {
  if (g_current_arc_app_dialog_view == this) {
    g_current_arc_app_dialog_view = nullptr;
  }
}

void ArcAppDialogView::AddMultiLineLabel(views::View* parent,
                                         const std::u16string& label_text) {
  auto label = std::make_unique<views::Label>(label_text);
  label->SetMultiLine(true);
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  label->SetAllowCharacterBreak(true);
  parent->AddChildView(std::move(label));
}

void ArcAppDialogView::ConfirmOrCancelForTest(bool confirm) {
  if (confirm) {
    AcceptDialog();
  } else {
    CancelDialog();
  }
}

void ArcAppDialogView::OnDialogAccepted() {
  // The dialog can either be accepted or cancelled, but never both.
  DCHECK(confirm_callback_);
  std::move(confirm_callback_).Run(true);
}

void ArcAppDialogView::OnDialogCancelled() {
  // The dialog can either be accepted or cancelled, but never both.
  DCHECK(confirm_callback_);
  std::move(confirm_callback_).Run(false);
}

void ArcAppDialogView::OnAppImageUpdated(
    const std::string& app_id,
    const gfx::ImageSkia& image,
    bool is_placeholder_icon,
    const std::optional<gfx::ImageSkia>& badge_image) {
  DCHECK_EQ(app_id, app_id_);
  DCHECK(!image.isNull());
  DCHECK_EQ(image.width(), kIconSourceSize);
  DCHECK_EQ(image.height(), kIconSourceSize);
  icon_view_->SetImageSize(image.size());
  icon_view_->SetImage(image);
}

BEGIN_METADATA(ArcAppDialogView)
END_METADATA

std::unique_ptr<ArcAppListPrefs::AppInfo> GetArcAppInfo(
    Profile* profile,
    const std::string& app_id) {
  ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
  DCHECK(arc_prefs);
  return arc_prefs->GetApp(app_id);
}

}  // namespace

void ShowUsbScanDeviceListPermissionDialog(Profile* profile,
                                           const std::string& app_id,
                                           ArcUsbConfirmCallback callback) {
  std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
      GetArcAppInfo(profile, app_id);
  if (!app_info) {
    std::move(callback).Run(false);
    return;
  }

  std::u16string window_title =
      l10n_util::GetStringUTF16(IDS_ARC_USB_PERMISSION_TITLE);

  std::u16string heading_text = l10n_util::GetStringFUTF16(
      IDS_ARC_USB_SCAN_DEVICE_LIST_PERMISSION_HEADING,
      base::UTF8ToUTF16(app_info->name));

  std::u16string confirm_button_text = l10n_util::GetStringUTF16(IDS_OK);

  std::u16string cancel_button_text = l10n_util::GetStringUTF16(IDS_CANCEL);

  new ArcAppDialogView(profile, nullptr /* controller */, app_id, window_title,
                       heading_text, std::u16string() /*subheading_text*/,
                       confirm_button_text, cancel_button_text,
                       std::move(callback));
}

void ShowUsbAccessPermissionDialog(Profile* profile,
                                   const std::string& app_id,
                                   const std::u16string& device_name,
                                   ArcUsbConfirmCallback callback) {
  std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
      GetArcAppInfo(profile, app_id);
  if (!app_info) {
    std::move(callback).Run(false);
    return;
  }

  std::u16string window_title =
      l10n_util::GetStringUTF16(IDS_ARC_USB_PERMISSION_TITLE);

  std::u16string heading_text = l10n_util::GetStringFUTF16(
      IDS_ARC_USB_ACCESS_PERMISSION_HEADING, base::UTF8ToUTF16(app_info->name));

  std::u16string subheading_text = device_name;

  std::u16string confirm_button_text = l10n_util::GetStringUTF16(IDS_OK);

  std::u16string cancel_button_text = l10n_util::GetStringUTF16(IDS_CANCEL);

  new ArcAppDialogView(profile, nullptr /* controller */, app_id, window_title,
                       heading_text, subheading_text, confirm_button_text,
                       cancel_button_text, std::move(callback));
}

bool IsArcAppDialogViewAliveForTest() {
  return g_current_arc_app_dialog_view != nullptr;
}

bool CloseAppDialogViewAndConfirmForTest(bool confirm) {
  if (!g_current_arc_app_dialog_view) {
    return false;
  }

  g_current_arc_app_dialog_view->ConfirmOrCancelForTest(confirm);
  return true;
}

}  // namespace arc