blob: b99ba485b7127473057f70568061d8eeea40d364 [file] [log] [blame]
// Copyright 2018 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_MEDIA_ROUTER_MEDIA_ROUTER_UI_H_
#define CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_H_
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/media_router/cast_dialog_controller.h"
#include "chrome/browser/ui/media_router/cast_dialog_model.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/media_router_file_dialog.h"
#include "chrome/browser/ui/media_router/media_router_ui_helper.h"
#include "chrome/browser/ui/media_router/media_sink_with_cast_modes.h"
#include "chrome/browser/ui/media_router/query_result_manager.h"
#include "chrome/browser/ui/webui/media_router/web_contents_display_observer.h"
#include "components/media_router/browser/issues_observer.h"
#include "components/media_router/browser/media_router_dialog_controller.h"
#include "components/media_router/browser/presentation/start_presentation_context.h"
#include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
#include "components/media_router/common/issue.h"
#include "components/media_router/common/media_source.h"
#include "url/origin.h"
class GURL;
namespace content {
struct PresentationRequest;
class WebContents;
} // namespace content
namespace U_ICU_NAMESPACE {
class Collator;
}
namespace media_router {
class MediaRoute;
class MediaRouter;
class MediaRoutesObserver;
class MediaSink;
class RouteRequestResult;
// Functions as an intermediary between MediaRouter and Views Cast dialog.
class MediaRouterUI
: public CastDialogController,
public QueryResultManager::Observer,
public WebContentsPresentationManager::Observer,
public MediaRouterFileDialog::MediaRouterFileDialogDelegate {
public:
explicit MediaRouterUI(content::WebContents* initiator);
MediaRouterUI(const MediaRouterUI&) = delete;
MediaRouterUI& operator=(const MediaRouterUI&) = delete;
~MediaRouterUI() override;
// CastDialogController:
void AddObserver(CastDialogController::Observer* observer) override;
void RemoveObserver(CastDialogController::Observer* observer) override;
void StartCasting(const std::string& sink_id,
MediaCastMode cast_mode) override;
void StopCasting(const std::string& route_id) override;
void ChooseLocalFile(
base::OnceCallback<void(const ui::SelectedFileInfo*)> callback) override;
void ClearIssue(const Issue::Id& issue_id) override;
// Initializes internal state (e.g. starts listening for MediaSinks) for
// targeting the default MediaSource (if any) of |initiator_|. The contents of
// the UI will change as the default MediaSource changes. If there is a
// default MediaSource, then PRESENTATION MediaCastMode will be added to
// |cast_modes_|. Init* methods can only be called once.
void InitWithDefaultMediaSource();
// Initializes mirroring sources of the tab in addition to what is done by
// |InitWithDefaultMediaSource()|.
void InitWithDefaultMediaSourceAndMirroring();
// Initializes internal state targeting the presentation specified in
// |context|. This is different from InitWithDefaultMediaSource*() in that it
// does not listen for default media source changes, as the UI is fixed to the
// source in |context|. Init* methods can only be called once.
// |context|: Context object for the PresentationRequest. This instance will
// take ownership of it. Must not be null.
void InitWithStartPresentationContext(
std::unique_ptr<StartPresentationContext> context);
// Initializes mirroring sources of the tab in addition to what is done by
// |InitWithStartPresentationContext()|.
void InitWithStartPresentationContextAndMirroring(
std::unique_ptr<StartPresentationContext> context);
// Requests a route be created from the source mapped to
// |cast_mode|, to the sink given by |sink_id|.
// Returns true if a route request is successfully submitted.
// |OnRouteResponseReceived()| will be invoked when the route request
// completes.
virtual bool CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode);
// Calls MediaRouter to terminate the given route.
void TerminateRoute(const MediaRoute::Id& route_id);
// Returns a subset of |sinks_| that should be listed in the dialog. This
// excludes the wired display that the initiator WebContents is on.
// Also filters cloud sinks in incognito windows.
std::vector<MediaSinkWithCastModes> GetEnabledSinks() const;
// Returns a PresentationRequest source name that can be shown in the dialog.
std::u16string GetPresentationRequestSourceName() const;
// Calls MediaRouter to add the given issue.
void AddIssue(const IssueInfo& issue);
// Calls MediaRouter to remove the given issue.
void RemoveIssue(const Issue::Id& issue_id);
// Opens a file picker for when the user selected local file casting.
void OpenFileDialog();
// Uses LoggerImpl to log current available sinks.
void LogMediaSinkStatus();
const std::vector<MediaRoute>& routes() const { return routes_; }
content::WebContents* initiator() const { return initiator_; }
void SimulateDocumentAvailableForTest();
#if defined(OS_MAC)
void set_screen_capture_allowed_for_testing(bool allowed) {
screen_capture_allowed_for_testing_ = allowed;
}
#endif
private:
friend class MediaRouterViewsUITest;
friend class MediaRouterCastUiForTest;
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, SetDialogHeader);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest,
UpdateSinksWhenDialogMovesToAnotherDisplay);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, NotifyObserver);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, SinkFriendlyName);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, ConnectingState);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, DisconnectingState);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, AddAndRemoveIssue);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, ShowDomainForHangouts);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUIIncognitoTest,
HidesCloudSinksForIncognito);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest,
RouteCreationTimeoutForPresentation);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest,
DesktopMirroringFailsWhenDisallowedOnMac);
FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest,
RouteCreationLocalFileModeInTab);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
UIMediaRoutesObserverAssignsCurrentCastModes);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
UIMediaRoutesObserverSkipsUnavailableCastModes);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
UpdateSinksWhenDialogMovesToAnotherDisplay);
class WebContentsFullscreenOnLoadedObserver;
struct RouteRequest {
public:
explicit RouteRequest(const MediaSink::Id& sink_id);
~RouteRequest();
int id;
MediaSink::Id sink_id;
};
// This class calls to refresh the UI when the highest priority issue is
// updated.
class UiIssuesObserver : public IssuesObserver {
public:
UiIssuesObserver(IssueManager* issue_manager, MediaRouterUI* ui);
UiIssuesObserver(const UiIssuesObserver&) = delete;
UiIssuesObserver& operator=(const UiIssuesObserver&) = delete;
~UiIssuesObserver() override;
// IssuesObserver:
void OnIssue(const Issue& issue) override;
void OnIssuesCleared() override;
private:
// Reference back to the owning MediaRouterUI instance.
const raw_ptr<MediaRouterUI> ui_;
};
class UIMediaRoutesObserver : public MediaRoutesObserver {
public:
using RoutesUpdatedCallback =
base::RepeatingCallback<void(const std::vector<MediaRoute>&)>;
UIMediaRoutesObserver(MediaRouter* router,
const RoutesUpdatedCallback& callback);
UIMediaRoutesObserver(const UIMediaRoutesObserver&) = delete;
UIMediaRoutesObserver& operator=(const UIMediaRoutesObserver&) = delete;
~UIMediaRoutesObserver() override;
// MediaRoutesObserver:
void OnRoutesUpdated(const std::vector<MediaRoute>& routes) override;
private:
// Callback to the owning MediaRouterUI instance.
RoutesUpdatedCallback callback_;
};
std::vector<MediaSource> GetSourcesForCastMode(MediaCastMode cast_mode) const;
// Closes the dialog after receiving a route response when using
// |start_presentation_context_|. This prevents the dialog from trying to use
// the same presentation request again.
virtual void HandleCreateSessionRequestRouteResponse(
const RouteRequestResult&);
// Initializes the dialog with mirroring sources derived from |initiator_|.
virtual void InitCommon();
void InitMirroring();
// WebContentsPresentationManager::Observer
void OnDefaultPresentationChanged(
const content::PresentationRequest* presentation_request) override;
void OnDefaultPresentationRemoved();
// Called to update the dialog with the current list of of enabled sinks.
void UpdateSinks();
// Populates common route-related parameters for calls to MediaRouter.
absl::optional<RouteParameters> GetRouteParameters(
const MediaSink::Id& sink_id,
MediaCastMode cast_mode);
// Returns the default PresentationRequest's frame origin if there is one.
// Otherwise returns an opaque origin.
url::Origin GetFrameOrigin() const;
// Creates and sends an issue if route creation timed out.
void SendIssueForRouteTimeout(
MediaCastMode cast_mode,
const MediaSink::Id& sink_id,
const std::u16string& presentation_request_source_name);
// Creates and sends an issue if casting fails due to lack of screen
// permissions.
#if defined(OS_MAC)
void SendIssueForScreenPermission(const MediaSink::Id& sink_id);
#endif
// Creates and sends an issue if casting fails for any reason other than
// those above.
void SendIssueForUnableToCast(MediaCastMode cast_mode,
const MediaSink::Id& sink_id);
// Creates and sends an issue for notifying the user that the tab audio cannot
// be mirrored from their device.
void SendIssueForTabAudioNotSupported(const MediaSink::Id& sink_id);
// Returns the IssueManager associated with |router_|.
IssueManager* GetIssueManager();
// Instantiates and initializes the issues observer.
void StartObservingIssues();
void OnIssue(const Issue& issue);
void OnIssueCleared();
// Called by |routes_observer_| when the set of active routes has changed.
void OnRoutesUpdated(const std::vector<MediaRoute>& routes);
// QueryResultManager::Observer:
void OnResultsUpdated(
const std::vector<MediaSinkWithCastModes>& sinks) override;
// Callback passed to MediaRouter to receive response to route creation
// requests.
virtual void OnRouteResponseReceived(
int route_request_id,
const MediaSink::Id& sink_id,
MediaCastMode cast_mode,
const std::u16string& presentation_request_source_name,
const RouteRequestResult& result);
// Update the header text in the dialog model and notify observers.
void UpdateModelHeader();
UIMediaSink ConvertToUISink(const MediaSinkWithCastModes& sink,
const MediaRoute* route,
const absl::optional<Issue>& issue);
// MediaRouterFileDialogDelegate:
void FileDialogFileSelected(const ui::SelectedFileInfo& file_info) override;
void FileDialogSelectionFailed(const IssueInfo& issue) override;
void FileDialogSelectionCanceled() override;
// Populates route-related parameters for CreateRoute() when doing file
// casting.
absl::optional<RouteParameters> GetLocalFileRouteParameters(
const MediaSink::Id& sink_id,
const GURL& file_url,
content::WebContents* tab_contents);
// If the current URL for |web_contents| is |file_url|, requests the first
// video in it to be shown fullscreen.
void FullScreenFirstVideoElement(const GURL& file_url,
content::WebContents* web_contents,
const RouteRequestResult& result);
// Sends a request to the file dialog to log UMA stats for the file that was
// cast if the result is successful.
void MaybeReportFileInformation(const RouteRequestResult& result);
// Opens the URL in a tab, returns the tab it was opened in.
content::WebContents* OpenTabWithUrl(const GURL& url);
// Returns the MediaRouter for this instance's BrowserContext.
virtual MediaRouter* GetMediaRouter() const;
// Retrieves the browser associated with this UI.
Browser* GetBrowser();
const absl::optional<RouteRequest> current_route_request() const {
return current_route_request_;
}
StartPresentationContext* start_presentation_context() const {
return start_presentation_context_.get();
}
QueryResultManager* query_result_manager() const {
return query_result_manager_.get();
}
void set_media_router_file_dialog_for_test(
std::unique_ptr<MediaRouterFileDialog> file_dialog) {
media_router_file_dialog_ = std::move(file_dialog);
}
void set_start_presentation_context_for_test(
std::unique_ptr<StartPresentationContext> start_presentation_context) {
start_presentation_context_ = std::move(start_presentation_context);
}
raw_ptr<content::WebContentsObserver> web_contents_observer_for_test_ =
nullptr;
// This value is set whenever there is an outstanding issue.
absl::optional<Issue> issue_;
// Contains up-to-date data to show in the dialog.
CastDialogModel model_;
// This value is set when the user opens a file picker, and used when a file
// is selected and casting starts.
absl::optional<MediaSink::Id> local_file_sink_id_;
// This value is set when the UI requests a route to be terminated, and gets
// reset when the route is removed.
absl::optional<MediaRoute::Id> terminating_route_id_;
// Observers for dialog model updates.
// TODO(takumif): CastDialogModel should manage the observers.
base::ObserverList<CastDialogController::Observer>::Unchecked observers_;
base::OnceCallback<void(const ui::SelectedFileInfo*)>
file_selection_callback_;
// This is non-null while this instance is registered to receive
// updates from them.
std::unique_ptr<MediaRoutesObserver> routes_observer_;
// This contains a value only when tracking a pending route request.
absl::optional<RouteRequest> current_route_request_;
// Used for locale-aware sorting of sinks by name. Set during
// InitCommon() using the current locale.
std::unique_ptr<icu::Collator> collator_;
std::vector<MediaSinkWithCastModes> sinks_;
std::vector<MediaRoute> routes_;
// Monitors and reports sink availability.
std::unique_ptr<QueryResultManager> query_result_manager_;
// If set, then the result of the next presentation route request will
// be handled by this object.
std::unique_ptr<StartPresentationContext> start_presentation_context_;
// Set to the presentation request corresponding to the presentation cast
// mode, if supported. Otherwise set to nullopt.
absl::optional<content::PresentationRequest> presentation_request_;
// |presentation_manager_| notifies |this| whenever there is an update to the
// default PresentationRequest or MediaRoutes associated with |initiator_|.
base::WeakPtr<WebContentsPresentationManager> presentation_manager_;
// WebContents for the tab for which the Cast dialog is shown.
const raw_ptr<content::WebContents> initiator_;
// The dialog that handles opening the file dialog and validating and
// returning the results.
std::unique_ptr<MediaRouterFileDialog> media_router_file_dialog_;
std::unique_ptr<IssuesObserver> issues_observer_;
// Keeps track of which display the initiator WebContents is on. This is used
// to make sure we don't show a wired display presentation over the
// controlling window.
std::unique_ptr<WebContentsDisplayObserver> display_observer_;
#if defined(OS_MAC)
absl::optional<bool> screen_capture_allowed_for_testing_;
#endif
raw_ptr<LoggerImpl> logger_;
// NOTE: Weak pointers must be invalidated before all other member variables.
// Therefore |weak_factory_| must be placed at the end.
base::WeakPtrFactory<MediaRouterUI> weak_factory_{this};
};
} // namespace media_router
#endif // CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_H_