// Copyright 2022 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_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_CONTROLLER_H_
#define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_CONTROLLER_H_
#include "ash/ash_export.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/shelf/shelf.h"
#include "ash/shell_observer.h"
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "base/observer_list_types.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom-forward.h"
#include "components/prefs/pref_registry_simple.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
namespace base {
class UnguessableToken;
} // namespace base
namespace ash {
struct AnchoredNudgeData;
class VideoConferenceTray;
using MediaApps = std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr>;
// Controller that will act as a "bridge" between VC apps management and the VC
// UI layers. The singleton instance is constructed immediately before and
// destructed immediately after the UI, so any code that keeps a reference to
// it must be prepared to accommodate this specific lifetime in order to prevent
// any use-after-free bugs.
class ASH_EXPORT VideoConferenceTrayController
: public media::CameraPrivacySwitchObserver,
public CrasAudioHandler::AudioObserver,
public SessionObserver,
public ShellObserver {
public:
class Observer : public base::CheckedObserver {
public:
~Observer() override = default;
// Called when the state of `has_media_app` within
// `VideoConferenceMediaState` is changed.
virtual void OnHasMediaAppStateChange() = 0;
// Called when the state of camera permission is changed.
virtual void OnCameraPermissionStateChange() = 0;
// Called when the state of microphone permission is changed.
virtual void OnMicrophonePermissionStateChange() = 0;
// Called when the state of camera capturing is changed.
virtual void OnCameraCapturingStateChange(bool is_capturing) = 0;
// Called when the state of microphone capturing is changed.
virtual void OnMicrophoneCapturingStateChange(bool is_capturing) = 0;
// Called when the state of screen sharing is changed.
virtual void OnScreenSharingStateChange(bool is_capturing_screen) = 0;
// Called when the Dlc download state is changed for `feature_tile_title` if
// any DLC was registered for that effect.
virtual void OnDlcDownloadStateChanged(
bool error,
const std::u16string& feature_tile_title) = 0;
};
VideoConferenceTrayController();
VideoConferenceTrayController(const VideoConferenceTrayController&) = delete;
VideoConferenceTrayController& operator=(
const VideoConferenceTrayController&) = delete;
~VideoConferenceTrayController() override;
// Called inside ash/ash_prefs.cc to register related prefs.
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
// Returns the singleton instance.
static VideoConferenceTrayController* Get();
// Adds this class as an observer for CrasAudioHandler and
// CameraHalDispatcherImpl.
// (1) We should not call this in /ash/system/* tests, because we are not
// using FakeCrasAudioClient or MockCameraHalServer. Currently, we directly
// mock the VideoConferenceTrayButtons inside
// FakeVideoConferenceTrayController; which is a simpler approach.
// (2) We need this initialization in
// ChromeBrowserMainExtraPartsAsh::PreProfileInit for production code.
void Initialize(VideoConferenceManagerBase* video_conference_manager);
// Observer functions.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Whether the tray should be shown.
bool ShouldShowTray() const;
// Caches a nudge data object for nudges that attempt to show while the tray
// is animating in, so they only show once the tray animation has ended. The
// request will be run immediately if the tray is not animating.
void CreateNudgeRequest(std::unique_ptr<AnchoredNudgeData> nudge_data);
// Shows the cached `requested_nudge_data_` object, if one exists.
void MaybeRunNudgeRequest();
// Attempts showing the speak-on-mute opt-in nudge.
void MaybeShowSpeakOnMuteOptInNudge();
// Returns true if we can show the animation to help users to discover the new
// feature.
bool ShouldShowImageButtonAnimation() const;
bool ShouldShowCreateWithAiButtonAnimation() const;
// Disables showing the animation for the button from now on. Calling the
// above ShouldShow...() will return false for the current active user going
// forward.
void DismissImageButtonAnimationForever();
void DismissCreateWithAiButtonAnimationForever();
// Callback used to update prefs whenever a user opts in or out of the
// speak-on-mute feature. An `opt_in` value of false means the user opted out.
void OnSpeakOnMuteNudgeOptInAction(bool opt_in);
void OnDlcDownloadStateFetched(bool add_warning,
const std::u16string& feature_tile_title);
// Closes all nudges that are shown anchored to the VC tray, if any.
void CloseAllVcNudges();
// Returns whether `state_` indicates permissions are granted for different
// mediums.
bool GetHasCameraPermissions() const;
bool GetHasMicrophonePermissions() const;
// Returns whether `state_` indicates a capture session is in progress for
// different mediums.
bool IsCapturingScreen() const;
bool IsCapturingCamera() const;
bool IsCapturingMicrophone() const;
// Sets the state for camera mute. Virtual for testing/mocking.
virtual void SetCameraMuted(bool muted);
// Gets the state for camera mute. Virtual for testing/mocking.
virtual bool GetCameraMuted();
// Sets the state for microphone mute. Virtual for testing/mocking.
virtual void SetMicrophoneMuted(bool muted);
// Gets the state for microphone mute. Virtual for testing/mocking.
virtual bool GetMicrophoneMuted();
// Stops all screen sharing. Virtual for testing/mocking.
virtual void StopAllScreenShare();
// Returns asynchronously a vector of media apps that will be displayed in the
// "Return to app" panel of the bubble. Virtual for testing/mocking.
virtual void GetMediaApps(base::OnceCallback<void(MediaApps)> ui_callback);
// Brings the app with the given `id` to the foreground.
virtual void ReturnToApp(const base::UnguessableToken& id);
// Updates the tray UI with the given `VideoConferenceMediaState`.
void UpdateWithMediaState(VideoConferenceMediaState state);
// Returns true if any running media apps have been granted permission for
// camera/microphone.
bool HasCameraPermission() const;
bool HasMicrophonePermission() const;
// Enable or disable input stream ewma power report.
void SetEwmaPowerReportEnabled(bool enabled);
// Return the last reported ewma power.
double GetEwmaPower();
// Enable or disable sidetone.
void SetSidetoneEnabled(bool enabled);
// Gets the state for sidetone.
bool GetSidetoneEnabled() const;
// Gets whether sidetone is supported.
bool IsSidetoneSupported() const;
// Update the sidetone supported value.
// Should be called before calling IsSidetoneSupported.
void UpdateSidetoneSupportedState();
// Handles device usage from a VC app while the device is system disabled.
virtual void HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice device,
const std::u16string& app_name);
// media::CameraPrivacySwitchObserver:
void OnCameraHWPrivacySwitchStateChanged(
const std::string& device_id,
cros::mojom::CameraPrivacySwitchState state) override;
void OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState state) override;
// CrasAudioHandler::AudioObserver:
void OnInputMuteChanged(
bool mute_on,
CrasAudioHandler::InputMuteChangeMethod method) override;
// CrasAudioHandler::AudioObserver:
// Pop up a toast when speaking on mute is detected.
void OnSpeakOnMuteDetected() override;
// SessionObserver:
void OnUserSessionAdded(const AccountId& account_id) override;
// ShellObserver:
void OnShellDestroying() override;
// Handles client updates such as a change of title or addition / removal of a
// VC app. Virtual to allow mock classes to override for testing.
virtual void HandleClientUpdate(
crosapi::mojom::VideoConferenceClientUpdatePtr update);
// Handles showing the shelf when a new app is added.
void OnAppAdded();
// Gets `disable_shelf_autohide_timer_`, used for testing.
base::OneShotTimer& GetShelfAutoHideTimerForTest();
virtual VideoConferenceTrayEffectsManager& GetEffectsManager();
// Passes create background image action to `video_conference_manager_`.
void CreateBackgroundImage();
bool camera_muted_by_hardware_switch() const {
return camera_muted_by_hardware_switch_;
}
bool camera_muted_by_software_switch() const {
return camera_muted_by_software_switch_;
}
bool initialized() const { return initialized_; }
private:
// All the types of the use while disabled nudge.
enum class UsedWhileDisabledNudgeType {
kCamera = 0,
kMicrophone = 1,
kBoth = 2,
kMaxValue = kBoth
};
// Updates the state of the camera icons across all `VideoConferenceTray`.
void UpdateCameraIcons();
// Records repeated shows metric when the timer is stop.
void RecordRepeatedShows();
// Returns true if any of the VC nudges are visible on screen.
bool IsAnyVcNudgeShown();
// Displays the use while disabled nudge according to the given `type`.
void DisplayUsedWhileDisabledNudge(UsedWhileDisabledNudgeType type,
const std::u16string& app_name);
UsedWhileDisabledNudgeType GetUsedWhileDisabledNudgeType(
crosapi::mojom::VideoConferenceMediaDevice device);
// This keeps track the current VC media state. The state is being updated by
// `UpdateWithMediaState()`, calling from `VideoConferenceManagerAsh`.
VideoConferenceMediaState state_;
// This keeps track of the current Camera Privacy Switch state.
// Updated via `OnCameraHWPrivacySwitchStateChanged()` and
// `OnCameraSWPrivacySwitchStateChanged()` Fetching this would otherwise take
// an asynchronous call to `media::CameraHalDispatcherImpl`.
bool camera_muted_by_hardware_switch_ = false;
bool camera_muted_by_software_switch_ = false;
// True if microphone is muted by the hardware switch, false if the microphone
// is muted through software. If the microphone is not muted, disregards this
// value.
bool microphone_muted_by_hardware_switch_ = false;
// Timer responsible for hiding the shelf after it has been shown to alert the
// user of a new app accessing the sensors.
base::OneShotTimer disable_shelf_autohide_timer_;
// List of locks which force the shelf to show, if the shelf is autohidden.
std::list<Shelf::ScopedDisableAutoHide> disable_shelf_autohide_locks_;
// Used by the views to construct and lay out effects in the bubble.
VideoConferenceTrayEffectsManager effects_manager_;
// Registered observers.
base::ObserverList<Observer> observer_list_;
// The last time speak-on-mute nudge shown.
// The cool down periods for nudges:
// 1. No cool down for the first nudge,
// 2. 2 mins for the second nudge,
// 3. 4 mins for the third nudge,
// 4. 8 mins for the forth nudge.
base::TimeTicks last_speak_on_mute_nudge_shown_time_;
// The counter of how many time the speak-on-mute nudge has shown in the
// current session.
int speak_on_mute_nudge_shown_count_ = 0;
// `video_conference_manager_` should be valid after `initialized_`.
// Currently, `VideoConferenceTrayController` is destroyed inside
// `ChromeBrowserMainParts::PostMainMessageLoopRun()` as a chrome_extra_part;
// `VideoConferenceManagerAsh` is destroyed inside crosapi_manager_.reset()
// which is after `VideoConferenceTrayController`.
raw_ptr<VideoConferenceManagerBase> video_conference_manager_ = nullptr;
bool initialized_ = false;
// Used to record metrics of repeated shows per 100 ms.
int count_repeated_shows_ = 0;
base::DelayTimer repeated_shows_timer_;
// Due to some constraint in `VideoConferenceManagerAsh`, when both microphone
// and camera is being accessed when disabled,`HandleDeviceUsedWhileDisabled`
// will be called twice for each device. Thus, we need to wait for both 2
// calls and display one nudge for both. These are the timer and the cache
// type to make that happen.
base::OneShotTimer use_while_disabled_signal_waiter_;
UsedWhileDisabledNudgeType use_while_disabled_nudge_on_wait_;
// The contents of a nudge data object that is cached so it can be shown once
// the tray has fully animated in.
std::unique_ptr<AnchoredNudgeData> requested_nudge_data_;
base::WeakPtrFactory<VideoConferenceTrayController> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_CONTROLLER_H_