chromium/ash/app_list/app_list_presenter_event_filter.cc

// Copyright 2021 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/app_list/app_list_presenter_event_filter.h"

#include <memory>
#include <optional>
#include <utility>

#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_presenter_impl.h"
#include "ash/app_list/app_list_util.h"
#include "ash/app_list/views/app_list_main_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/bubble/bubble_utils.h"
#include "ash/shelf/back_button.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "base/check.h"
#include "ui/aura/window.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace ash {

AppListPresenterEventFilter::AppListPresenterEventFilter(
    AppListControllerImpl* controller,
    AppListPresenterImpl* presenter,
    AppListView* view)
    : controller_(controller), presenter_(presenter), view_(view) {
  DCHECK(controller_);
  DCHECK(presenter_);
  DCHECK(view_);
  Shell::Get()->AddPreTargetHandler(this);
}

AppListPresenterEventFilter::~AppListPresenterEventFilter() {
  Shell::Get()->RemovePreTargetHandler(this);
}

void AppListPresenterEventFilter::OnMouseEvent(ui::MouseEvent* event) {
  // Moving the mouse shouldn't hide focus rings.
  if (event->IsAnyButton())
    controller_->SetKeyboardTraversalMode(false);

  if (event->type() == ui::EventType::kMousePressed) {
    ProcessLocatedEvent(event);
  }
}

void AppListPresenterEventFilter::OnGestureEvent(ui::GestureEvent* event) {
  controller_->SetKeyboardTraversalMode(false);

  // Checks tap types instead of ui::EventType::kTouchPressed so that swipes on
  // the shelf do not close the launcher. https://crbug.com/750274
  if (event->type() == ui::EventType::kGestureTap ||
      event->type() == ui::EventType::kGestureTwoFingerTap ||
      event->type() == ui::EventType::kGestureLongPress) {
    ProcessLocatedEvent(event);
  }
}

void AppListPresenterEventFilter::OnKeyEvent(ui::KeyEvent* event) {
  // If keyboard traversal is already engaged, no-op.
  if (controller_->KeyboardTraversalEngaged())
    return;

  // If the home launcher is not shown in tablet mode, ignore events.
  if (Shell::Get()->IsInTabletMode() && !controller_->IsVisible())
    return;

  // Don't absorb the first event for the search box while it is open.
  if (view_->search_box_view()->is_search_box_active())
    return;

  // Don't absorb the first event when renaming folder.
  if (view_->IsFolderBeingRenamed())
    return;

  // Arrow keys or Tab will engage the traversal mode.
  if ((IsUnhandledArrowKeyEvent(*event) || event->key_code() == ui::VKEY_TAB)) {
    // Handle the first arrow key event to just show the focus rings (if not
    // showing Assistant). Don't absorb the first event when showing Assistant.
    if (!view_->IsShowingEmbeddedAssistantUI())
      event->SetHandled();
    controller_->SetKeyboardTraversalMode(true);
  }
}

void AppListPresenterEventFilter::ProcessLocatedEvent(ui::LocatedEvent* event) {
  // Check the general rules for closing bubbles.
  if (!bubble_utils::ShouldCloseBubbleForEvent(*event))
    return;

  aura::Window* target = static_cast<aura::Window*>(event->target());
  if (!target)
    return;

  // If the event happened on the home button's widget, it'll get handled by the
  // button.
  Shelf* shelf = Shelf::ForWindow(target);
  HomeButton* home_button = shelf->navigation_widget()->GetHomeButton();
  if (home_button && home_button->GetWidget() &&
      target == home_button->GetWidget()->GetNativeWindow()) {
    gfx::Point location_in_home_button = event->location();
    views::View::ConvertPointFromWidget(home_button, &location_in_home_button);
    if (home_button->HitTestPoint(location_in_home_button))
      return;
  }

  // If the event happened on the back button, it'll get handled by the
  // button.
  BackButton* back_button = shelf->navigation_widget()->GetBackButton();
  if (back_button && back_button->GetWidget() &&
      target == back_button->GetWidget()->GetNativeWindow()) {
    gfx::Point location_in_back_button = event->location();
    views::View::ConvertPointFromWidget(back_button, &location_in_back_button);
    if (back_button->HitTestPoint(location_in_back_button))
      return;
  }

  aura::Window* window = view_->GetWidget()->GetNativeView()->parent();
  if (window->Contains(target))
    return;

  // Try to close an open folder window: return if an open folder view was
  // closed successfully.
  if (presenter_->HandleCloseOpenFolder())
    return;

  if (!Shell::Get()->IsInTabletMode()) {
    // Don't dismiss the auto-hide shelf if event happened in status area. Then
    // the event can still be propagated.
    const aura::Window* status_window =
        shelf->shelf_widget()->status_area_widget()->GetNativeWindow();
    if (status_window && status_window->Contains(target))
      return;

    // Record the current AppListViewState to be used later for metrics. The
    // AppListViewState will change on app launch, so this will record the
    // AppListViewState before the app was launched.
    controller_->RecordAppListState();
    presenter_->Dismiss(event->time_stamp());
  }
}

}  // namespace ash