0

Update the focus behavior and the a11y name for undo toast.

The following things are done in this cl:
(1) Updated the a11y name for undo button on reorder toast.
(2) Moved the focus to the undo button after sorting.
(3) After the sort is reverted, move the focus to the search box.

Bug: 1300299
Change-Id: I1bbc8053469e6ed3dc51ffe0c49b02a390b1ea30
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3561096
Reviewed-by: Matthew Mourgos <mmourgos@chromium.org>
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Wen-Chien Wang <wcwang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#989516}
This commit is contained in:
Wen-Chien Wang
2022-04-06 18:06:25 +00:00
committed by Chromium LUCI CQ
parent fea4c53dfd
commit 96d09e5367
13 changed files with 187 additions and 28 deletions

@ -15,10 +15,12 @@
#include "ash/app_list/model/app_list_model.h"
#include "ash/app_list/views/app_list_nudge_controller.h"
#include "ash/app_list/views/app_list_toast_container_view.h"
#include "ash/app_list/views/app_list_toast_view.h"
#include "ash/app_list/views/app_list_view_util.h"
#include "ash/app_list/views/continue_section_view.h"
#include "ash/app_list/views/recent_apps_view.h"
#include "ash/app_list/views/scrollable_apps_grid_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/app_list/views/search_result_page_dialog_controller.h"
#include "ash/bubble/bubble_utils.h"
#include "ash/constants/ash_features.h"
@ -26,14 +28,12 @@
#include "ash/controls/scroll_view_gradient_helper.h"
#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/bind.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h"
@ -44,6 +44,7 @@
#include "ui/gfx/text_constants.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
@ -106,8 +107,11 @@ AppListBubbleAppsPage::AppListBubbleAppsPage(
AppListConfig* app_list_config,
AppListA11yAnnouncer* a11y_announcer,
SearchResultPageDialogController* dialog_controller,
AppListFolderController* folder_controller)
: app_list_nudge_controller_(std::make_unique<AppListNudgeController>()),
AppListFolderController* folder_controller,
SearchBoxView* search_box)
: view_delegate_(view_delegate),
search_box_(search_box),
app_list_nudge_controller_(std::make_unique<AppListNudgeController>()),
dialog_controller_(dialog_controller) {
DCHECK(view_delegate);
DCHECK(drag_and_drop_host);
@ -412,15 +416,21 @@ void AppListBubbleAppsPage::UpdateForNewSortingOrder(
DCHECK_EQ(animate, !update_position_closure.is_null());
DCHECK(!animation_done_closure || animate);
// A11y announcements must happen before animations, otherwise "Search your
// apps..." is spoken first because focus moves immediately to the search box.
if (new_order)
// A11y announcements must happen before animations, otherwise the undo
// guidance is spoken first because focus moves immediately to the undo button
// on the toast. Note that when `new_order` is null, `animate` was set to true
// only if the sort was reverted.
if (new_order) {
toast_container_->AnnounceSortOrder(*new_order);
} else if (animate) {
toast_container_->AnnounceUndoSort();
}
if (!animate) {
// Reordering is not required so update the undo toast and return early.
app_list_nudge_controller_->OnTemporarySortOrderChanged(new_order);
toast_container_->OnTemporarySortOrderChanged(new_order);
HandleFocusAfterSort();
return;
}
@ -435,7 +445,7 @@ void AppListBubbleAppsPage::UpdateForNewSortingOrder(
views::AnimationBuilder animation_builder =
scrollable_apps_grid_view_->FadeOutVisibleItemsForReorder(
base::BindRepeating(
&AppListBubbleAppsPage::OnAppsGridViewFadeOutAnimationEneded,
&AppListBubbleAppsPage::OnAppsGridViewFadeOutAnimationEnded,
weak_factory_.GetWeakPtr(), new_order));
// Configure the toast fade out animation if the toast is going to be hidden.
@ -609,7 +619,23 @@ void AppListBubbleAppsPage::OnAppsGridViewAnimationEnded() {
gradient_helper_->UpdateGradientZone();
}
void AppListBubbleAppsPage::OnAppsGridViewFadeOutAnimationEneded(
void AppListBubbleAppsPage::HandleFocusAfterSort() {
// As the sort update on AppListBubbleAppsPage can be called in both clamshell
// mode and tablet mode, return early if it's currently in tablet mode because
// the AppListBubbleAppsPage isn't visible.
if (view_delegate_->IsInTabletMode())
return;
// If the sort is done and the toast is visible, request the focus on the
// undo button on the toast. Otherwise request the focus on the search box.
if (toast_container_->is_toast_visible()) {
toast_container_->toast_view()->toast_button()->RequestFocus();
} else {
search_box_->search_box()->RequestFocus();
}
}
void AppListBubbleAppsPage::OnAppsGridViewFadeOutAnimationEnded(
const absl::optional<AppListSortOrder>& new_order,
bool aborted) {
// Update item positions after the fade out animation but before the fade in
@ -632,6 +658,7 @@ void AppListBubbleAppsPage::OnAppsGridViewFadeOutAnimationEneded(
const bool old_toast_visible = toast_container_->is_toast_visible();
toast_container_->OnTemporarySortOrderChanged(new_order);
HandleFocusAfterSort();
const bool target_toast_visible = toast_container_->is_toast_visible();
// If there is a layer created for fading out `toast_container_`, destroy

@ -42,6 +42,7 @@ class AppListViewDelegate;
class ContinueSectionView;
class RecentAppsView;
class SearchResultPageDialogController;
class SearchBoxView;
class ScrollableAppsGridView;
class ScrollViewGradientHelper;
@ -65,7 +66,8 @@ class ASH_EXPORT AppListBubbleAppsPage
AppListConfig* app_list_config,
AppListA11yAnnouncer* a11y_announcer,
SearchResultPageDialogController* dialog_controller,
AppListFolderController* folder_controller);
AppListFolderController* folder_controller,
SearchBoxView* search_box);
AppListBubbleAppsPage(const AppListBubbleAppsPage&) = delete;
AppListBubbleAppsPage& operator=(const AppListBubbleAppsPage&) = delete;
~AppListBubbleAppsPage() override;
@ -169,9 +171,12 @@ class ASH_EXPORT AppListBubbleAppsPage
// Callback for when the apps grid view animation ends.
void OnAppsGridViewAnimationEnded();
// Called after sort to handle focus.
void HandleFocusAfterSort();
// Called when the animation to fade out app list items is completed.
// `aborted` indicates whether the fade out animation is aborted.
void OnAppsGridViewFadeOutAnimationEneded(
void OnAppsGridViewFadeOutAnimationEnded(
const absl::optional<AppListSortOrder>& new_order,
bool aborted);
@ -192,6 +197,7 @@ class ASH_EXPORT AppListBubbleAppsPage
int vertical_offset,
base::TimeDelta duration);
AppListViewDelegate* view_delegate_ = nullptr;
views::ScrollView* scroll_view_ = nullptr;
ContinueSectionView* continue_section_ = nullptr;
RecentAppsView* recent_apps_ = nullptr;
@ -199,6 +205,9 @@ class ASH_EXPORT AppListBubbleAppsPage
AppListToastContainerView* toast_container_ = nullptr;
ScrollableAppsGridView* scrollable_apps_grid_view_ = nullptr;
// The search box owned by AppListBubbleView.
SearchBoxView* search_box_ = nullptr;
std::unique_ptr<AppListNudgeController> app_list_nudge_controller_;
// Controller for showing a modal dialog in the continue section.

@ -28,6 +28,7 @@
#include "ui/compositor/test/test_utils.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield.h"
@ -46,7 +47,7 @@ class AppListBubbleAppsPageTest : public AshTestBase {
// Sorts app list with the specified order. If `wait` is true, wait for the
// reorder animation to complete.
void SortAppList(AppListSortOrder order, bool wait) {
void SortAppList(const absl::optional<AppListSortOrder>& order, bool wait) {
AppListController::Get()->UpdateAppListWithNewTemporarySortOrder(
order,
/*animate=*/true, /*update_position_closure=*/base::DoNothing());
@ -256,14 +257,40 @@ TEST_F(AppListBubbleAppsPageTest, SortAppsMakesA11yAnnouncement) {
// An alert fired with a message.
EXPECT_EQ(event, ax::mojom::Event::kAlert);
ui::AXNodeData node_data;
announcement_view->GetViewAccessibility().GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kName),
ui::AXNodeData sort_data, button_data;
announcement_view->GetViewAccessibility().GetAccessibleNodeData(&sort_data);
EXPECT_EQ(sort_data.GetStringAttribute(ax::mojom::StringAttribute::kName),
"Apps are sorted by name");
views::LabelButton* undo_button =
apps_page->toast_container_for_test()->GetToastDismissButton();
undo_button->GetViewAccessibility().GetAccessibleNodeData(&button_data);
EXPECT_EQ(button_data.GetStringAttribute(ax::mojom::StringAttribute::kName),
"Undo sort order by name");
// Test the announcement that is announced after reverting the sort.
base::RunLoop undo_run_loop;
announcement_view->GetViewAccessibility().set_accessibility_events_callback(
base::BindLambdaForTesting([&](const ui::AXPlatformNodeDelegate* unused,
const ax::mojom::Event event_in) {
event = event_in;
undo_run_loop.Quit();
}));
// Simulate the sort undo by setting the new order to nullopt.
SortAppList(absl::nullopt, /*wait=*/false);
undo_run_loop.Run();
EXPECT_EQ(event, ax::mojom::Event::kAlert);
ui::AXNodeData undo_data;
announcement_view->GetViewAccessibility().GetAccessibleNodeData(&undo_data);
EXPECT_EQ(undo_data.GetStringAttribute(ax::mojom::StringAttribute::kName),
"Sort undo successful");
}
// Verify that after sorting app list with animation the search box should
// have focus.
// Verify that after sorting app list with animation, the undo sort toast in app
// list should have focus. In addition, the focus should move to the search box
// after reverting the sort.
TEST_F(AppListBubbleAppsPageTest, SortAppsWithItemFocused) {
ui::ScopedAnimationDurationScaleMode scope_duration(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@ -281,10 +308,20 @@ TEST_F(AppListBubbleAppsPageTest, SortAppsWithItemFocused) {
helper->GetScrollableAppsGridView()->GetFocusManager());
SortAppList(AppListSortOrder::kNameAlphabetical, /*wait=*/true);
// Verify that focus only changes once from `first_item` to the search box.
EXPECT_EQ(1, listener.focus_change_count());
// Verify that the focus moves twice. It first goes to the search box during
// the animation and then the undo button on the undo toast after the end of
// animation.
EXPECT_EQ(2, listener.focus_change_count());
EXPECT_FALSE(first_item->HasFocus());
EXPECT_TRUE(helper->GetSearchBoxView()->search_box()->HasFocus());
EXPECT_TRUE(helper->GetBubbleAppsPage()
->toast_container_for_test()
->GetToastDismissButton()
->HasFocus());
// Simulate the sort undo by setting the new order to nullopt. The focus
// should be on the search box after undoing the sort.
SortAppList(absl::nullopt, /*wait=*/true);
EXPECT_TRUE(helper->GetBubbleSearchBoxView()->search_box()->HasFocus());
}
class AppListBubbleAppsReorderTest

@ -265,7 +265,7 @@ void AppListBubbleView::InitContentsView(
pages_container->AddChildView(std::make_unique<AppListBubbleAppsPage>(
view_delegate_, drag_and_drop_host, GetAppListConfig(),
a11y_announcer_.get(), search_page_dialog_controller_.get(),
/*folder_controller=*/this));
/*folder_controller=*/this, /*search_box=*/search_box_view_));
search_page_ =
pages_container->AddChildView(std::make_unique<AppListBubbleSearchPage>(

@ -16,6 +16,7 @@
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
@ -205,12 +206,16 @@ void AppListToastContainerView::OnTemporarySortOrderChanged(
const std::u16string toast_text = CalculateToastTextFromOrder(*new_order);
const gfx::VectorIcon* toast_icon = GetToastIconForOrder(*new_order);
const std::u16string a11y_text_on_undo_button =
GetA11yTextOnUndoButtonFromOrder(*new_order);
if (toast_view_) {
// If the reorder undo toast is showing, updates the title and icon of the
// toast.
toast_view_->SetTitle(toast_text);
toast_view_->SetIcon(toast_icon);
toast_view_->toast_button()->GetViewAccessibility().OverrideName(
a11y_text_on_undo_button);
return;
}
@ -224,6 +229,9 @@ void AppListToastContainerView::OnTemporarySortOrderChanged(
&AppListToastContainerView::OnReorderUndoButtonClicked,
base::Unretained(this)))
.Build());
toast_view_->toast_button()->GetViewAccessibility().OverrideName(
a11y_text_on_undo_button);
toast_view_->UpdateInteriorMargins(kReorderUndoInteriorMargin);
current_toast_ = ToastType::kReorderUndo;
}
@ -237,6 +245,11 @@ void AppListToastContainerView::AnnounceSortOrder(AppListSortOrder new_order) {
a11y_announcer_->Announce(CalculateToastTextFromOrder(new_order));
}
void AppListToastContainerView::AnnounceUndoSort() {
a11y_announcer_->Announce(
l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_UNDO_SORT_DONE_SPOKEN_TEXT));
}
views::LabelButton* AppListToastContainerView::GetToastDismissButton() {
if (!toast_view_ || current_toast_ != ToastType::kReorderUndo)
return nullptr;
@ -264,4 +277,20 @@ std::u16string AppListToastContainerView::CalculateToastTextFromOrder(
}
}
std::u16string AppListToastContainerView::GetA11yTextOnUndoButtonFromOrder(
AppListSortOrder order) const {
switch (order) {
case AppListSortOrder::kNameAlphabetical:
case AppListSortOrder::kNameReverseAlphabetical:
return l10n_util::GetStringUTF16(
IDS_ASH_LAUNCHER_UNDO_NAME_SORT_TOAST_SPOKEN_TEXT);
case AppListSortOrder::kColor:
return l10n_util::GetStringUTF16(
IDS_ASH_LAUNCHER_UNDO_COLOR_SORT_TOAST_SPOKEN_TEXT);
case AppListSortOrder::kCustom:
NOTREACHED();
return u"";
}
}
} // namespace ash

@ -6,6 +6,7 @@
#define ASH_APP_LIST_VIEWS_APP_LIST_TOAST_CONTAINER_VIEW_H_
#include <memory>
#include <string>
#include "ash/app_list/views/app_list_nudge_controller.h"
#include "ui/views/view.h"
@ -110,9 +111,13 @@ class AppListToastContainerView : public views::View {
// Fires an accessibility alert with the text of the sort order toast.
void AnnounceSortOrder(AppListSortOrder new_order);
// Fires an accessibility alert to notify the users that the sort is reverted.
void AnnounceUndoSort();
// This function expects that `toast_view_` exists.
views::LabelButton* GetToastDismissButton();
AppListToastView* toast_view() { return toast_view_; }
bool is_toast_visible() const { return toast_view_; }
ToastType current_toast() const { return current_toast_; }
@ -126,6 +131,8 @@ class AppListToastContainerView : public views::View {
[[nodiscard]] std::u16string CalculateToastTextFromOrder(
AppListSortOrder order) const;
std::u16string GetA11yTextOnUndoButtonFromOrder(AppListSortOrder order) const;
AppListA11yAnnouncer* const a11y_announcer_;
// The app list toast container is visually part of the apps grid and should

@ -17,6 +17,7 @@
#include "ash/app_list/views/app_list_main_view.h"
#include "ash/app_list/views/app_list_nudge_controller.h"
#include "ash/app_list/views/app_list_toast_container_view.h"
#include "ash/app_list/views/app_list_toast_view.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/app_list/views/contents_view.h"
#include "ash/app_list/views/continue_section_view.h"
@ -51,6 +52,7 @@
#include "ui/gfx/paint_vector_icon.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/flex_layout.h"
@ -718,13 +720,20 @@ void AppsContainerView::UpdateForNewSortingOrder(
DCHECK_EQ(animate, !update_position_closure.is_null());
DCHECK(!animation_done_closure || animate);
if (new_order)
// A11y announcements must happen before animations, otherwise the undo
// guidance is spoken first because focus moves immediately to the undo button
// on the toast.
if (new_order) {
toast_container_->AnnounceSortOrder(*new_order);
} else if (animate) {
toast_container_->AnnounceUndoSort();
}
if (!animate) {
// Reordering is not required so update the undo toast and return early.
app_list_nudge_controller_->OnTemporarySortOrderChanged(new_order);
toast_container_->OnTemporarySortOrderChanged(new_order);
HandleFocusAfterSort();
return;
}
@ -751,7 +760,7 @@ void AppsContainerView::UpdateForNewSortingOrder(
views::AnimationBuilder animation_builder =
apps_grid_view_->FadeOutVisibleItemsForReorder(base::BindRepeating(
&AppsContainerView::OnAppsGridViewFadeOutAnimationEneded,
&AppsContainerView::OnAppsGridViewFadeOutAnimationEnded,
weak_ptr_factory_.GetWeakPtr(), new_order));
// Configure the toast fade out animation if the toast is going to be hidden.
@ -1540,7 +1549,7 @@ void AppsContainerView::UpdateGradientMaskBounds() {
gradient_layer_delegate_->layer()->SetBounds(container_bounds);
}
void AppsContainerView::OnAppsGridViewFadeOutAnimationEneded(
void AppsContainerView::OnAppsGridViewFadeOutAnimationEnded(
const absl::optional<AppListSortOrder>& new_order,
bool abort) {
// Update item positions after the fade out animation but before the fade in
@ -1563,6 +1572,7 @@ void AppsContainerView::OnAppsGridViewFadeOutAnimationEneded(
const bool old_toast_visible = toast_container_->is_toast_visible();
toast_container_->OnTemporarySortOrderChanged(new_order);
HandleFocusAfterSort();
// Skip the fade in animation if the fade out animation is aborted.
if (abort) {
@ -1633,6 +1643,22 @@ void AppsContainerView::OnReorderAnimationEnded() {
std::move(reorder_animation_done_closure_).Run();
}
void AppsContainerView::HandleFocusAfterSort() {
// As the sort update on AppsContainerView can be called in both clamshell
// mode and tablet mode, return early if it's currently in clamshell mode
// because the AppsContainerView isn't visible.
if (!contents_view_->app_list_view()->is_tablet_mode())
return;
// If the sort is done and the toast is visible, request the focus on the
// undo button on the toast. Otherwise request the focus on the search box.
if (toast_container_->is_toast_visible()) {
toast_container_->toast_view()->toast_button()->RequestFocus();
} else {
contents_view_->GetSearchBoxView()->search_box()->RequestFocus();
}
}
int AppsContainerView::GetSeparatorHeight() {
if (!separator_ || !separator_->GetVisible())
return 0;

@ -317,7 +317,7 @@ class ASH_EXPORT AppsContainerView
// Called when the animation to fade out app list items is completed.
// `aborted` indicates whether the fade out animation is aborted.
void OnAppsGridViewFadeOutAnimationEneded(
void OnAppsGridViewFadeOutAnimationEnded(
const absl::optional<AppListSortOrder>& new_order,
bool aborted);
@ -331,6 +331,9 @@ class ASH_EXPORT AppsContainerView
// (2) At the end of the fade in animation.
void OnReorderAnimationEnded();
// Called after sort to handle focus.
void HandleFocusAfterSort();
// While true, the gradient mask will not be removed as a mask layer until
// cardified state ends.
bool keep_gradient_mask_for_cardified_state_ = false;

@ -29,6 +29,7 @@
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h"
namespace ash {
@ -108,7 +109,7 @@ class PagedAppsGridViewTest : public PagedAppsGridViewTestBase {
// Sorts app list with the specified order. If `wait` is true, wait for the
// reorder animation to complete.
void SortAppList(AppListSortOrder order, bool wait) {
void SortAppList(const absl::optional<AppListSortOrder>& order, bool wait) {
AppListController::Get()->UpdateAppListWithNewTemporarySortOrder(
order,
/*animate=*/true, /*update_position_closure=*/base::DoNothing());
@ -551,9 +552,17 @@ TEST_F(PagedAppsGridViewTest, SortAppsWithItemFocused) {
// Verify that the reorder undo toast's layer opacity does not change.
EXPECT_EQ(1.f, reorder_undo_toast_container->layer()->opacity());
// Verify that focus only changes once from `first_item` to the search box.
EXPECT_EQ(1, listener.focus_change_count());
// Verify that the focus moves twice. It first goes to the search box during
// the animation and then the undo button on the undo toast after the end of
// animation.
EXPECT_EQ(2, listener.focus_change_count());
EXPECT_FALSE(first_item->HasFocus());
EXPECT_TRUE(
reorder_undo_toast_container->GetToastDismissButton()->HasFocus());
// Simulate the sort undo by setting the new order to nullopt. The focus
// should be on the search box after undoing the sort.
SortAppList(absl::nullopt, /*wait=*/true);
EXPECT_TRUE(helper->GetSearchBoxView()->search_box()->HasFocus());
}

@ -4544,6 +4544,15 @@ Here are some things you can try to get started.
<message name="IDS_ASH_LAUNCHER_UNDO_SORT_TOAST_ACTION_BUTTON" desc="The label for the action button for a toast shown within the launcher UI that appears after the user sorts their apps, either aplhabetically or by icon color. The action button reverts the order of apps in the launcher to the one from before the user requested sort.">
Undo
</message>
<message name="IDS_ASH_LAUNCHER_UNDO_NAME_SORT_TOAST_SPOKEN_TEXT" desc="Accessibility text read by chromevox when focusing on the undo button shown in a toast that gets shown in the launcher UI after the user sorts apps in the launcher by name. This undo button reverts the order of apps in launcher to the one before sort actions.">
Undo sort order by name
</message>
<message name="IDS_ASH_LAUNCHER_UNDO_COLOR_SORT_TOAST_SPOKEN_TEXT" desc="Accessibility text read by chromevox when focusing on the undo button shown in a toast that gets shown in the launcher UI after the user sorts apps in the launcher by color. This undo button reverts the order of apps in launcher to the one before sort actions.">
Undo sort order by color
</message>
<message name="IDS_ASH_LAUNCHER_UNDO_SORT_DONE_SPOKEN_TEXT" desc="Accessibility text read by chromevox after successfully undoing/reverting the sorting action on apps in launcher.">
Sort undo successful
</message>
<message name="IDS_ASH_LAUNCHER_APP_LIST_REORDER_NUDGE_TITLE" desc="Title of a nudge shown in the Chrome OS launcher UI above the list of apps that informs users that they can sort their apps by name or color.">
Sort your apps by name or color
</message>

@ -0,0 +1 @@
1cbd0fa774dffeb7de639ccdddbc3db690702f40

@ -0,0 +1 @@
ce03f313060611e83e1fb4a5bda1f70a85feba40

@ -0,0 +1 @@
3058229db180ad857569786d23532e1faa51e75b