blob: 1f8897c30502c0d1b858d00ab36bebf2e9bca081 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_SERVICE_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_SERVICE_H_
#include <map>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/media/router/presentation/web_contents_presentation_manager.h"
#include "chrome/browser/ui/global_media_controls/cast_media_notification_provider.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_observer.h"
#include "chrome/browser/ui/global_media_controls/overlay_media_notifications_manager.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/media_message_center/media_notification_controller.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "services/media_session/public/mojom/media_controller.mojom-forward.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace content {
class WebContents;
} // namespace content
namespace media_message_center {
class MediaSessionNotificationItem;
} // namespace media_message_center
class MediaDialogDelegate;
class MediaNotificationContainerImpl;
class MediaNotificationServiceObserver;
class MediaNotificationService
: public KeyedService,
public media_session::mojom::AudioFocusObserver,
public media_message_center::MediaNotificationController,
public MediaNotificationContainerObserver {
public:
explicit MediaNotificationService(Profile* profile);
MediaNotificationService(const MediaNotificationService&) = delete;
MediaNotificationService& operator=(const MediaNotificationService&) = delete;
~MediaNotificationService() override;
void AddObserver(MediaNotificationServiceObserver* observer);
void RemoveObserver(MediaNotificationServiceObserver* observer);
// media_session::mojom::AudioFocusObserver implementation.
void OnFocusGained(
media_session::mojom::AudioFocusRequestStatePtr session) override;
void OnFocusLost(
media_session::mojom::AudioFocusRequestStatePtr session) override;
// media_message_center::MediaNotificationController implementation.
void ShowNotification(const std::string& id) override;
void HideNotification(const std::string& id) override;
void RemoveItem(const std::string& id) override;
scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
void LogMediaSessionActionButtonPressed(
const std::string& id,
media_session::mojom::MediaSessionAction action) override;
// MediaNotificationContainerObserver implementation.
void OnContainerExpanded(bool expanded) override {}
void OnContainerMetadataChanged() override {}
void OnContainerActionsChanged() override {}
void OnContainerClicked(const std::string& id) override;
void OnContainerDismissed(const std::string& id) override;
void OnContainerDestroyed(const std::string& id) override;
void OnContainerDraggedOut(const std::string& id, gfx::Rect bounds) override;
// KeyedService implementation.
void Shutdown() override;
// Called by the |overlay_media_notifications_manager_| when an overlay
// notification is closed.
void OnOverlayNotificationClosed(const std::string& id);
void OnCastNotificationsChanged();
void SetDialogDelegate(MediaDialogDelegate* delegate);
// True if there are active non-frozen media session notifications or active
// cast notifications.
bool HasActiveNotifications() const;
// True if there are active frozen media session notifications.
bool HasFrozenNotifications() const;
// True if there is an open MediaDialogView associated with this service.
bool HasOpenDialog() const;
// Called by a |MediaNotificationService::Session| when it becomes active.
void OnSessionBecameActive(const std::string& id);
// Called by a |MediaNotificationService::Session| when it becomes inactive.
void OnSessionBecameInactive(const std::string& id);
private:
friend class MediaNotificationServiceTest;
friend class MediaToolbarButtonControllerTest;
FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceTest,
HideAfterTimeoutAndActiveAgainOnPlay);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceTest,
SessionIsRemovedImmediatelyWhenATabCloses);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceTest, DismissesMediaSession);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceTest,
HidesInactiveNotifications);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceTest,
HidingNotification_FeatureDisabled);
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GlobalMediaControlsDismissReason {
kUserDismissedNotification = 0,
kInactiveTimeout = 1,
kTabClosed = 2,
kMediaSessionStopped = 3,
kMaxValue = kMediaSessionStopped,
};
class Session
: public content::WebContentsObserver,
public media_session::mojom::MediaControllerObserver,
public media_router::WebContentsPresentationManager::Observer {
public:
Session(MediaNotificationService* owner,
const std::string& id,
std::unique_ptr<media_message_center::MediaSessionNotificationItem>
item,
content::WebContents* web_contents,
mojo::Remote<media_session::mojom::MediaController> controller);
Session(const Session&) = delete;
Session& operator=(const Session&) = delete;
~Session() override;
// content::WebContentsObserver:
void WebContentsDestroyed() override;
// media_session::mojom::MediaControllerObserver:
void MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) override;
void MediaSessionMetadataChanged(
const base::Optional<media_session::MediaMetadata>& metadata) override {
}
void MediaSessionActionsChanged(
const std::vector<media_session::mojom::MediaSessionAction>& actions)
override {}
void MediaSessionChanged(
const base::Optional<base::UnguessableToken>& request_id) override {}
void MediaSessionPositionChanged(
const base::Optional<media_session::MediaPosition>& position) override;
// media_router::WebContentsPresentationManager::Observer:
void OnMediaRoutesChanged(
const std::vector<media_router::MediaRoute>& routes) override;
media_message_center::MediaSessionNotificationItem* item() {
return item_.get();
}
// Called when a new MediaController is given to the item. We need to
// observe the same session as our underlying item.
void SetController(
mojo::Remote<media_session::mojom::MediaController> controller);
// Sets the reason why this session was dismissed/removed. Can only be
// called if the value has not already been set.
void set_dismiss_reason(GlobalMediaControlsDismissReason reason);
// Called when a session is interacted with (to reset |inactive_timer_|).
void OnSessionInteractedWith();
// Called when the notification associated with this session is pulled out
// into an overlay or it's overlay is closed.
void OnSessionOverlayStateChanged(bool is_in_overlay);
bool IsPlaying();
private:
static void RecordDismissReason(GlobalMediaControlsDismissReason reason);
void StartInactiveTimer();
void OnInactiveTimerFired();
void RecordInteractionDelayAfterPause();
void MarkActiveIfNecessary();
MediaNotificationService* owner_;
const std::string id_;
std::unique_ptr<media_message_center::MediaSessionNotificationItem> item_;
// Used to stop/hide a paused session after a period of inactivity.
base::OneShotTimer inactive_timer_;
base::TimeTicks last_interaction_time_ = base::TimeTicks::Now();
// The reason why this session was dismissed/removed.
base::Optional<GlobalMediaControlsDismissReason> dismiss_reason_;
// True if the session's playback state is "playing".
bool is_playing_ = false;
// True if we're currently marked inactive.
bool is_marked_inactive_ = false;
// True if we're in an overlay notification.
bool is_in_overlay_ = false;
// Used to receive updates to the Media Session playback state.
mojo::Receiver<media_session::mojom::MediaControllerObserver>
observer_receiver_{this};
base::WeakPtr<media_router::WebContentsPresentationManager>
presentation_manager_;
};
void OnItemUnfrozen(const std::string& id);
void OnReceivedAudioFocusRequests(
std::vector<media_session::mojom::AudioFocusRequestStatePtr> sessions);
base::WeakPtr<media_message_center::MediaNotificationItem>
GetNotificationItem(const std::string& id);
MediaDialogDelegate* dialog_delegate_ = nullptr;
OverlayMediaNotificationsManager overlay_media_notifications_manager_;
// Used to track whether there are any active controllable sessions. If not,
// then there's nothing to show in the dialog and we can hide the toolbar
// icon. Contains sessions from both Media Session API and Cast.
std::unordered_set<std::string> active_controllable_session_ids_;
// Tracks the sessions that are currently frozen. If there are only frozen
// sessions, we will disable the toolbar icon and wait to hide it.
std::unordered_set<std::string> frozen_session_ids_;
// Tracks the sessions that are currently dragged out of the dialog. These
// should not be shown in the dialog and will be ignored for showing the
// toolbar icon.
std::unordered_set<std::string> dragged_out_session_ids_;
// Tracks the sessions that are currently inactive. Sessions become inactive
// after a period of time of being paused with no user interaction. Inactive
// sessions are hidden from the dialog until the user interacts with them
// again (e.g. by playing the session).
std::unordered_set<std::string> inactive_session_ids_;
// Stores a Session for each media session keyed by its |request_id| in string
// format.
std::map<std::string, Session> sessions_;
// A map of all containers we're currently observing.
std::map<std::string, MediaNotificationContainerImpl*> observed_containers_;
// Connections with the media session service to listen for audio focus
// updates and control media sessions.
mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote_;
mojo::Remote<media_session::mojom::MediaControllerManager>
controller_manager_remote_;
mojo::Receiver<media_session::mojom::AudioFocusObserver>
audio_focus_observer_receiver_{this};
std::unique_ptr<CastMediaNotificationProvider> cast_notification_provider_;
base::ObserverList<MediaNotificationServiceObserver> observers_;
// Tracks the number of times we have recorded an action for a specific
// source. We use this to cap the number of UKM recordings per site.
std::map<ukm::SourceId, int> actions_recorded_to_ukm_;
base::WeakPtrFactory<MediaNotificationService> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_SERVICE_H_