chromium/ash/system/notification_center/ash_message_popup_collection.h

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

#ifndef ASH_SYSTEM_NOTIFICATION_CENTER_ASH_MESSAGE_POPUP_COLLECTION_H_
#define ASH_SYSTEM_NOTIFICATION_CENTER_ASH_MESSAGE_POPUP_COLLECTION_H_

#include <stdint.h>
#include <memory>

#include "ash/ash_export.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/shelf/shelf_observer.h"
#include "ash/system/tray/system_tray_observer.h"
#include "ash/system/tray/tray_event_filter.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "ui/compositor/throughput_tracker.h"
#include "ui/display/display_observer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/message_center/views/message_popup_collection.h"
#include "ui/message_center/views/message_view.h"
#include "ui/views/widget/widget_observer.h"

namespace display {
class Screen;
enum class TabletState;
}  // namespace display

namespace views {
class Widget;
}  // namespace views

namespace ash {

class AshMessagePopupCollectionTest;
class Shelf;
class TrayBubbleView;
class TrayEventFilterTest;

// The MessagePopupCollection subclass for Ash. It needs to handle alignment of
// the shelf and its autohide state.
class ASH_EXPORT AshMessagePopupCollection
    : public display::DisplayObserver,
      public message_center::MessagePopupCollection,
      public message_center::MessageView::Observer,
      public views::WidgetObserver {
 public:
  // The name that will set for the message popup widget in
  // ConfigureWidgetInitParamsForContainer(), and that can be used to identify a
  // message popup widget.
  static const char kMessagePopupWidgetName[];

  // All the types of surfaces that can make popup collection shift up. Used
  // inside of `NotifierCollisionHandler` for metrics collection. Make sure to
  // keep this in sync with `NotifierCollisionSurfaceType` in
  // tools/metrics/histograms/metadata/ash/enums.xml.
  enum class NotifierCollisionSurfaceType {
    // Default value. Ideally this should never be recorded in the metrics.
    kNone = 0,

    kShelfPodBubble = 1,
    kSliderBubble = 2,
    kExtendedHotseat = 3,
    kSliderBubbleAndExtendedHotseat = 4,
    kMaxValue = kSliderBubbleAndExtendedHotseat
  };

  AshMessagePopupCollection(display::Screen* screen, Shelf* shelf);

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

  ~AshMessagePopupCollection() override;

  // Start observing the system.
  void StartObserving(display::Screen* screen, const display::Display& display);

  // message_center::MessagePopupCollection:
  int GetPopupOriginX(const gfx::Rect& popup_bounds) const override;
  int GetBaseline() const override;
  gfx::Rect GetWorkArea() const override;
  bool IsTopDown() const override;
  bool IsFromLeft() const override;
  bool RecomputeAlignment(const display::Display& display) override;
  void ConfigureWidgetInitParamsForContainer(
      views::Widget* widget,
      views::Widget::InitParams* init_params) override;
  bool IsPrimaryDisplayForNotification() const override;
  bool BlockForMixedFullscreen(
      const message_center::Notification& notification) const override;
  void NotifyPopupAdded(message_center::MessagePopupView* popup) override;
  void NotifyPopupClosed(message_center::MessagePopupView* popup) override;
  void NotifySilentNotification(const std::string& notification_id) override;
  void NotifyPopupCollectionHeightChanged() override;
  void AnimationStarted() override;
  void AnimationFinished() override;
  message_center::MessagePopupView* CreatePopup(
      const message_center::Notification& notification) override;
  void ClosePopupItem(PopupItem& item) override;

  // Returns true if `widget` is a popup widget belongs to this popup
  // collection.
  bool IsWidgetAPopupNotification(views::Widget* widget);

  // Sets `animation_idle_closure_`.
  void SetAnimationIdleClosureForTest(base::OnceClosure closure);

  int popups_animating_for_test() const { return popups_animating_; }

 private:
  friend class AshMessagePopupCollectionTest;
  friend class NotificationGroupingControllerTest;
  friend class TrayEventFilterTest;

  // Handles the collision of popup notifications with corner anchored shelf pod
  // bubbles, sliders, shelf, and the extended hotseat by updating the popup
  // baseline.
  class NotifierCollisionHandler : public ShelfObserver,
                                   public SystemTrayObserver,
                                   public display::DisplayObserver {
   public:
    explicit NotifierCollisionHandler(
        AshMessagePopupCollection* popup_collection);

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

    ~NotifierCollisionHandler() override;

    // Triggered whenever the height of the popup collection changes.
    void OnPopupCollectionHeightChanged();

    // Calculates the offset that is applied to the popup collection's baseline.
    // It considers the extended hotseat, corner anchored shelf pod bubbles and
    // slider bubbles.
    int CalculateBaselineOffset();

    // SystemTrayObserver:
    void OnFocusLeavingSystemTray(bool reverse) override {}
    void OnStatusAreaAnchoredBubbleVisibilityChanged(
        TrayBubbleView* tray_bubble,
        bool visible) override;
    void OnTrayBubbleBoundsChanged(TrayBubbleView* tray_bubble) override;

   private:
    // Handles bubble visibility or bounds changes.
    void HandleBubbleVisibilityOrBoundsChanged();

    // Calculates the baseline offset applied when the hotseat is extended in
    // tablet mode and a corner anchored shelf pod bubble is not open.
    int CalculateExtendedHotseatOffset() const;

    // Calculates the baseline offset applied when a slider is visible and a
    // corner anchored shelf pod bubble is not open.
    int CalculateSliderOffset() const;

    // Records metrics for the count of popups when it is put on top of a
    // surface.
    void RecordOnTopOfSurfacesPopupCount();

    // Records surface type when there are popup(s) on top of that surface.
    void RecordSurfaceType();

    // display::DisplayObserver:
    void OnDisplayTabletStateChanged(display::TabletState state) override;

    // ShelfObserver:
    void OnBackgroundTypeChanged(ShelfBackgroundType background_type,
                                 AnimationChangeType change_type) override;
    void OnShelfWorkAreaInsetsChanged() override;
    void OnHotseatStateChanged(HotseatState old_state,
                               HotseatState new_state) override;

    raw_ptr<AshMessagePopupCollection> const popup_collection_;

    // Keeps track of the current baseline offset and surface type for metrics
    // collection purpose.
    int baseline_offset_ = 0;
    NotifierCollisionSurfaceType surface_type_ =
        NotifierCollisionSurfaceType::kNone;

    // True if bubble changes are being handled in
    // `HandleBubbleVisibilityOrBoundsChanged()`.
    bool is_handling_bubble_change_ = false;

    display::ScopedDisplayObserver display_observer_{this};
  };

  // message_center::MessageView::Observer:
  void OnSlideOut(const std::string& notification_id) override;
  void OnCloseButtonPressed(const std::string& notification_id) override;
  void OnSettingsButtonPressed(const std::string& notification_id) override;
  void OnSnoozeButtonPressed(const std::string& notification_id) override;

  // Gets the current alignment of the shelf.
  ShelfAlignment GetAlignment() const;

  // Utility function to get the display which should be care about.
  display::Display GetCurrentDisplay() const;

  // Computes the new work area.
  void UpdateWorkArea();

  // display::DisplayObserver:
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t metrics) override;

  // views::WidgetObserver:
  void OnWidgetClosing(views::Widget* widget) override;
  void OnWidgetActivationChanged(views::Widget* widget, bool active) override;

  std::unique_ptr<NotifierCollisionHandler> notifier_collision_handler_;

  std::optional<display::ScopedDisplayObserver> display_observer_;

  raw_ptr<display::Screen> screen_;
  gfx::Rect work_area_;

  // Outlives this class.
  raw_ptr<Shelf> shelf_;

  std::set<raw_ptr<views::Widget, SetExperimental>> tracked_widgets_;

  // Tracks the smoothness of popup animation.
  std::optional<ui::ThroughputTracker> animation_tracker_;

  // Keeps track of number of items that are animating. This is used when we
  // have more than one popup appear in the screen and different animations are
  // performed at the same time (fade in, move up, etc.), making sure that we
  // stop the throughput tracker only when all of these animations are finished.
  int popups_animating_ = 0;

  // A closure called when all item animations complete. Used for tests only.
  base::OnceClosure animation_idle_closure_;

  // Keeps track the last pop up added, used by throughout tracker. We only
  // record smoothness when this variable is in scope.
  raw_ptr<message_center::MessagePopupView> last_pop_up_added_ = nullptr;
};

}  // namespace ash

#endif  // ASH_SYSTEM_NOTIFICATION_CENTER_ASH_MESSAGE_POPUP_COLLECTION_H_