chromium/chrome/browser/ui/views/desktop_capture/desktop_media_permission_pane_view_mac.mm

// Copyright 2024 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/views/desktop_capture/desktop_media_permission_pane_view_mac.h"

#include "base/apple/foundation_util.h"
#include "base/mac/launch_application.h"
#include "base/mac/mac_util.h"
#include "base/metrics/user_metrics.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/component_extension_resources.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#include "ui/lottie/animation.h"
#include "ui/views/background.h"
#include "ui/views/controls/animated_image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"

BASE_FEATURE(kAnimationForDesktopCapturePermissionChecker,
             "AnimationForDesktopCapturePermissionChecker",
             base::FEATURE_ENABLED_BY_DEFAULT);

namespace {

BASE_FEATURE(kDesktopCapturePermissionCheckerRestartMessage,
             "DesktopCapturePermissionCheckerRestartMessage",
             base::FEATURE_ENABLED_BY_DEFAULT);

std::u16string WithRestartMessage(int message_id) {
  return l10n_util::GetStringUTF16(message_id) + u"\n" +
         l10n_util::GetStringUTF16(
             IDS_DESKTOP_MEDIA_PICKER_PERMISSION_RESTART_TEXT_MAC);
}

std::u16string GetLabelText(DesktopMediaList::Type type) {
  switch (type) {
    case DesktopMediaList::Type::kScreen:
      return base::FeatureList::IsEnabled(
                 kDesktopCapturePermissionCheckerRestartMessage)
                 ? WithRestartMessage(
                       IDS_DESKTOP_MEDIA_PICKER_SCREEN_PERMISSION_TEXT_PERIOD_MAC)
                 : l10n_util::GetStringUTF16(
                       IDS_DESKTOP_MEDIA_PICKER_SCREEN_PERMISSION_TEXT_MAC);

    case DesktopMediaList::Type::kWindow:
      return base::FeatureList::IsEnabled(
                 kDesktopCapturePermissionCheckerRestartMessage)
                 ? WithRestartMessage(
                       IDS_DESKTOP_MEDIA_PICKER_WINDOW_PERMISSION_TEXT_PERIOD_MAC)
                 : l10n_util::GetStringUTF16(
                       IDS_DESKTOP_MEDIA_PICKER_WINDOW_PERMISSION_TEXT_MAC);

    case DesktopMediaList::Type::kNone:
    case DesktopMediaList::Type::kWebContents:
    case DesktopMediaList::Type::kCurrentTab:
      break;
  }
  NOTREACHED();
}

std::unique_ptr<views::View> MakeToggleAnimation() {
  if (!base::FeatureList::IsEnabled(
          kAnimationForDesktopCapturePermissionChecker)) {
    return nullptr;
  }

  NSImage* app_icon = [NSImage imageNamed:NSImageNameApplicationIcon];
  if (!app_icon) {
    return nullptr;
  }

  std::optional<std::vector<uint8_t>> toggle_animation =
      ui::ResourceBundle::GetSharedInstance().GetLottieData(
          SCREEN_SHARING_TOGGLE_ANIMATION_JSON);
  if (!toggle_animation.has_value()) {
    return nullptr;
  }

  auto animation_container = std::make_unique<views::View>();
  views::BoxLayout* animation_layout =
      animation_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kHorizontal));
  animation_layout->set_main_axis_alignment(
      views::BoxLayout::MainAxisAlignment::kCenter);
  animation_layout->set_between_child_spacing(50);

  views::ImageView* logo_image_view =
      animation_container->AddChildView(std::make_unique<views::ImageView>());
  logo_image_view->SetImage(gfx::ImageSkiaFromNSImage(app_icon));
  logo_image_view->SetImageSize(gfx::Size(55, 55));
  // Adds a margin on the left side of the logo to balance the right margin that
  // is included in the toggle animation. This visually centers the content of
  // |animation_container|.
  logo_image_view->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::TLBR(0, 30, 0, 0)));

  views::AnimatedImageView* animation = animation_container->AddChildView(
      std::make_unique<views::AnimatedImageView>());
  animation->SetAnimatedImage(std::make_unique<lottie::Animation>(
      cc::SkottieWrapper::UnsafeCreateSerializable(
          std::move(*toggle_animation))));

  animation->SetHorizontalAlignment(views::ImageViewBase::Alignment::kTrailing);
  animation->SetImageSize(gfx::Size(80, 80));
  animation->Play();
  return animation_container;
}

}  // namespace

DesktopMediaPermissionPaneViewMac::DesktopMediaPermissionPaneViewMac(
    DesktopMediaList::Type type,
    base::RepeatingCallback<void()> open_screen_recording_settings_callback)
    : type_(type),
      open_screen_recording_settings_callback_(
          open_screen_recording_settings_callback
              ? open_screen_recording_settings_callback
              : base::BindRepeating(&base::mac::OpenSystemSettingsPane,
                                    base::mac::SystemSettingsPane::
                                        kPrivacySecurity_ScreenRecording,
                                    /*id_param=*/"")) {
  SetBackground(
      views::CreateThemedRoundedRectBackground(ui::kColorSysSurface4,
                                               /*top_radius=*/0.0f,
                                               /*bottom_radius=*/8.0f));
  const ChromeLayoutProvider* const provider = ChromeLayoutProvider::Get();
  views::BoxLayout* layout =
      SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kVertical, gfx::Insets(0),
          provider->GetDistanceMetric(
              DISTANCE_UNRELATED_CONTROL_VERTICAL_LARGE)));
  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);

  if (std::unique_ptr<views::View> toggle_view = MakeToggleAnimation()) {
    AddChildView(std::move(toggle_view));
  }
  views::Label* label =
      AddChildView(std::make_unique<views::Label>(GetLabelText(type_)));
  label->SetMultiLine(true);

  View* button_container = AddChildView(std::make_unique<views::View>());
  views::BoxLayout* button_layout =
      button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kHorizontal));
  button_layout->set_main_axis_alignment(
      views::BoxLayout::MainAxisAlignment::kCenter);
  // Unretained safe because button is (transitively) owned by `this`.
  button_ =
      button_container->AddChildView(std::make_unique<views::MdTextButton>(
          base::BindRepeating(
              &DesktopMediaPermissionPaneViewMac::
                  OpenScreenRecordingSettingsPane,
              base::Unretained(this)),
          l10n_util::GetStringUTF16(
              IDS_DESKTOP_MEDIA_PICKER_PERMISSION_BUTTON_MAC)));
  button_->SetStyle(ui::ButtonStyle::kProminent);
}

DesktopMediaPermissionPaneViewMac::~DesktopMediaPermissionPaneViewMac() =
    default;

bool DesktopMediaPermissionPaneViewMac::WasPermissionButtonClicked() const {
  return clicked_;
}

void DesktopMediaPermissionPaneViewMac::SimulateClickForTesting() {
  button_->AcceleratorPressed(ui::Accelerator());
}

void DesktopMediaPermissionPaneViewMac::OpenScreenRecordingSettingsPane() {
  clicked_ = true;
  switch (type_) {
    case DesktopMediaList::Type::kScreen:
    case DesktopMediaList::Type::kWindow:
      RecordAction(base::UserMetricsAction(
          type_ == DesktopMediaList::Type::kScreen
              ? "GetDisplayMedia.PermissionPane.Screen.ClickedButton"
              : "GetDisplayMedia.PermissionPane.Window.ClickedButton"));
      base::ThreadPool::PostTask(FROM_HERE,
                                 open_screen_recording_settings_callback_);
      return;

    case DesktopMediaList::Type::kNone:
    case DesktopMediaList::Type::kWebContents:
    case DesktopMediaList::Type::kCurrentTab:
      break;
  }
  NOTREACHED();
}

BEGIN_METADATA(DesktopMediaPermissionPaneViewMac)
END_METADATA