diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 79b54c1baf295..f98ee2961f530 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -8829,6 +8829,9 @@ To shut down the device, press and hold the power button on the device again. <message name="IDS_ASH_BIRCH_CORAL_ADDON_SELECTOR_HIDDEN" translateable="false" desc="The accessible name for the birch coral button selector UI."> Show </message> + <message name="IDS_ASH_BIRCH_CORAL_SAVED_GROUPS_MAX_NUM_REACHED" translateable="false" desc="Message shown to users when they attempt to save a new coral group when the maximum number of coral saved groups has been reached."> + Maximum number of saved groups reached. + </message> <!-- Graduation app strings --> <message name="IDS_ASH_GRADUATION_NUDGE_TEXT" desc="Text shown in nudge that is displayed after the Content Transfer app becomes available."> diff --git a/ash/constants/notifier_catalogs.h b/ash/constants/notifier_catalogs.h index 0a0f6011b203f..2ffdccf0e3a6a 100644 --- a/ash/constants/notifier_catalogs.h +++ b/ash/constants/notifier_catalogs.h @@ -325,7 +325,8 @@ enum class ToastCatalogName { kOnTaskUrlBlocked = 55, kCopyImageToClipboardAction = 56, kCaptureModeTextCopied = 57, - kMaxValue = kCaptureModeTextCopied + kCoralSavedGroupLimitMax = 58, + kMaxValue = kCoralSavedGroupLimitMax }; } // namespace ash diff --git a/ash/wm/coral/coral_controller_unittest.cc b/ash/wm/coral/coral_controller_unittest.cc index 46fe781c96ee2..a639e0931612f 100644 --- a/ash/wm/coral/coral_controller_unittest.cc +++ b/ash/wm/coral/coral_controller_unittest.cc @@ -13,23 +13,36 @@ #include "ash/shell.h" #include "ash/system/toast/toast_manager_impl.h" #include "ash/test/ash_test_base.h" +#include "ash/test/ash_test_helper.h" #include "ash/wm/coral/coral_test_util.h" #include "ash/wm/desks/desk.h" #include "ash/wm/desks/desks_controller.h" #include "ash/wm/desks/desks_test_util.h" +#include "ash/wm/desks/templates/saved_desk_test_helper.h" +#include "ash/wm/desks/templates/saved_desk_test_util.h" +#include "ash/wm/overview/birch/birch_bar_controller.h" +#include "ash/wm/overview/birch/birch_bar_menu_model_adapter.h" #include "ash/wm/overview/birch/birch_chip_button.h" +#include "ash/wm/overview/birch/birch_chip_context_menu_model.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_utils.h" #include "ash/wm/snap_group/snap_group_controller.h" #include "ash/wm/snap_group/snap_group_test_util.h" #include "base/test/scoped_feature_list.h" #include "ui/aura/client/aura_constants.h" +#include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/controls/menu/submenu_view.h" #include "ui/views/view_utils.h" namespace ash { class CoralControllerTest : public AshTestBase { public: + CoralControllerTest() { + feature_list_.InitWithFeatures( + {features::kCoralFeature, features::kCoralSavedDeskFeature}, {}); + } + void ClickFirstCoralButton() { DeskSwitchAnimationWaiter waiter; BirchChipButton* coral_button = GetFirstCoralButton(); @@ -38,6 +51,12 @@ class CoralControllerTest : public AshTestBase { waiter.Wait(); } + void AddCoralEntry(const std::string& name) { + AddSavedDeskEntry(ash_test_helper()->saved_desk_test_helper()->desk_model(), + base::Uuid::GenerateRandomV4(), name, base::Time::Now(), + DeskTemplateType::kCoral); + } + void SetUp() override { AshTestBase::SetUp(); @@ -66,7 +85,7 @@ class CoralControllerTest : public AshTestBase { private: std::unique_ptr<TestBirchClient> birch_client_; - base::test::ScopedFeatureList feature_list_{features::kCoralFeature}; + base::test::ScopedFeatureList feature_list_; }; // Tests that clicking the in session coral button opens and activates a new @@ -199,4 +218,34 @@ TEST_F(CoralControllerTest, SnapGroupTwoWindowsInCoralGroup) { window2.get())); } +TEST_F(CoralControllerTest, MaxCoralSavedGroupLimit) { + ash_test_helper()->saved_desk_test_helper()->WaitForDeskModels(); + + // Add enough entries to hit the max. + for (int i = 0; i < 6; ++i) { + AddCoralEntry(base::NumberToString(i)); + } + + Shell::Get()->overview_controller()->StartOverview( + OverviewStartAction::kTests); + + // Right click on the coral button to bring up the context menu and the save + // as group option. + RightClickOn(GetFirstCoralButton()); + BirchBarMenuModelAdapter* model_adapter = + BirchBarController::Get()->chip_menu_model_adapter_for_testing(); + EXPECT_TRUE(model_adapter->IsShowingMenu()); + views::MenuItemView* save_as_group_item = + model_adapter->root_for_testing()->GetSubmenu()->GetMenuItemAt(1); + ASSERT_EQ(save_as_group_item->GetCommand(), + base::to_underlying( + BirchChipContextMenuModel::CommandId::kCoralSaveForLater)); + + // Left click on the menu item and test that the toast shows up since we + // cannot create more coral saved groups. + LeftClickOn(save_as_group_item); + EXPECT_TRUE(Shell::Get()->toast_manager()->IsToastShown( + "coral_max_saved_groups_toast")); +} + } // namespace ash diff --git a/ash/wm/desks/templates/saved_desk_presenter.cc b/ash/wm/desks/templates/saved_desk_presenter.cc index b9c8099dadbb4..a9c4fec860c2a 100644 --- a/ash/wm/desks/templates/saved_desk_presenter.cc +++ b/ash/wm/desks/templates/saved_desk_presenter.cc @@ -330,16 +330,30 @@ SavedDeskPresenter::~SavedDeskPresenter() = default; size_t SavedDeskPresenter::GetEntryCount(DeskTemplateType type) const { auto* model = GetDeskModel(); - return type == DeskTemplateType::kTemplate - ? model->GetDeskTemplateEntryCount() - : model->GetSaveAndRecallDeskEntryCount(); + switch (type) { + case DeskTemplateType::kTemplate: + return model->GetDeskTemplateEntryCount(); + case DeskTemplateType::kSaveAndRecall: + return model->GetSaveAndRecallDeskEntryCount(); + case DeskTemplateType::kCoral: + return model->GetCoralEntryCount(); + default: + NOTREACHED(); + } } size_t SavedDeskPresenter::GetMaxEntryCount(DeskTemplateType type) const { auto* model = GetDeskModel(); - return type == DeskTemplateType::kTemplate - ? model->GetMaxDeskTemplateEntryCount() - : model->GetMaxSaveAndRecallDeskEntryCount(); + switch (type) { + case DeskTemplateType::kTemplate: + return model->GetMaxDeskTemplateEntryCount(); + case DeskTemplateType::kSaveAndRecall: + return model->GetMaxSaveAndRecallDeskEntryCount(); + case DeskTemplateType::kCoral: + return model->GetMaxCoralEntryCount(); + default: + NOTREACHED(); + } } ash::DeskTemplate* SavedDeskPresenter::FindOtherEntryWithName( diff --git a/ash/wm/overview/birch/birch_chip_button.cc b/ash/wm/overview/birch/birch_chip_button.cc index 9045a0f33a1e9..ad5b2530a8444 100644 --- a/ash/wm/overview/birch/birch_chip_button.cc +++ b/ash/wm/overview/birch/birch_chip_button.cc @@ -6,12 +6,15 @@ #include "ash/birch/birch_coral_provider.h" #include "ash/birch/birch_item.h" +#include "ash/constants/notifier_catalogs.h" #include "ash/public/cpp/coral_delegate.h" #include "ash/resources/vector_icons/vector_icons.h" #include "ash/shell.h" #include "ash/strings/grit/ash_strings.h" #include "ash/style/icon_button.h" #include "ash/style/typography.h" +#include "ash/system/toast/toast_manager_impl.h" +#include "ash/wm/desks/templates/saved_desk_presenter.h" #include "ash/wm/overview/birch/birch_animation_utils.h" #include "ash/wm/overview/birch/birch_bar_constants.h" #include "ash/wm/overview/birch/birch_bar_controller.h" @@ -19,6 +22,8 @@ #include "ash/wm/overview/birch/birch_chip_context_menu_model.h" #include "ash/wm/overview/birch/resources/grit/coral_resources.h" #include "ash/wm/overview/birch/tab_app_selection_host.h" +#include "ash/wm/overview/overview_controller.h" +#include "ash/wm/overview/overview_session.h" #include "base/notreached.h" #include "base/strings/utf_string_conversions.h" #include "base/types/cxx23_to_underlying.h" @@ -89,6 +94,8 @@ constexpr ui::ColorId kSubtitleColorId = cros_tokens::kCrosSysOnSurfaceVariant; constexpr gfx::Size kLoadingAnimationSize = gfx::Size(100, 20); +constexpr char kMaxSavedGroupsToastId[] = "coral_max_saved_groups_toast"; + BirchSuggestionType GetSuggestionTypeFromItemType(BirchItemType item_type) { switch (item_type) { case BirchItemType::kWeather: @@ -452,6 +459,23 @@ void BirchChipButton::ExecuteCommand(int command_id, int event_flags) { break; case base::to_underlying(CommandId::kCoralSaveForLater): { CHECK_EQ(BirchItemType::kCoral, item_->GetType()); + + // Show a toast if we already have the max amount of allowed coral saved + // groups. + auto* saved_desk_presenter = + OverviewController::Get()->overview_session()->saved_desk_presenter(); + if (saved_desk_presenter->GetEntryCount(DeskTemplateType::kCoral) >= + saved_desk_presenter->GetMaxEntryCount(DeskTemplateType::kCoral)) { + ToastData toast(kMaxSavedGroupsToastId, + ToastCatalogName::kCoralSavedGroupLimitMax, + l10n_util::GetStringUTF16( + IDS_ASH_BIRCH_CORAL_SAVED_GROUPS_MAX_NUM_REACHED), + ToastData::kDefaultToastDuration, + /*visible_on_lock_screen=*/false); + Shell::Get()->toast_manager()->Show(std::move(toast)); + return; + } + auto* coral_provider = BirchCoralProvider::Get(); Shell::Get()->coral_delegate()->CreateSavedDeskFromGroup( coral_provider->ExtractGroupById( diff --git a/ash/wm/overview/birch/birch_chip_context_menu_model.cc b/ash/wm/overview/birch/birch_chip_context_menu_model.cc index a4eaa1e89aefe..56611b4b2c416 100644 --- a/ash/wm/overview/birch/birch_chip_context_menu_model.cc +++ b/ash/wm/overview/birch/birch_chip_context_menu_model.cc @@ -81,7 +81,8 @@ BirchChipContextMenuModel::BirchChipContextMenuModel( // TODO(zxdan): Localize the strings. AddItemWithIcon(base::to_underlying(CommandId::kCoralNewDesk), u"Open", CreateIconForMenuItem(kCoralOpenIcon)); - if (features::IsCoralSavedDeskFeatureEnabled()) { + if (features::IsCoralSavedDeskFeatureEnabled() && + !display::Screen::GetScreen()->InTabletMode()) { AddItemWithIcon(base::to_underlying(CommandId::kCoralSaveForLater), u"Save group for later", CreateIconForMenuItem(kSaveDeskForLaterIcon)); diff --git a/components/desks_storage/core/desk_model.h b/components/desks_storage/core/desk_model.h index 66666b4526347..62c2fb87bd094 100644 --- a/components/desks_storage/core/desk_model.h +++ b/components/desks_storage/core/desk_model.h @@ -166,6 +166,9 @@ class DeskModel { // Gets the number of desk templates currently saved. virtual size_t GetDeskTemplateEntryCount() const = 0; + // Gets the number of coral saved groups currently saved. + virtual size_t GetCoralEntryCount() const = 0; + // Gets the maximum number of save and recall desks entry this storage backend // could hold. virtual size_t GetMaxSaveAndRecallDeskEntryCount() const = 0; @@ -174,6 +177,10 @@ class DeskModel { // could hold. virtual size_t GetMaxDeskTemplateEntryCount() const = 0; + // Gets the maximum number of coral saved groups this storage backend could + // hold. + virtual size_t GetMaxCoralEntryCount() const = 0; + // Returns a vector of desk template UUIDs. // This method assumes each implementation has a cache and can return the // UUIDs synchronously. diff --git a/components/desks_storage/core/desk_model_wrapper.cc b/components/desks_storage/core/desk_model_wrapper.cc index fe528705e22c2..d2776e643596a 100644 --- a/components/desks_storage/core/desk_model_wrapper.cc +++ b/components/desks_storage/core/desk_model_wrapper.cc @@ -127,6 +127,10 @@ size_t DeskModelWrapper::GetDeskTemplateEntryCount() const { policy_entries_.size(); } +size_t DeskModelWrapper::GetCoralEntryCount() const { + return 0u; +} + size_t DeskModelWrapper::GetMaxSaveAndRecallDeskEntryCount() const { return save_and_recall_desks_model_->GetMaxSaveAndRecallDeskEntryCount(); } @@ -136,6 +140,10 @@ size_t DeskModelWrapper::GetMaxDeskTemplateEntryCount() const { policy_entries_.size(); } +size_t DeskModelWrapper::GetMaxCoralEntryCount() const { + return 0u; +} + std::set<base::Uuid> DeskModelWrapper::GetAllEntryUuids() const { std::set<base::Uuid> keys; diff --git a/components/desks_storage/core/desk_model_wrapper.h b/components/desks_storage/core/desk_model_wrapper.h index 93a34edf27d09..6087173f4b30b 100644 --- a/components/desks_storage/core/desk_model_wrapper.h +++ b/components/desks_storage/core/desk_model_wrapper.h @@ -47,8 +47,10 @@ class DeskModelWrapper : public DeskModel { size_t GetEntryCount() const override; size_t GetSaveAndRecallDeskEntryCount() const override; size_t GetDeskTemplateEntryCount() const override; + size_t GetCoralEntryCount() const override; size_t GetMaxSaveAndRecallDeskEntryCount() const override; size_t GetMaxDeskTemplateEntryCount() const override; + size_t GetMaxCoralEntryCount() const override; std::set<base::Uuid> GetAllEntryUuids() const override; bool IsReady() const override; bool IsSyncing() const override; diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc index 472df6764c85d..19811282dce49 100644 --- a/components/desks_storage/core/desk_sync_bridge.cc +++ b/components/desks_storage/core/desk_sync_bridge.cc @@ -475,6 +475,10 @@ size_t DeskSyncBridge::GetDeskTemplateEntryCount() const { return template_count + policy_entries_.size(); } +size_t DeskSyncBridge::GetCoralEntryCount() const { + return 0u; +} + // Chrome sync does not support save and recall desks yet. Return 0 for max // count. size_t DeskSyncBridge::GetMaxSaveAndRecallDeskEntryCount() const { @@ -485,6 +489,10 @@ size_t DeskSyncBridge::GetMaxDeskTemplateEntryCount() const { return kMaxTemplateCount + policy_entries_.size(); } +size_t DeskSyncBridge::GetMaxCoralEntryCount() const { + return 0u; +} + std::set<base::Uuid> DeskSyncBridge::GetAllEntryUuids() const { std::set<base::Uuid> keys; diff --git a/components/desks_storage/core/desk_sync_bridge.h b/components/desks_storage/core/desk_sync_bridge.h index 070e1e97b6f95..6b54444a6f890 100644 --- a/components/desks_storage/core/desk_sync_bridge.h +++ b/components/desks_storage/core/desk_sync_bridge.h @@ -74,8 +74,10 @@ class DeskSyncBridge : public syncer::DataTypeSyncBridge, public DeskModel { size_t GetEntryCount() const override; size_t GetSaveAndRecallDeskEntryCount() const override; size_t GetDeskTemplateEntryCount() const override; + size_t GetCoralEntryCount() const override; size_t GetMaxSaveAndRecallDeskEntryCount() const override; size_t GetMaxDeskTemplateEntryCount() const override; + size_t GetMaxCoralEntryCount() const override; std::set<base::Uuid> GetAllEntryUuids() const override; bool IsReady() const override; // Whether this sync bridge is syncing local data to sync. This sync bridge diff --git a/components/desks_storage/core/fake_desk_sync_bridge.cc b/components/desks_storage/core/fake_desk_sync_bridge.cc index 15e2046911522..33034e1d269a8 100644 --- a/components/desks_storage/core/fake_desk_sync_bridge.cc +++ b/components/desks_storage/core/fake_desk_sync_bridge.cc @@ -160,6 +160,10 @@ size_t FakeDeskSyncBridge::GetDeskTemplateEntryCount() const { return template_count + policy_entries_.size(); } +size_t FakeDeskSyncBridge::GetCoralEntryCount() const { + return 0u; +} + // Chrome sync does not support save and recall desks yet. Return 0 for max // count. size_t FakeDeskSyncBridge::GetMaxSaveAndRecallDeskEntryCount() const { @@ -170,6 +174,10 @@ size_t FakeDeskSyncBridge::GetMaxDeskTemplateEntryCount() const { return 6u + policy_entries_.size(); } +size_t FakeDeskSyncBridge::GetMaxCoralEntryCount() const { + return 0u; +} + std::set<base::Uuid> FakeDeskSyncBridge::GetAllEntryUuids() const { std::set<base::Uuid> keys; diff --git a/components/desks_storage/core/fake_desk_sync_bridge.h b/components/desks_storage/core/fake_desk_sync_bridge.h index f15ec039af772..ea3ff8ce10888 100644 --- a/components/desks_storage/core/fake_desk_sync_bridge.h +++ b/components/desks_storage/core/fake_desk_sync_bridge.h @@ -35,8 +35,10 @@ class FakeDeskSyncBridge : public DeskModel { size_t GetEntryCount() const override; size_t GetSaveAndRecallDeskEntryCount() const override; size_t GetDeskTemplateEntryCount() const override; + size_t GetCoralEntryCount() const override; size_t GetMaxSaveAndRecallDeskEntryCount() const override; size_t GetMaxDeskTemplateEntryCount() const override; + size_t GetMaxCoralEntryCount() const override; std::set<base::Uuid> GetAllEntryUuids() const override; bool IsReady() const override; // Whether this sync bridge is syncing local data to sync. This sync bridge diff --git a/components/desks_storage/core/local_desk_data_manager.cc b/components/desks_storage/core/local_desk_data_manager.cc index 848934a484f0e..36758e19b5aeb 100644 --- a/components/desks_storage/core/local_desk_data_manager.cc +++ b/components/desks_storage/core/local_desk_data_manager.cc @@ -56,7 +56,8 @@ constexpr size_t kMaxCoralDeskCount = 6u; // Set of valid desk types. constexpr auto kValidDeskTypes = base::MakeFixedFlatSet<ash::DeskTemplateType>( - {ash::DeskTemplateType::kTemplate, ash::DeskTemplateType::kSaveAndRecall}); + {ash::DeskTemplateType::kTemplate, ash::DeskTemplateType::kSaveAndRecall, + ash::DeskTemplateType::kCoral}); // Reads a file at `fully_qualified_path` into a // `ash::DeskTemplate` or as `SavedDeskParseError` code. This function returns a @@ -398,7 +399,8 @@ void LocalDeskDataManager::DeleteAllEntries(DeleteEntryCallback callback) { // TODO(crbug.com/1320805): Remove this function once both desk models support // desk type counts. size_t LocalDeskDataManager::GetEntryCount() const { - return GetSaveAndRecallDeskEntryCount() + GetDeskTemplateEntryCount(); + return GetSaveAndRecallDeskEntryCount() + GetDeskTemplateEntryCount() + + GetCoralEntryCount(); } size_t LocalDeskDataManager::GetSaveAndRecallDeskEntryCount() const { @@ -410,6 +412,10 @@ size_t LocalDeskDataManager::GetDeskTemplateEntryCount() const { policy_entries_.size(); } +size_t LocalDeskDataManager::GetCoralEntryCount() const { + return saved_desks_list_.at(ash::DeskTemplateType::kCoral).size(); +} + size_t LocalDeskDataManager::GetMaxSaveAndRecallDeskEntryCount() const { return kMaxSaveAndRecallDeskCount; } @@ -418,6 +424,10 @@ size_t LocalDeskDataManager::GetMaxDeskTemplateEntryCount() const { return kMaxDeskTemplateCount + policy_entries_.size(); } +size_t LocalDeskDataManager::GetMaxCoralEntryCount() const { + return kMaxCoralDeskCount; +} + std::set<base::Uuid> LocalDeskDataManager::GetAllEntryUuids() const { std::set<base::Uuid> keys; for (const auto& type_and_saved_desks : saved_desks_list_) { diff --git a/components/desks_storage/core/local_desk_data_manager.h b/components/desks_storage/core/local_desk_data_manager.h index cb97220c752fa..e62f6456b2ec7 100644 --- a/components/desks_storage/core/local_desk_data_manager.h +++ b/components/desks_storage/core/local_desk_data_manager.h @@ -105,8 +105,10 @@ class LocalDeskDataManager : public DeskModel, public AdminTemplateModel { size_t GetEntryCount() const override; size_t GetSaveAndRecallDeskEntryCount() const override; size_t GetDeskTemplateEntryCount() const override; + size_t GetCoralEntryCount() const override; size_t GetMaxSaveAndRecallDeskEntryCount() const override; size_t GetMaxDeskTemplateEntryCount() const override; + size_t GetMaxCoralEntryCount() const override; std::set<base::Uuid> GetAllEntryUuids() const override; bool IsReady() const override; bool IsSyncing() const override; diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml index 1f91b040da51c..898270936496f 100644 --- a/tools/metrics/histograms/metadata/ash/enums.xml +++ b/tools/metrics/histograms/metadata/ash/enums.xml @@ -2365,6 +2365,7 @@ chromeos/ash/components/peripheral_notification/peripheral_notification_manager. <int value="55" label="OnTask URL blocked"/> <int value="56" label="Copy Image To Clipboard Action"/> <int value="57" label="Capture Mode Text Copied"/> + <int value="58" label="Coral Saved Groups Limit Max"/> </enum> <enum name="TogglePickerAction">