chromium/ash/media/media_controller_impl.cc

// Copyright 2016 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/media/media_controller_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/media_client.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "media/base/media_switches.h"
#include "services/media_session/public/mojom/constants.mojom.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/base/accelerators/media_keys_util.h"

namespace ash {

namespace {

constexpr base::TimeDelta kDefaultSeekTime =
    base::Seconds(media_session::mojom::kDefaultSeekTimeSeconds);

bool IsMediaSessionActionEligibleForKeyControl(
    media_session::mojom::MediaSessionAction action) {
  return action == media_session::mojom::MediaSessionAction::kPlay ||
         action == media_session::mojom::MediaSessionAction::kPause ||
         action == media_session::mojom::MediaSessionAction::kPreviousTrack ||
         action == media_session::mojom::MediaSessionAction::kNextTrack;
}

}  // namespace

MediaControllerImpl::MediaControllerImpl() {
  // If media session media key handling is enabled this will setup a connection
  // and bind an observer to the media session service.
  if (base::FeatureList::IsEnabled(media::kHardwareMediaKeyHandling))
    GetMediaSessionController();
}

MediaControllerImpl::~MediaControllerImpl() = default;

// static
void MediaControllerImpl::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kLockScreenMediaControlsEnabled, true);
}

bool MediaControllerImpl::AreLockScreenMediaKeysEnabled() const {
  PrefService* prefs =
      Shell::Get()->session_controller()->GetPrimaryUserPrefService();
  CHECK(prefs);

  return prefs->GetBoolean(prefs::kLockScreenMediaControlsEnabled) &&
         !media_controls_dismissed_;
}

void MediaControllerImpl::SetMediaControlsDismissed(
    bool media_controls_dismissed) {
  media_controls_dismissed_ = media_controls_dismissed;
}

void MediaControllerImpl::AddObserver(MediaCaptureObserver* observer) {
  observers_.AddObserver(observer);
}

void MediaControllerImpl::RemoveObserver(MediaCaptureObserver* observer) {
  observers_.RemoveObserver(observer);
}

void MediaControllerImpl::SetClient(MediaClient* client) {
  client_ = client;

  // When |client_| is changed or encounters an error we should reset the
  // |force_media_client_key_handling_| bit.
  ResetForceMediaClientKeyHandling();
}

void MediaControllerImpl::SetForceMediaClientKeyHandling(bool enabled) {
  force_media_client_key_handling_ = enabled;
}

void MediaControllerImpl::NotifyCaptureState(
    const base::flat_map<AccountId, MediaCaptureState>& capture_states) {
  for (auto& observer : observers_)
    observer.OnMediaCaptureChanged(capture_states);
}

void MediaControllerImpl::NotifyVmMediaNotificationState(bool camera,
                                                         bool mic,
                                                         bool camera_and_mic) {
  for (auto& observer : observers_)
    observer.OnVmMediaNotificationChanged(camera, mic, camera_and_mic);
}

void MediaControllerImpl::HandleMediaPlayPause() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPlayPause);
    client_->HandleMediaPlayPause();
    return;
  }

  // If media session media key handling is enabled. Toggle play pause using the
  // media session service.
  if (ShouldUseMediaSession()) {
    switch (media_session_info_->playback_state) {
      case media_session::mojom::MediaPlaybackState::kPaused:
        GetMediaSessionController()->Resume();
        ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPlay);
        break;
      case media_session::mojom::MediaPlaybackState::kPlaying:
        GetMediaSessionController()->Suspend();
        ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPause);
        break;
    }

    return;
  }

  // If media session does not handle the key then we don't know whether the
  // action will play or pause so we should record a generic "play/pause".
  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPlayPause);

  if (client_)
    client_->HandleMediaPlayPause();
}

void MediaControllerImpl::HandleMediaPlay() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPlay);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaPlay();
    return;
  }

  // If media session media key handling is enabled. Fire play using the media
  // session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->Resume();
    return;
  }

  if (client_)
    client_->HandleMediaPlay();
}

void MediaControllerImpl::HandleMediaPause() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPause);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaPause();
    return;
  }

  // If media session media key handling is enabled. Fire pause using the media
  // session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->Suspend();
    return;
  }

  if (client_)
    client_->HandleMediaPause();
}

void MediaControllerImpl::HandleMediaStop() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kStop);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaStop();
    return;
  }

  // If media session media key handling is enabled. Fire stop using the media
  // session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->Stop();
    return;
  }

  if (client_)
    client_->HandleMediaStop();
}

void MediaControllerImpl::HandleMediaNextTrack() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kNextTrack);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaNextTrack();
    return;
  }

  // If media session media key handling is enabled. Fire next track using the
  // media session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->NextTrack();
    return;
  }

  if (client_)
    client_->HandleMediaNextTrack();
}

void MediaControllerImpl::HandleMediaPrevTrack() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPreviousTrack);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaPrevTrack();
    return;
  }

  // If media session media key handling is enabled. Fire previous track using
  // the media session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->PreviousTrack();
    return;
  }

  if (client_)
    client_->HandleMediaPrevTrack();
}

void MediaControllerImpl::HandleMediaSeekBackward() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kSeekBackward);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaSeekBackward();
    return;
  }

  // If media session media key handling is enabled. Seek backward with
  // kDefaultSeekTime using the media session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->Seek(kDefaultSeekTime * -1);
    return;
  }

  if (client_)
    client_->HandleMediaSeekBackward();
}

void MediaControllerImpl::HandleMediaSeekForward() {
  if (Shell::Get()->session_controller()->IsScreenLocked() &&
      !AreLockScreenMediaKeysEnabled()) {
    return;
  }

  ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kSeekForward);

  // If the |client_| is force handling the keys then we should forward them.
  if (client_ && force_media_client_key_handling_) {
    client_->HandleMediaSeekForward();
    return;
  }

  // If media session media key handling is enabled. Seek forward with
  // kDefaultSeekTime using the media session service.
  if (ShouldUseMediaSession()) {
    GetMediaSessionController()->Seek(kDefaultSeekTime);
    return;
  }

  if (client_)
    client_->HandleMediaSeekForward();
}

void MediaControllerImpl::RequestCaptureState() {
  if (client_)
    client_->RequestCaptureState();
}

void MediaControllerImpl::SuspendMediaSessions() {
  if (client_)
    client_->SuspendMediaSessions();
}

void MediaControllerImpl::MediaSessionInfoChanged(
    media_session::mojom::MediaSessionInfoPtr session_info) {
  media_session_info_ = std::move(session_info);
}

void MediaControllerImpl::MediaSessionActionsChanged(
    const std::vector<media_session::mojom::MediaSessionAction>& actions) {
  supported_media_session_action_ = false;

  for (auto action : actions) {
    if (IsMediaSessionActionEligibleForKeyControl(action)) {
      supported_media_session_action_ = true;
      return;
    }
  }
}

void MediaControllerImpl::SetMediaSessionControllerForTest(
    mojo::Remote<media_session::mojom::MediaController> controller) {
  media_session_controller_remote_ = std::move(controller);
  BindMediaControllerObserver();
}

void MediaControllerImpl::FlushForTesting() {
  if (media_session_controller_remote_)
    media_session_controller_remote_.FlushForTesting();
}

media_session::mojom::MediaController*
MediaControllerImpl::GetMediaSessionController() {
  // NOTE: |media_session_controller_remote_| may be overridden by tests and
  // therefore non-null even if the real Media Session Service is unavailable.
  if (media_session_controller_remote_)
    return media_session_controller_remote_.get();

  // The service may be unavailable in some test environments.
  if (!Shell::HasInstance())
    return nullptr;

  media_session::MediaSessionService* service =
      Shell::Get()->shell_delegate()->GetMediaSessionService();
  if (!service)
    return nullptr;

  mojo::Remote<media_session::mojom::MediaControllerManager>
      controller_manager_remote;
  service->BindMediaControllerManager(
      controller_manager_remote.BindNewPipeAndPassReceiver());
  controller_manager_remote->CreateActiveMediaController(
      media_session_controller_remote_.BindNewPipeAndPassReceiver());

  media_session_controller_remote_.set_disconnect_handler(
      base::BindOnce(&MediaControllerImpl::OnMediaSessionControllerError,
                     base::Unretained(this)));

  BindMediaControllerObserver();

  return media_session_controller_remote_.get();
}

void MediaControllerImpl::OnMediaSessionControllerError() {
  media_session_controller_remote_.reset();
  supported_media_session_action_ = false;
}

void MediaControllerImpl::BindMediaControllerObserver() {
  if (!media_session_controller_remote_.is_bound())
    return;

  media_session_controller_remote_->AddObserver(
      media_controller_observer_receiver_.BindNewPipeAndPassRemote());
}

bool MediaControllerImpl::ShouldUseMediaSession() {
  return base::FeatureList::IsEnabled(media::kHardwareMediaKeyHandling) &&
         GetMediaSessionController() && supported_media_session_action_ &&
         !media_session_info_.is_null();
}

void MediaControllerImpl::ResetForceMediaClientKeyHandling() {
  force_media_client_key_handling_ = false;
}

}  // namespace ash