chromium/chromeos/ash/components/audio/audio_selection_notification_handler.h

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

#ifndef CHROMEOS_ASH_COMPONENTS_AUDIO_AUDIO_SELECTION_NOTIFICATION_HANDLER_H_
#define CHROMEOS_ASH_COMPONENTS_AUDIO_AUDIO_SELECTION_NOTIFICATION_HANDLER_H_

#include <optional>

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/audio/audio_device.h"
#include "chromeos/ash/components/audio/audio_device_id.h"
#include "chromeos/ash/components/audio/audio_device_metrics_handler.h"
#include "chromeos/ash/components/audio/device_activate_type.h"
#include "ui/message_center/message_center.h"

namespace ash {

// A callback of CrasAudioHandler::SwitchToDevice function, used to handle the
// switch button on audio selection notification being clicked.
using SwitchToDeviceCallback =
    base::RepeatingCallback<void(const AudioDevice& device,
                                 bool notify,
                                 DeviceActivateType activate_by)>;

// A callback function to open OS Settings audio page.
using OpenSettingsAudioPageCallback = base::RepeatingCallback<void()>;

// AudioSelectionNotificationHandler handles the creation and display of the
// audio selection notification.
class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_AUDIO)
    AudioSelectionNotificationHandler {
 public:
  AudioSelectionNotificationHandler();
  AudioSelectionNotificationHandler(const AudioSelectionNotificationHandler&) =
      delete;
  AudioSelectionNotificationHandler& operator=(
      const AudioSelectionNotificationHandler&) = delete;
  ~AudioSelectionNotificationHandler();

  // Time delta to debounce the audio selection notification.
  static constexpr base::TimeDelta kDebounceTime = base::Milliseconds(1500);

  // The audio selection notification id, used to identify the notification
  // itself.
  static constexpr char kAudioSelectionNotificationId[] =
      "audio_selection_notification";

  // The audio selection notifier id.
  static constexpr char kAudioSelectionNotifierId[] =
      "ash.audio_selection_notification_handler";

  // Different types of audio selection notification.
  // Do not reorder since it's used in histogram metrics.
  enum class NotificationType {
    // A single audio device source with only input audio device. e.g. a web
    // cam.
    kSingleSourceWithInputOnly = 0,
    // A single audio device source with only output audio device. e.g. a HDMI
    // display.
    kSingleSourceWithOutputOnly = 1,
    // A single audio device source with both input and output audio devices.
    // e.g. a USB audio device.
    kSingleSourceWithInputAndOutput = 2,
    // Multiple audio device sources, e.g. a web cam and a HDMI display.
    kMultipleSources = 3,
    kMaxValue = kMultipleSources,
  };

  // Stores minimal info to create a notification. The device names will be
  // displayed in notification body. If the notification type is
  // kMultipleSources, device name refers to currently activated device name.
  // Otherwise, device name refers to hot plugged device name.
  struct NotificationTemplate {
    NotificationTemplate(NotificationType type,
                         std::optional<std::string> input_device_name,
                         std::optional<std::string> output_device_name);
    ~NotificationTemplate();

    NotificationType type;
    std::optional<std::string> input_device_name;
    std::optional<std::string> output_device_name;
  };

  // Creates and displays an audio selection notification to let users make the
  // switching or not switching decision.
  // TODO(b/333608911): Revisit audio selection notification after updated audio
  // sliders in quick settings.
  void ShowAudioSelectionNotification(
      const AudioDeviceList& hotplug_input_devices,
      const AudioDeviceList& hotplug_output_devices,
      const std::optional<std::string>& active_input_device_name,
      const std::optional<std::string>& active_output_device_name,
      SwitchToDeviceCallback switch_to_device_callback,
      OpenSettingsAudioPageCallback open_settings_audio_page_callback);

  // Handles the situation when a hotplugged device which triggers the
  // notification has been activated by users via settings or quick settings,
  // rather than via the switch button on notification body. Remove the
  // notification in this case.
  void RemoveNotificationIfHotpluggedDeviceActivated(
      const AudioDeviceList& activated_devices);

  // Handles the situation when a hotplugged device which triggers the
  // notification has been removed. Remove the notification in this case.
  void RemoveNotificationIfHotpluggedDeviceDisconnected(
      bool is_input,
      const AudioDeviceList& current_devices);

 private:
  // Grant friend access for comprehensive testing of private/protected members.
  friend class AudioSelectionNotificationHandlerTest;

  // Handles when the switch button is clicked.
  void HandleSwitchButtonClicked(
      const AudioDeviceList& devices_to_activate,
      SwitchToDeviceCallback switch_to_device_callback,
      NotificationType notification_type,
      std::optional<int> button_index);

  // Handles when the settings button is clicked. |open_settigns_callback| is
  // the callback to open the system settings audio page. |button_index|
  // indicates the index of the button on notification body that is clicked.
  void HandleSettingsButtonClicked(
      OpenSettingsAudioPageCallback open_settigns_callback,
      std::optional<int> button_index);

  // Checks if one audio input device and one audio output device belong to the
  // same physical audio device.
  bool AudioNodesBelongToSameSource(const AudioDevice& input_device,
                                    const AudioDevice& output_device);

  // Gets necessary information to create and display notitification, such as
  // notitication type and device name.
  NotificationTemplate GetNotificationTemplate(
      const AudioDeviceList& hotplug_input_devices,
      const AudioDeviceList& hotplug_output_devices,
      const std::optional<std::string>& active_input_device_name,
      const std::optional<std::string>& active_output_device_name);

  // A helper function to determine notification type and show notification.
  void ShowNotification(
      const std::optional<std::string>& active_input_device_name,
      const std::optional<std::string>& active_output_device_name,
      SwitchToDeviceCallback switch_to_device_callback,
      OpenSettingsAudioPageCallback open_settings_audio_page_callback);

  // Handles firing of audio selection related metrics.
  AudioDeviceMetricsHandler audio_device_metrics_handler_;

  // The hotplugged devices that trigger the notification. Clear the list if the
  // notification is removed.
  AudioDeviceList hotplug_input_devices_;
  AudioDeviceList hotplug_output_devices_;

  // Used to debounce the notification.
  base::RetainingOneShotTimer show_notification_debounce_timer_;

  base::WeakPtrFactory<AudioSelectionNotificationHandler> weak_ptr_factory_{
      this};
};

}  // namespace ash

#endif  // CHROMEOS_ASH_COMPONENTS_AUDIO_AUDIO_SELECTION_NOTIFICATION_HANDLER_H_