chromium/ash/shelf/kiosk_apps_button.cc

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

#include "ash/shelf/kiosk_apps_button.h"

#include <memory>
#include <string>
#include <vector>

#include "ash/public/cpp/kiosk_app_menu.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/login_shelf_button.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/functional/callback.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/controls/menu/menu_runner.h"

namespace ash {

class KioskAppsButton::KioskAppsMenuModel
    : public ui::SimpleMenuModel,
      public ui::SimpleMenuModel::Delegate {
 public:
  KioskAppsMenuModel() : ui::SimpleMenuModel(this) {}

  KioskAppsMenuModel(const KioskAppsMenuModel&) = delete;
  KioskAppsMenuModel& operator=(const KioskAppsMenuModel&) = delete;

  ~KioskAppsMenuModel() override = default;

  bool IsLaunchEnabled() const { return is_launch_enabled_; }
  void SetLaunchEnabled(bool enabled) { is_launch_enabled_ = enabled; }

  bool LaunchApp(const std::string& chrome_app_id) {
    for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
      if (kiosk_apps_[i].chrome_app_id == chrome_app_id) {
        ExecuteCommand(/*command_id=*/i, /*event_flags=*/0);
        return true;
      }
    }
    return false;
  }

  bool LaunchApp(const AccountId& account_id) {
    for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
      if (kiosk_apps_[i].account_id == account_id) {
        ExecuteCommand(/*command_id=*/i, /*event_flags=*/0);
        return true;
      }
    }
    return false;
  }

  void SetApps(const std::vector<KioskAppMenuEntry>& kiosk_apps) {
    kiosk_apps_ = kiosk_apps;
    Clear();
    const gfx::Size kAppIconSize(16, 16);
    for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
      gfx::ImageSkia icon = gfx::ImageSkiaOperations::CreateResizedImage(
          kiosk_apps_[i].icon, skia::ImageOperations::RESIZE_GOOD,
          kAppIconSize);
      AddItemWithIcon(i, kiosk_apps_[i].name,
                      ui::ImageModel::FromImageSkia(icon));
    }
  }

  void OnMenuWillShow(SimpleMenuModel* source) override {
    is_menu_opened_ = true;
    on_show_menu_.Run();
  }

  void MenuClosed(SimpleMenuModel* source) override {
    on_close_menu_.Run();
    is_menu_opened_ = false;
  }

  bool IsMenuOpened() const { return is_menu_opened_; }

  void ExecuteCommand(int command_id, int event_flags) override {
    DCHECK(command_id >= 0 &&
           base::checked_cast<size_t>(command_id) < kiosk_apps_.size());
    // Once an app is clicked on, don't allow any additional clicks until
    // the state is reset (when login screen reappears).
    is_launch_enabled_ = false;
    launch_app_callback_.Run(kiosk_apps_[command_id]);
  }

  void ConfigureKioskCallbacks(
      base::RepeatingCallback<void(const KioskAppMenuEntry&)> launch_app,
      base::RepeatingClosure on_show_menu,
      base::RepeatingClosure on_close_menu) {
    launch_app_callback_ = std::move(launch_app);
    on_show_menu_ = std::move(on_show_menu);
    on_close_menu_ = std::move(on_close_menu);
  }

 private:
  base::RepeatingCallback<void(const KioskAppMenuEntry&)> launch_app_callback_;
  base::RepeatingClosure on_show_menu_;
  base::RepeatingClosure on_close_menu_;
  std::vector<KioskAppMenuEntry> kiosk_apps_;

  bool is_launch_enabled_ = true;
  bool is_menu_opened_ = false;
};

KioskAppsButton::KioskAppsButton()
    : LoginShelfButton(PressedCallback(),
                       IDS_ASH_SHELF_APPS_BUTTON,
                       kShelfAppsButtonIcon) {
  menu_model_ = std::make_unique<KioskAppsMenuModel>();
  std::unique_ptr<views::MenuButtonController> menu_button_controller =
      std::make_unique<views::MenuButtonController>(
          this,
          base::BindRepeating(
              [](KioskAppsButton* button) {
                if (button->menu_model_->IsLaunchEnabled()) {
                  button->DisplayMenu();
                }
              },
              this),
          std::make_unique<Button::DefaultButtonControllerDelegate>(this));
  menu_button_controller_ = menu_button_controller.get();
  SetButtonController(std::move(menu_button_controller));

  set_suppress_default_focus_handling();
}

KioskAppsButton::~KioskAppsButton() = default;

bool KioskAppsButton::LaunchAppForTesting(const std::string& chrome_app_id) {
  return menu_model_->LaunchApp(chrome_app_id);
}

bool KioskAppsButton::LaunchAppForTesting(const AccountId& account_id) {
  return menu_model_->LaunchApp(account_id);
}

void KioskAppsButton::SetApps(
    const std::vector<KioskAppMenuEntry>& kiosk_apps) {
  menu_model_->SetApps(kiosk_apps);
  // If the menu is being shown, update it.
  if (menu_runner_ && menu_runner_->IsRunning()) {
    DisplayMenu();
  }
}

void KioskAppsButton::ConfigureKioskCallbacks(
    base::RepeatingCallback<void(const KioskAppMenuEntry&)> launch_app,
    base::RepeatingClosure on_show_menu,
    base::RepeatingClosure on_close_menu) {
  menu_model_->ConfigureKioskCallbacks(
      std::move(launch_app), std::move(on_show_menu), std::move(on_close_menu));
}

bool KioskAppsButton::HasApps() const {
  return menu_model_->GetItemCount() > 0;
}

void KioskAppsButton::SetVisible(bool visible) {
  LoginShelfButton::SetVisible(visible);
  if (visible) {
    menu_model_->SetLaunchEnabled(true);
  }
}

void KioskAppsButton::DisplayMenu() {
  const gfx::Point point = GetMenuPosition();
  const gfx::Point origin(point.x() - width(), point.y() - height());
  menu_runner_ = std::make_unique<views::MenuRunner>(
      menu_model_.get(), views::MenuRunner::HAS_MNEMONICS);
  menu_runner_->RunMenuAt(
      GetWidget()->GetTopLevelWidget(), menu_button_controller_,
      gfx::Rect(origin, gfx::Size()),
      views::MenuAnchorPosition::kBubbleBottomLeft, ui::MENU_SOURCE_NONE);
}

bool KioskAppsButton::IsMenuOpened() const {
  return menu_model_->IsMenuOpened();
}

void KioskAppsButton::SetCallback(PressedCallback callback) {
  menu_button_controller_->SetCallback(std::move(callback));
}

void KioskAppsButton::NotifyClick(const ui::Event& event) {
  // Run pressed callback via MenuButtonController, instead of directly.
  menu_button_controller_->Activate(&event);
}

BEGIN_METADATA(KioskAppsButton)
END_METADATA

}  // namespace ash