#include "ash/app_list/app_list_bubble_presenter.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "ash/app_list/app_list_bubble_event_filter.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_event_targeter.h"
#include "ash/app_list/apps_collections_controller.h"
#include "ash/app_list/views/app_list_bubble_apps_collections_page.h"
#include "ash/app_list/views/app_list_bubble_apps_page.h"
#include "ash/app_list/views/app_list_bubble_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/public/cpp/app_list/app_list_client.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/scanner/scanner_metrics.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/wm/container_finder.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
constexpr base::TimeDelta kZeroStateSearchTimeout = base::Milliseconds(16);
constexpr int kWorkAreaPadding = 8;
constexpr int kExtraTopOfScreenSpacing = 16;
gfx::Rect GetWorkAreaForBubble(aura::Window* root_window) {
display::Display display =
display::Screen::Get()->GetDisplayNearestWindow(root_window);
gfx::RectF work_area(display.work_area());
gfx::RectF shelf_bounds(Shelf::ForWindow(root_window)->GetIdealBounds());
wm::TranslateRectToScreen(root_window, &shelf_bounds);
work_area.Subtract(shelf_bounds);
return gfx::ToRoundedRect(work_area);
}
int GetBubbleWidth(gfx::Rect work_area, aura::Window* root_window) {
return work_area.width() < 1200 ? 544 : 640;
}
gfx::Size ComputeBubbleSize(aura::Window* root_window,
AppListBubbleView* bubble_view) {
const int default_height = 688;
const int shelf_size = ShelfConfig::Get()->shelf_size();
const gfx::Rect work_area = GetWorkAreaForBubble(root_window);
int height = default_height;
const int width = GetBubbleWidth(work_area, root_window);
if (work_area.height() <
default_height + shelf_size + kExtraTopOfScreenSpacing) {
height = work_area.height() - shelf_size - kExtraTopOfScreenSpacing;
} else if (work_area.height() >
default_height * 2 + shelf_size + kExtraTopOfScreenSpacing) {
int height_to_fit_all_apps = bubble_view->GetHeightToFitAllApps();
int max_height =
(work_area.height() - shelf_size - kExtraTopOfScreenSpacing) / 2;
DCHECK_GE(max_height, default_height);
height = std::clamp(height_to_fit_all_apps, default_height, max_height);
}
return gfx::Size(width, height);
}
gfx::Rect ComputeBubbleBounds(aura::Window* root_window,
AppListBubbleView* bubble_view) {
const gfx::Rect work_area = GetWorkAreaForBubble(root_window);
const gfx::Size bubble_size = ComputeBubbleSize(root_window, bubble_view);
const int padding = kWorkAreaPadding;
int x = 0;
int y = 0;
switch (Shelf::ForWindow(root_window)->alignment()) {
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
if (base::i18n::IsRTL())
x = work_area.right() - padding - bubble_size.width();
else
x = work_area.x() + padding;
y = work_area.bottom() - padding - bubble_size.height();
break;
case ShelfAlignment::kLeft:
x = work_area.x() + padding;
y = work_area.y() + padding;
break;
case ShelfAlignment::kRight:
x = work_area.right() - padding - bubble_size.width();
y = work_area.y() + padding;
break;
}
return gfx::Rect(x, y, bubble_size.width(), bubble_size.height());
}
views::Widget* CreateBubbleWidget(aura::Window* root_window) {
views::Widget* widget = new views::Widget();
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.name = "AppListBubble";
params.parent =
Shell::GetContainer(root_window, kShellWindowId_AppListContainer);
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.layer_type = ui::LAYER_NOT_DRAWN;
widget->Init(std::move(params));
return widget;
}
}
AppListBubblePresenter::AppListBubblePresenter(
AppListControllerImpl* controller)
: controller_(controller) {
DCHECK(controller_);
}
AppListBubblePresenter::~AppListBubblePresenter() {
CHECK(!views::WidgetObserver::IsInObserverList());
}
void AppListBubblePresenter::Shutdown() {
DVLOG(1) << __PRETTY_FUNCTION__;
if (bubble_view_)
bubble_view_->AbortAllAnimations();
if (bubble_widget_)
bubble_widget_->CloseNow();
DCHECK(!bubble_widget_);
DCHECK(!bubble_view_);
}
void AppListBubblePresenter::Show(int64_t display_id) {
DVLOG(1) << __PRETTY_FUNCTION__;
if (is_target_visibility_show_)
return;
if (bubble_view_)
bubble_view_->AbortAllAnimations();
is_target_visibility_show_ = true;
target_page_ = AppsCollectionsController::Get()->ShouldShowAppsCollection()
? AppListBubblePage::kAppsCollections
: AppListBubblePage::kApps;
controller_->OnVisibilityWillChange(true, display_id);
controller_->GetClient()->StartZeroStateSearch(
base::BindOnce(&AppListBubblePresenter::OnZeroStateSearchDone,
weak_factory_.GetWeakPtr(), display_id),
kZeroStateSearchTimeout);
}
void AppListBubblePresenter::OnZeroStateSearchDone(int64_t display_id) {
DVLOG(1) << __PRETTY_FUNCTION__;
if (!is_target_visibility_show_)
return;
aura::Window* root_window = Shell::GetRootWindowForDisplayId(display_id);
if (!root_window)
return;
Shelf* shelf = Shelf::ForWindow(root_window);
HomeButton* home_button = shelf->navigation_widget()->GetHomeButton();
if (!bubble_widget_) {
base::TimeTicks time_shown = base::TimeTicks::Now();
bubble_widget_ = CreateBubbleWidget(root_window);
bubble_widget_->GetNativeWindow()->SetTitle(l10n_util::GetStringUTF16(
IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT));
bubble_widget_->GetNativeWindow()->SetEventTargeter(
std::make_unique<AppListEventTargeter>(controller_));
bubble_view_ = bubble_widget_->SetContentsView(
std::make_unique<AppListBubbleView>(controller_));
bubble_widget_->widget_delegate()->SetEnableArrowKeyTraversal(true);
bubble_widget_->AddObserver(this);
Shell::Get()->activation_client()->AddObserver(this);
bubble_event_filter_ = std::make_unique<AppListBubbleEventFilter>(
bubble_widget_, home_button,
base::BindRepeating(&AppListBubblePresenter::OnPressOutsideBubble,
base::Unretained(this)));
UmaHistogramTimes("Apps.AppListBubbleCreationTime",
base::TimeTicks::Now() - time_shown);
} else {
DCHECK(bubble_view_);
bubble_view_->UpdateSuggestions();
bubble_event_filter_->SetButton(home_button);
}
bubble_widget_->SetBounds(ComputeBubbleBounds(root_window, bubble_view_));
controller_->SetKeyboardTraversalMode(true);
bubble_widget_->Show();
bubble_view_->ShowPage(target_page_);
const bool is_side_shelf = !shelf->IsHorizontalAlignment();
bubble_view_->StartShowAnimation(is_side_shelf);
controller_->OnVisibilityChanged(true, display_id);
views::ImageButton* sunfish_button =
bubble_view_->search_box_view()->sunfish_button();
CHECK(sunfish_button);
controller_->MaybeShowSunfishLauncherNudge(sunfish_button);
}
ShelfAction AppListBubblePresenter::Toggle(int64_t display_id) {
DVLOG(1) << __PRETTY_FUNCTION__;
if (is_target_visibility_show_) {
Dismiss();
return SHELF_ACTION_APP_LIST_DISMISSED;
}
Show(display_id);
return SHELF_ACTION_APP_LIST_SHOWN;
}
void AppListBubblePresenter::Dismiss() {
DVLOG(1) << __PRETTY_FUNCTION__;
if (!is_target_visibility_show_)
return;
if (bubble_view_)
bubble_view_->AbortAllAnimations();
const int64_t display_id = GetDisplayId();
is_target_visibility_show_ = false;
controller_->SetKeyboardTraversalMode(false);
controller_->ViewClosing();
controller_->OnVisibilityWillChange(false, display_id);
if (bubble_view_) {
aura::Window* bubble_window = bubble_view_->GetWidget()->GetNativeWindow();
DCHECK(bubble_window);
Shelf* shelf = Shelf::ForWindow(bubble_window);
const bool is_side_shelf = !shelf->IsHorizontalAlignment();
bubble_view_->StartHideAnimation(
is_side_shelf,
base::BindOnce(&AppListBubblePresenter::OnHideAnimationEnded,
weak_factory_.GetWeakPtr()));
}
controller_->OnVisibilityChanged(false, display_id);
}
aura::Window* AppListBubblePresenter::GetWindow() const {
return is_target_visibility_show_ && bubble_widget_
? bubble_widget_->GetNativeWindow()
: nullptr;
}
bool AppListBubblePresenter::IsShowing() const {
return is_target_visibility_show_;
}
void AppListBubblePresenter::UpdateContinueSectionVisibility() {
if (bubble_view_)
bubble_view_->UpdateContinueSectionVisibility();
}
void AppListBubblePresenter::UpdateForNewSortingOrder(
const std::optional<AppListSortOrder>& new_order,
bool animate,
base::OnceClosure update_position_closure) {
DCHECK_EQ(animate, !update_position_closure.is_null());
if (!bubble_view_) {
if (update_position_closure)
std::move(update_position_closure).Run();
return;
}
bubble_view_->UpdateForNewSortingOrder(new_order, animate,
std::move(update_position_closure));
}
void AppListBubblePresenter::OnWidgetDestroying(views::Widget* widget) {
DVLOG(1) << __PRETTY_FUNCTION__;
bubble_event_filter_.reset();
Shell::Get()->activation_client()->RemoveObserver(this);
bubble_widget_->RemoveObserver(this);
bubble_widget_ = nullptr;
bubble_view_ = nullptr;
}
void AppListBubblePresenter::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!is_target_visibility_show_)
return;
if (gained_active) {
if (auto* container = GetContainerForWindow(gained_active)) {
const int container_id = container->GetId();
if (container_id == kShellWindowId_AppListContainer ||
container_id == kShellWindowId_ShelfContainer ||
container_id == kShellWindowId_HelpBubbleContainer) {
return;
}
}
}
if (reason == wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT)
return;
aura::Window* app_list_container =
bubble_widget_->GetNativeWindow()->parent();
if ((lost_active && app_list_container->Contains(lost_active)) ||
(gained_active && !app_list_container->Contains(gained_active))) {
Dismiss();
}
}
void AppListBubblePresenter::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
if (!IsShowing())
return;
if (display.id() != GetDisplayId())
return;
aura::Window* root_window =
bubble_widget_->GetNativeWindow()->GetRootWindow();
bubble_widget_->SetBounds(ComputeBubbleBounds(root_window, bubble_view_));
}
void AppListBubblePresenter::OnPressOutsideBubble(
const ui::LocatedEvent& event) {
controller_->RecordAppListState();
if (bubble_widget_->IsActive()) {
bubble_widget_->Deactivate();
}
Dismiss();
}
int64_t AppListBubblePresenter::GetDisplayId() const {
if (!is_target_visibility_show_ || !bubble_widget_)
return display::kInvalidDisplayId;
return display::Screen::Get()
->GetDisplayNearestView(bubble_widget_->GetNativeView())
.id();
}
void AppListBubblePresenter::OnHideAnimationEnded() {
auto lock = TrayBackgroundView::DisableCloseBubbleOnWindowActivated();
bubble_widget_->Hide();
}
int AppListBubblePresenter::GetPreferredBubbleWidth(
aura::Window* root_window) const {
return GetBubbleWidth(GetWorkAreaForBubble(root_window), root_window);
}
}