1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344

ash / system / video_conference / video_conference_tray_controller.h [blame]

// 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_