From 1dd545e06b6bff83b083948f1d72e941fb87c463 Mon Sep 17 00:00:00 2001 From: Achuith Bhandarkar Date: Thu, 20 Aug 2015 20:19:42 -0700 Subject: [PATCH] Make the ChromeOS chromecast system tray integration use a private API. This is a combination of two code review's which have circular dependencies: - crrev.com/1291113002 - crrev.com/1291703010 Review URL: https://codereview.chromium.org/1288073005 . Cr-Commit-Position: refs/heads/master@{#344675} --- ash/cast_config_delegate.h | 29 ++- ash/system/cast/tray_cast.cc | 243 ++++++++---------- ash/system/cast/tray_cast.h | 9 +- .../cast_devices_private_api.cc | 103 ++++++++ .../cast_devices_private_api.h | 71 +++++ .../ui/ash/cast_config_delegate_chromeos.cc | 285 +++++++++------------ .../browser/ui/ash/cast_config_delegate_chromeos.h | 3 +- chrome/chrome_browser_extensions.gypi | 2 + chrome/common/extensions/api/_api_features.json | 4 + .../common/extensions/api/cast_devices_private.idl | 63 +++++ chrome/common/extensions/api/schemas.gypi | 1 + .../browser/extension_event_histogram_value.h | 3 + .../browser/extension_function_histogram_value.h | 1 + extensions/common/api/_permission_features.json | 1 + tools/metrics/histograms/histograms.xml | 4 + 15 files changed, 504 insertions(+), 318 deletions(-) create mode 100644 chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.cc create mode 100644 chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h rewrite chrome/browser/ui/ash/cast_config_delegate_chromeos.cc (65%) create mode 100644 chrome/common/extensions/api/cast_devices_private.idl diff --git a/ash/cast_config_delegate.h b/ash/cast_config_delegate.h index f38a05c5f3f6..8fd73889a53e 100644 --- a/ash/cast_config_delegate.h +++ b/ash/cast_config_delegate.h @@ -5,11 +5,12 @@ #ifndef ASH_CAST_CONFIG_DELEGATE_H_ #define ASH_CAST_CONFIG_DELEGATE_H_ -#include #include +#include #include "ash/ash_export.h" #include "base/callback.h" +#include "base/callback_list.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" @@ -37,7 +38,11 @@ class CastConfigDelegate { EXTENSION = -1, DESKTOP = -2, DISCOVERED_ACTIVITY = -3, - EXTERNAL_EXTENSION_CLIENT = -4 + EXTERNAL_EXTENSION_CLIENT = -4, + + // Not in the extension. Used when the extension does not give us a tabId + // (ie, the cast is running from another device). + UNKNOWN = -5 }; Activity(); @@ -45,8 +50,6 @@ class CastConfigDelegate { std::string id; base::string16 title; - std::string activity_type; - bool allow_stop = false; // The id for the tab we are casting. Could be one of the TabId values, // or a value >= 0 that represents that tab index of the tab we are @@ -64,26 +67,30 @@ class CastConfigDelegate { }; // The key is the receiver id. - using ReceiversAndActivites = std::map; + using ReceiversAndActivites = std::vector; using ReceiversAndActivitesCallback = base::Callback; + using DeviceUpdateSubscription = scoped_ptr< + base::CallbackList::Subscription>; virtual ~CastConfigDelegate() {} // Returns true if cast extension is installed. virtual bool HasCastExtension() const = 0; - // Fetches the current set of receivers and their possible activities. This - // method will lookup the current chromecast extension and query its current - // state. The |callback| will be invoked when the receiver/activity data is - // available. - virtual void GetReceiversAndActivities( + // Adds a listener that will get invoked whenever the receivers or their + // associated activites have changed. + virtual DeviceUpdateSubscription RegisterDeviceUpdateObserver( const ReceiversAndActivitesCallback& callback) = 0; + // Request fresh data from the backend. When the data is available, all + // registered observers will get called. + virtual void RequestDeviceRefresh() = 0; + // Cast to a receiver specified by |receiver_id|. virtual void CastToReceiver(const std::string& receiver_id) = 0; - // Stop an ongoing cast. + // Stop an ongoing cast (this should be a user initiated stop). virtual void StopCasting() = 0; // Opens Options page for cast. diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc index 6128b430b717..5d9a60770eb9 100644 --- a/ash/system/cast/tray_cast.cc +++ b/ash/system/cast/tray_cast.cc @@ -36,6 +36,13 @@ namespace ash { namespace { const int kStopButtonRightPadding = 18; + +// Returns the active CastConfigDelegate instance. +ash::CastConfigDelegate* GetCastConfigDelegate() { + return ash::Shell::GetInstance() + ->system_tray_delegate() + ->GetCastConfigDelegate(); +} } // namespace namespace tray { @@ -47,83 +54,44 @@ namespace tray { class CastSelectDefaultView : public TrayItemMore { public: CastSelectDefaultView(SystemTrayItem* owner, - CastConfigDelegate* cast_config_delegate, bool show_more); ~CastSelectDefaultView() override; - // Updates the label based on the current set of receivers (if there are or - // are not any available receivers). - void UpdateLabel(); - private: - void UpdateLabelCallback( - const CastConfigDelegate::ReceiversAndActivites& receivers_activities); - - CastConfigDelegate* cast_config_delegate_; - base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(CastSelectDefaultView); }; -CastSelectDefaultView::CastSelectDefaultView( - SystemTrayItem* owner, - CastConfigDelegate* cast_config_delegate, - bool show_more) - : TrayItemMore(owner, show_more), - cast_config_delegate_(cast_config_delegate), - weak_ptr_factory_(this) { +CastSelectDefaultView::CastSelectDefaultView(SystemTrayItem* owner, + bool show_more) + : TrayItemMore(owner, show_more) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - SetImage(rb.GetImageNamed(IDR_AURA_UBER_TRAY_CAST).ToImageSkia()); - - // We first set a default label before we actually know what the label will - // be, because it could take awhile before UpdateLabel() actually applies - // the correct label. - SetLabel(rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE)); - UpdateLabel(); -} - -CastSelectDefaultView::~CastSelectDefaultView() { -} -void CastSelectDefaultView::UpdateLabelCallback( - const CastConfigDelegate::ReceiversAndActivites& receivers_activities) { - // The label needs to reflect if there are no cast receivers - const base::string16 label = - ui::ResourceBundle::GetSharedInstance().GetLocalizedString( - receivers_activities.empty() ? IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE - : IDS_ASH_STATUS_TRAY_CAST_DESKTOP); + // Update the image and label. + SetImage(rb.GetImageNamed(IDR_AURA_UBER_TRAY_CAST).ToImageSkia()); + base::string16 label = + rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_DESKTOP); SetLabel(label); SetAccessibleName(label); - SetVisible(true); } -void CastSelectDefaultView::UpdateLabel() { - if (cast_config_delegate_ == nullptr || - cast_config_delegate_->HasCastExtension() == false) - return; - - cast_config_delegate_->GetReceiversAndActivities( - base::Bind(&CastSelectDefaultView::UpdateLabelCallback, - weak_ptr_factory_.GetWeakPtr())); -} +CastSelectDefaultView::~CastSelectDefaultView() {} // This view is displayed when the screen is actively being casted; it allows // the user to easily stop casting. It fully replaces the // |CastSelectDefaultView| view inside of the |CastDuplexView|. class CastCastView : public views::View, public views::ButtonListener { public: - explicit CastCastView(CastConfigDelegate* cast_config_delegate); + CastCastView(); ~CastCastView() override; void StopCasting(); // Updates the label for the stop view to include information about the // current device that is being casted. - void UpdateLabel(); - - private: - void UpdateLabelCallback( + void UpdateLabel( const CastConfigDelegate::ReceiversAndActivites& receivers_activities); + private: // Overridden from views::View. int GetHeightForWidth(int width) const override; void Layout() override; @@ -131,7 +99,6 @@ class CastCastView : public views::View, public views::ButtonListener { // Overridden from views::ButtonListener. void ButtonPressed(views::Button* sender, const ui::Event& event) override; - CastConfigDelegate* cast_config_delegate_; views::ImageView* icon_; views::Label* label_; TrayPopupLabelButton* stop_button_; @@ -140,8 +107,7 @@ class CastCastView : public views::View, public views::ButtonListener { DISALLOW_COPY_AND_ASSIGN(CastCastView); }; -CastCastView::CastCastView(CastConfigDelegate* cast_config_delegate) - : cast_config_delegate_(cast_config_delegate), weak_ptr_factory_(this) { +CastCastView::CastCastView() : weak_ptr_factory_(this) { // We will initialize the primary tray view which shows a stop button here. set_background(views::Background::CreateSolidBackground(kBackgroundColor)); @@ -170,8 +136,6 @@ CastCastView::CastCastView(CastConfigDelegate* cast_config_delegate) IDS_ASH_STATUS_TRAY_CAST_STOP); stop_button_ = new TrayPopupLabelButton(this, stop_button_text); AddChildView(stop_button_); - - UpdateLabel(); } CastCastView::~CastCastView() { @@ -209,25 +173,16 @@ void CastCastView::Layout() { } void CastCastView::StopCasting() { - cast_config_delegate_->StopCasting(); + GetCastConfigDelegate()->StopCasting(); Shell::GetInstance()->metrics()->RecordUserMetricsAction( ash::UMA_STATUS_AREA_CAST_STOP_CAST); } -void CastCastView::UpdateLabel() { - if (cast_config_delegate_ == nullptr || - cast_config_delegate_->HasCastExtension() == false) - return; - - cast_config_delegate_->GetReceiversAndActivities(base::Bind( - &CastCastView::UpdateLabelCallback, weak_ptr_factory_.GetWeakPtr())); -} - -void CastCastView::UpdateLabelCallback( +void CastCastView::UpdateLabel( const CastConfigDelegate::ReceiversAndActivites& receivers_activities) { for (auto& i : receivers_activities) { - const CastConfigDelegate::Receiver receiver = i.second.receiver; - const CastConfigDelegate::Activity activity = i.second.activity; + const CastConfigDelegate::Receiver& receiver = i.receiver; + const CastConfigDelegate::Activity& activity = i.activity; if (!activity.id.empty()) { // We want to display different labels inside of the title depending on // what we are actually casting - either the desktop, a tab, or a fallback @@ -261,9 +216,10 @@ void CastCastView::ButtonPressed(views::Button* sender, // is active. class CastDuplexView : public views::View { public: - CastDuplexView(SystemTrayItem* owner, - CastConfigDelegate* config_delegate, - bool show_more); + CastDuplexView( + SystemTrayItem* owner, + bool show_more, + const CastConfigDelegate::ReceiversAndActivites& receivers_activities); ~CastDuplexView() override; // Activate either the casting or select view. @@ -288,11 +244,13 @@ class CastDuplexView : public views::View { DISALLOW_COPY_AND_ASSIGN(CastDuplexView); }; -CastDuplexView::CastDuplexView(SystemTrayItem* owner, - CastConfigDelegate* config_delegate, - bool show_more) { - select_view_ = new CastSelectDefaultView(owner, config_delegate, show_more); - cast_view_ = new CastCastView(config_delegate); +CastDuplexView::CastDuplexView( + SystemTrayItem* owner, + bool show_more, + const CastConfigDelegate::ReceiversAndActivites& receivers_activities) { + select_view_ = new CastSelectDefaultView(owner, show_more); + cast_view_ = new CastCastView(); + cast_view_->UpdateLabel(receivers_activities); SetLayoutManager(new views::FillLayout()); ActivateSelectView(); @@ -390,21 +348,22 @@ void CastTrayView::UpdateAlignment(ShelfAlignment alignment) { class CastDetailedView : public TrayDetailsView, public ViewClickListener { public: CastDetailedView(SystemTrayItem* owner, - CastConfigDelegate* cast_config_delegate, - user::LoginStatus login); + user::LoginStatus login, + const CastConfigDelegate::ReceiversAndActivites& + receivers_and_activities); ~CastDetailedView() override; // Makes the detail view think the view associated with the given receiver_id // was clicked. This will start a cast. void SimulateViewClickedForTest(const std::string& receiver_id); + // Updates the list of available receivers. + void UpdateReceiverList(const CastConfigDelegate::ReceiversAndActivites& + new_receivers_and_activities); + private: void CreateItems(); - void UpdateReceiverList(); - void UpdateReceiverListCallback( - const CastConfigDelegate::ReceiversAndActivites& - new_receivers_and_activities); void UpdateReceiverListFromCachedData(); views::View* AddToReceiverList( const CastConfigDelegate::ReceiverAndActivity& receiverActivity); @@ -415,26 +374,25 @@ class CastDetailedView : public TrayDetailsView, public ViewClickListener { // Overridden from ViewClickListener. void OnViewClicked(views::View* sender) override; - CastConfigDelegate* cast_config_delegate_; user::LoginStatus login_; views::View* options_ = nullptr; - CastConfigDelegate::ReceiversAndActivites receivers_and_activities_; - // A mapping from the view pointer to the associated activity id + // A mapping from the receiver id to the receiver/activity data. + std::map + receivers_and_activities_; + // A mapping from the view pointer to the associated activity id. std::map receiver_activity_map_; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(CastDetailedView); }; -CastDetailedView::CastDetailedView(SystemTrayItem* owner, - CastConfigDelegate* cast_config_delegate, - user::LoginStatus login) - : TrayDetailsView(owner), - cast_config_delegate_(cast_config_delegate), - login_(login), - weak_ptr_factory_(this) { +CastDetailedView::CastDetailedView( + SystemTrayItem* owner, + user::LoginStatus login, + const CastConfigDelegate::ReceiversAndActivites& receivers_and_activities) + : TrayDetailsView(owner), login_(login), weak_ptr_factory_(this) { CreateItems(); - UpdateReceiverList(); + UpdateReceiverList(receivers_and_activities); } CastDetailedView::~CastDetailedView() { @@ -456,25 +414,29 @@ void CastDetailedView::CreateItems() { AppendHeaderEntry(); } -void CastDetailedView::UpdateReceiverList() { - cast_config_delegate_->GetReceiversAndActivities( - base::Bind(&CastDetailedView::UpdateReceiverListCallback, - weak_ptr_factory_.GetWeakPtr())); -} - -void CastDetailedView::UpdateReceiverListCallback( +void CastDetailedView::UpdateReceiverList( const CastConfigDelegate::ReceiversAndActivites& new_receivers_and_activities) { // Add/update existing. for (auto i = new_receivers_and_activities.begin(); i != new_receivers_and_activities.end(); ++i) { - receivers_and_activities_[i->first] = i->second; + receivers_and_activities_[i->receiver.id] = *i; } - // Remove non-existent. - for (auto i = receivers_and_activities_.begin(); - i != receivers_and_activities_.end(); ++i) { - if (new_receivers_and_activities.count(i->first) == 0) - receivers_and_activities_.erase(i->first); + + // Remove non-existent receivers. Removing an element invalidates all existing + // iterators. + auto i = receivers_and_activities_.begin(); + while (i != receivers_and_activities_.end()) { + bool has_receiver = false; + for (auto receiver : new_receivers_and_activities) { + if (i->first == receiver.receiver.id) + has_receiver = true; + } + + if (has_receiver) + ++i; + else + i = receivers_and_activities_.erase(i); } // Update UI. @@ -538,15 +500,17 @@ void CastDetailedView::AppendHeaderEntry() { } void CastDetailedView::OnViewClicked(views::View* sender) { + ash::CastConfigDelegate* cast_config_delegate = GetCastConfigDelegate(); + if (sender == footer()->content()) { TransitionToDefaultView(); } else if (sender == options_) { - cast_config_delegate_->LaunchCastOptions(); + cast_config_delegate->LaunchCastOptions(); } else { // Find the receiver we are going to cast to auto it = receiver_activity_map_.find(sender); if (it != receiver_activity_map_.end()) { - cast_config_delegate_->CastToReceiver(it->second); + cast_config_delegate->CastToReceiver(it->second); Shell::GetInstance()->metrics()->RecordUserMetricsAction( ash::UMA_STATUS_AREA_DETAILED_CAST_VIEW_LAUNCH_CAST); } @@ -557,9 +521,6 @@ void CastDetailedView::OnViewClicked(views::View* sender) { TrayCast::TrayCast(SystemTray* system_tray) : SystemTrayItem(system_tray), - cast_config_delegate_(ash::Shell::GetInstance() - ->system_tray_delegate() - ->GetCastConfigDelegate()), weak_ptr_factory_(this) { Shell::GetInstance()->AddShellObserver(this); } @@ -591,8 +552,28 @@ views::View* TrayCast::CreateTrayView(user::LoginStatus status) { views::View* TrayCast::CreateDefaultView(user::LoginStatus status) { CHECK(default_ == nullptr); - default_ = new tray::CastDuplexView(this, cast_config_delegate_, - status != user::LOGGED_IN_LOCKED); + if (HasCastExtension()) { + ash::CastConfigDelegate* cast_config_delegate = GetCastConfigDelegate(); + + // We add the cast listener here instead of in the ctor for two reasons: + // - The ctor gets called too early in the initialization cycle (at least + // for the tests); the correct profile hasn't been setup yet. + // - The listener is only added if there is a cast extension. If the call + // below were in the ctor, then the cast tray item would not appear if the + // user installed the extension in an existing session. + if (!device_update_subscription_) { + device_update_subscription_ = + cast_config_delegate->RegisterDeviceUpdateObserver(base::Bind( + &TrayCast::OnReceiversUpdated, weak_ptr_factory_.GetWeakPtr())); + } + + // The extension updates its view model whenever the popup is opened, so we + // probably should as well. + cast_config_delegate->RequestDeviceRefresh(); + } + + default_ = new tray::CastDuplexView(this, status != user::LOGGED_IN_LOCKED, + receivers_and_activities_); default_->set_id(TRAY_VIEW); default_->select_view()->set_id(SELECT_VIEW); default_->cast_view()->set_id(CAST_VIEW); @@ -605,7 +586,8 @@ views::View* TrayCast::CreateDetailedView(user::LoginStatus status) { Shell::GetInstance()->metrics()->RecordUserMetricsAction( ash::UMA_STATUS_AREA_DETAILED_CAST_VIEW); CHECK(detailed_ == nullptr); - detailed_ = new tray::CastDetailedView(this, cast_config_delegate_, status); + detailed_ = + new tray::CastDetailedView(this, status, receivers_and_activities_); return detailed_; } @@ -622,34 +604,31 @@ void TrayCast::DestroyDetailedView() { } bool TrayCast::HasCastExtension() { - return cast_config_delegate_ != nullptr && - cast_config_delegate_->HasCastExtension(); + ash::CastConfigDelegate* cast_config_delegate = GetCastConfigDelegate(); + return cast_config_delegate != nullptr && + cast_config_delegate->HasCastExtension(); } -void TrayCast::UpdateCachedReceiverState( +void TrayCast::OnReceiversUpdated( const CastConfigDelegate::ReceiversAndActivites& receivers_activities) { - has_cast_receivers_ = !receivers_activities.empty(); - if (default_) - default_->SetVisible(has_cast_receivers_); + receivers_and_activities_ = receivers_activities; + + if (default_) { + bool has_receivers = !receivers_and_activities_.empty(); + default_->SetVisible(has_receivers); + default_->cast_view()->UpdateLabel(receivers_and_activities_); + } + if (detailed_) + detailed_->UpdateReceiverList(receivers_and_activities_); } void TrayCast::UpdatePrimaryView() { if (HasCastExtension()) { if (default_) { - if (is_casting_) { + if (is_casting_) default_->ActivateCastView(); - } else { + else default_->ActivateSelectView(); - - // We only want to show the select view if we have a receiver we can - // cast to. To prevent showing the tray item and then hiding it some - // short time after, we cache if we have any receivers. We set our - // default visibility to true if we do have a receiver, false otherwise. - default_->SetVisible(has_cast_receivers_); - cast_config_delegate_->GetReceiversAndActivities( - base::Bind(&TrayCast::UpdateCachedReceiverState, - weak_ptr_factory_.GetWeakPtr())); - } } if (tray_) diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h index 8474a754e257..96bb903d84a9 100644 --- a/ash/system/cast/tray_cast.h +++ b/ash/system/cast/tray_cast.h @@ -48,7 +48,7 @@ class ASH_EXPORT TrayCast : public SystemTrayItem, public ShellObserver { // Callback used to enable/disable the begin casting view depending on // if we have any cast receivers. - void UpdateCachedReceiverState( + void OnReceiversUpdated( const CastConfigDelegate::ReceiversAndActivites& receivers_activities); // This makes sure that the current view displayed in the tray is the correct @@ -58,14 +58,15 @@ class ASH_EXPORT TrayCast : public SystemTrayItem, public ShellObserver { // casting session. void UpdatePrimaryView(); + CastConfigDelegate::ReceiversAndActivites receivers_and_activities_; + CastConfigDelegate::DeviceUpdateSubscription device_update_subscription_; + bool is_casting_ = false; + // Not owned. tray::CastTrayView* tray_ = nullptr; tray::CastDuplexView* default_ = nullptr; tray::CastDetailedView* detailed_ = nullptr; - CastConfigDelegate* cast_config_delegate_; - bool has_cast_receivers_ = false; - bool is_casting_ = false; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(TrayCast); diff --git a/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.cc b/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.cc new file mode 100644 index 000000000000..bab8394e7b78 --- /dev/null +++ b/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.cc @@ -0,0 +1,103 @@ +// Copyright 2015 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. + +#include "chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h" + +#include "base/lazy_instance.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/extensions/api/cast_devices_private.h" + +namespace extensions { + +namespace { + +ash::CastConfigDelegate::Receiver ConvertReceiverType( + const api::cast_devices_private::Receiver& receiver) { + ash::CastConfigDelegate::Receiver result; + result.id = receiver.id; + result.name = base::UTF8ToUTF16(receiver.name); + return result; +} + +ash::CastConfigDelegate::Activity ConvertActivityType( + const api::cast_devices_private::Activity& activity) { + ash::CastConfigDelegate::Activity result; + result.id = activity.id; + result.title = base::UTF8ToUTF16(activity.title); + if (activity.tab_id) + result.tab_id = *activity.tab_id; + else + result.tab_id = ash::CastConfigDelegate::Activity::TabId::UNKNOWN; + return result; +} + +ash::CastConfigDelegate::ReceiverAndActivity ConvertReceiverAndActivityType( + const api::cast_devices_private::Receiver& receiver, + const api::cast_devices_private::Activity* activity) { + ash::CastConfigDelegate::ReceiverAndActivity result; + result.receiver = ConvertReceiverType(receiver); + if (activity) + result.activity = ConvertActivityType(*activity); + return result; +} + +} // namespace + +static base::LazyInstance< + BrowserContextKeyedAPIFactory> g_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory* +CastDeviceUpdateListeners::GetFactoryInstance() { + return g_factory.Pointer(); +} + +// static +CastDeviceUpdateListeners* CastDeviceUpdateListeners::Get( + content::BrowserContext* context) { + return BrowserContextKeyedAPIFactory::Get(context); +} + +CastDeviceUpdateListeners::CastDeviceUpdateListeners( + content::BrowserContext* context) {} + +CastDeviceUpdateListeners::~CastDeviceUpdateListeners() {} + +ash::CastConfigDelegate::DeviceUpdateSubscription +CastDeviceUpdateListeners::RegisterCallback( + const ash::CastConfigDelegate::ReceiversAndActivitesCallback& callback) { + return callback_list_.Add(callback); +} + +void CastDeviceUpdateListeners::NotifyCallbacks( + const ReceiverAndActivityList& devices) { + callback_list_.Notify(devices); +} + +CastDevicesPrivateUpdateDevicesFunction:: + CastDevicesPrivateUpdateDevicesFunction() {} + +CastDevicesPrivateUpdateDevicesFunction:: + ~CastDevicesPrivateUpdateDevicesFunction() {} + +ExtensionFunction::ResponseAction +CastDevicesPrivateUpdateDevicesFunction::Run() { + auto params = + api::cast_devices_private::UpdateDevices::Params::Create(*args_); + + CastDeviceUpdateListeners::ReceiverAndActivityList devices; + for (linked_ptr device : + params->devices) { + devices.push_back(ConvertReceiverAndActivityType(device->receiver, + device->activity.get())); + } + + auto listeners = CastDeviceUpdateListeners::Get(browser_context()); + listeners->NotifyCallbacks(devices); + + return RespondNow(NoArguments()); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h b/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h new file mode 100644 index 000000000000..7621d83c1571 --- /dev/null +++ b/chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h @@ -0,0 +1,71 @@ +// Copyright 2015 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_EXTENSIONS_API_CAST_DEVICES_PRIVATE_CAST_DEVICES_PRIVATE_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_CAST_DEVICES_PRIVATE_CAST_DEVICES_PRIVATE_API_H_ + +#include + +#include "ash/cast_config_delegate.h" +#include "base/callback_list.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/extension_function.h" + +namespace extensions { + +class CastDeviceUpdateListeners : public BrowserContextKeyedAPI { + public: + explicit CastDeviceUpdateListeners(content::BrowserContext* context); + ~CastDeviceUpdateListeners() override; + + // Fetch an instance for the given context. + static CastDeviceUpdateListeners* Get(content::BrowserContext* context); + + // Register a function that will be invoked only when a new device update is + // available. + ash::CastConfigDelegate::DeviceUpdateSubscription RegisterCallback( + const ash::CastConfigDelegate::ReceiversAndActivitesCallback& callback); + + // BrowserContextKeyedAPI implementation: + static BrowserContextKeyedAPIFactory* + GetFactoryInstance(); + static const bool kServiceIsCreatedWithBrowserContext = false; + + private: + using ReceiverAndActivityList = + std::vector; + + friend class CastDevicesPrivateUpdateDevicesFunction; // For NotifyCallbacks. + void NotifyCallbacks(const ReceiverAndActivityList& devices); + + base::CallbackList callback_list_; + + friend class BrowserContextKeyedAPIFactory; + + // BrowserContextKeyedAPI implementation: + static const char* service_name() { return "CastDeviceUpdateListeners"; } + + DISALLOW_COPY_AND_ASSIGN(CastDeviceUpdateListeners); +}; + +// static void updateDeviceState(ReceiverActivity[] devices); +class CastDevicesPrivateUpdateDevicesFunction : + public UIThreadExtensionFunction { + public: + CastDevicesPrivateUpdateDevicesFunction(); + + private: + ~CastDevicesPrivateUpdateDevicesFunction() override; + + // ExtensionFunction: + ResponseAction Run() override; + + DECLARE_EXTENSION_FUNCTION("cast.devicesPrivate.updateDevices", + CASTDEVICESPRIVATE_UPDATEDEVICES); + DISALLOW_COPY_AND_ASSIGN(CastDevicesPrivateUpdateDevicesFunction); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_CAST_DEVICES_PRIVATE_CAST_DEVICES_PRIVATE_API_H_ diff --git a/chrome/browser/ui/ash/cast_config_delegate_chromeos.cc b/chrome/browser/ui/ash/cast_config_delegate_chromeos.cc dissimilarity index 65% index d90c2f16da55..9b7e6ef882ef 100644 --- a/chrome/browser/ui/ash/cast_config_delegate_chromeos.cc +++ b/chrome/browser/ui/ash/cast_config_delegate_chromeos.cc @@ -1,170 +1,115 @@ -// Copyright 2015 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. - -#include "chrome/browser/ui/ash/cast_config_delegate_chromeos.h" - -#include - -#include "base/memory/scoped_ptr.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/ui/browser_navigator.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/render_frame_host.h" -#include "content/public/browser/render_view_host.h" -#include "extensions/browser/extension_host.h" -#include "extensions/browser/extension_registry.h" -#include "extensions/browser/process_manager.h" -#include "extensions/common/extension.h" - -namespace chromeos { -namespace { - -using JavaScriptResultCallback = - content::RenderFrameHost::JavaScriptResultCallback; - -// Returns the cast extension if it exists. -const extensions::Extension* FindCastExtension() { - // TODO(jdufault): Figure out how to correctly handle multiprofile mode. - // See crbug.com/488751 - Profile* profile = ProfileManager::GetActiveUserProfile(); - const extensions::ExtensionRegistry* extension_registry = - extensions::ExtensionRegistry::Get(profile); - const extensions::ExtensionSet& enabled_extensions = - extension_registry->enabled_extensions(); - - for (size_t i = 0; i < arraysize(extensions::kChromecastExtensionIds); ++i) { - const std::string extension_id(extensions::kChromecastExtensionIds[i]); - if (enabled_extensions.Contains(extension_id)) { - return extension_registry->GetExtensionById( - extension_id, extensions::ExtensionRegistry::ENABLED); - } - } - return nullptr; -} - -// Utility method that returns the currently active RenderViewHost. -content::RenderViewHost* GetRenderViewHost() { - const extensions::Extension* extension = FindCastExtension(); - if (!extension) - return nullptr; - // TODO(jdufault): Figure out how to correctly handle multiprofile mode. - // See crbug.com/488751 - Profile* profile = ProfileManager::GetActiveUserProfile(); - if (!profile) - return nullptr; - extensions::ProcessManager* pm = extensions::ProcessManager::Get(profile); - return pm->GetBackgroundHostForExtension(extension->id())->render_view_host(); -} - -// Executes JavaScript in the context of the cast extension's background page. -void ExecuteJavaScript(const std::string& javascript) { - auto rvh = GetRenderViewHost(); - if (!rvh) - return; - rvh->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(javascript)); -} - -// Executes JavaScript in the context of the cast extension's background page. -// Invokes |callback| with the return value of the invoked javascript. -void ExecuteJavaScriptWithCallback(const std::string& javascript, - const JavaScriptResultCallback& callback) { - auto rvh = GetRenderViewHost(); - if (!rvh) - return; - rvh->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(javascript), - callback); -} - -// Handler for GetReceiversAndActivities. -void GetReceiversAndActivitiesCallback( - const ash::CastConfigDelegate::ReceiversAndActivitesCallback& callback, - const base::Value* value) { - ash::CastConfigDelegate::ReceiversAndActivites receiver_activites; - const base::ListValue* ra_list = nullptr; - if (value->GetAsList(&ra_list)) { - for (auto i = ra_list->begin(); i != ra_list->end(); ++i) { - const base::DictionaryValue* ra_dict = nullptr; - if ((*i)->GetAsDictionary(&ra_dict)) { - const base::DictionaryValue* receiver_dict(nullptr), - *activity_dict(nullptr); - ash::CastConfigDelegate::ReceiverAndActivity receiver_activity; - if (ra_dict->GetDictionary("receiver", &receiver_dict)) { - receiver_dict->GetString("name", &receiver_activity.receiver.name); - receiver_dict->GetString("id", &receiver_activity.receiver.id); - } - if (ra_dict->GetDictionary("activity", &activity_dict) && - !activity_dict->empty()) { - activity_dict->GetString("id", &receiver_activity.activity.id); - activity_dict->GetString("title", &receiver_activity.activity.title); - activity_dict->GetString("activityType", - &receiver_activity.activity.activity_type); - activity_dict->GetBoolean("allowStop", - &receiver_activity.activity.allow_stop); - activity_dict->GetInteger("tabId", - &receiver_activity.activity.tab_id); - } - receiver_activites[receiver_activity.receiver.id] = receiver_activity; - } - } - } - callback.Run(receiver_activites); -} - -} // namespace - -CastConfigDelegateChromeos::CastConfigDelegateChromeos() { -} - -CastConfigDelegateChromeos::~CastConfigDelegateChromeos() { -} - -bool CastConfigDelegateChromeos::HasCastExtension() const { - // TODO(jdufault): Temporarily disable the cast tray integration until we - // figure out how to get ExecuteJavaScriptInIsolatedWorld to work as expected. - // See crbug.com/514952. - return false; -} - -void CastConfigDelegateChromeos::GetReceiversAndActivities( - const ReceiversAndActivitesCallback& callback) { - // The methods in backgroundSetup are renamed during minification, so we have - // to bind the exported global API methods to backgroundSetup using call(). - ExecuteJavaScriptWithCallback( - "getMirrorCapableReceiversAndActivities.call(backgroundSetup);", - base::Bind(&GetReceiversAndActivitiesCallback, callback)); -} - -void CastConfigDelegateChromeos::CastToReceiver( - const std::string& receiver_id) { - // The methods in backgroundSetup are renamed during minification, so we have - // to bind the exported global API methods to backgroundSetup using call(). - ExecuteJavaScript("launchDesktopMirroring.call(backgroundSetup, '" + - receiver_id + "');"); -} - -void CastConfigDelegateChromeos::StopCasting() { - // The methods in backgroundSetup are renamed during minification, so we have - // to bind the exported global API methods to backgroundSetup using call(). - ExecuteJavaScript("stopMirroring.call(backgroundSetup, 'user-stop');"); - - // TODO(jdufault): Remove this after the beta/release versions of the - // cast extension have been updated so that they properly export the - // stopMirroring function. For now, we try to invoke all of the other - // names that the function goes by. See crbug.com/489929. - ExecuteJavaScript("backgroundSetup.Qu('user-stop');"); -} - -void CastConfigDelegateChromeos::LaunchCastOptions() { - chrome::NavigateParams params( - ProfileManager::GetActiveUserProfile(), - FindCastExtension()->GetResourceURL("options.html"), - ui::PAGE_TRANSITION_LINK); - params.disposition = NEW_FOREGROUND_TAB; - params.window_action = chrome::NavigateParams::SHOW_WINDOW; - chrome::Navigate(¶ms); -} - -} // namespace chromeos +// Copyright 2015 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. + +#include "chrome/browser/ui/ash/cast_config_delegate_chromeos.h" + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/api/cast_devices_private/cast_devices_private_api.h" +#include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser_navigator.h" +#include "chrome/common/extensions/api/cast_devices_private.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_host.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/process_manager.h" +#include "extensions/common/extension.h" + +namespace chromeos { +namespace { + +Profile* GetProfile() { + // TODO(jdufault): Figure out how to correctly handle multiprofile mode. + // See crbug.com/488751 + return ProfileManager::GetActiveUserProfile(); +} + +// Returns the cast extension if it exists. +const extensions::Extension* FindCastExtension() { + Profile* profile = GetProfile(); + const extensions::ExtensionRegistry* extension_registry = + extensions::ExtensionRegistry::Get(profile); + const extensions::ExtensionSet& enabled_extensions = + extension_registry->enabled_extensions(); + + for (size_t i = 0; i < arraysize(extensions::kChromecastExtensionIds); ++i) { + const std::string extension_id(extensions::kChromecastExtensionIds[i]); + if (enabled_extensions.Contains(extension_id)) { + return extension_registry->GetExtensionById( + extension_id, extensions::ExtensionRegistry::ENABLED); + } + } + return nullptr; +} + +} // namespace + +CastConfigDelegateChromeos::CastConfigDelegateChromeos() { +} + +CastConfigDelegateChromeos::~CastConfigDelegateChromeos() { +} + +bool CastConfigDelegateChromeos::HasCastExtension() const { + return FindCastExtension() != nullptr; +} + +CastConfigDelegateChromeos::DeviceUpdateSubscription +CastConfigDelegateChromeos::RegisterDeviceUpdateObserver( + const ReceiversAndActivitesCallback& callback) { + auto listeners = extensions::CastDeviceUpdateListeners::Get(GetProfile()); + return listeners->RegisterCallback(callback); +} + +void CastConfigDelegateChromeos::RequestDeviceRefresh() { + scoped_ptr args = + extensions::api::cast_devices_private::UpdateDevicesRequested::Create(); + scoped_ptr event(new extensions::Event( + extensions::events::CAST_DEVICES_PRIVATE_ON_UPDATE_DEVICES_REQUESTED, + extensions::api::cast_devices_private::UpdateDevicesRequested::kEventName, + args.Pass())); + extensions::EventRouter::Get(GetProfile()) + ->DispatchEventToExtension(FindCastExtension()->id(), event.Pass()); +} + +void CastConfigDelegateChromeos::CastToReceiver( + const std::string& receiver_id) { + scoped_ptr args = + extensions::api::cast_devices_private::StartCast::Create(receiver_id); + scoped_ptr event(new extensions::Event( + extensions::events::CAST_DEVICES_PRIVATE_ON_START_CAST, + extensions::api::cast_devices_private::StartCast::kEventName, + args.Pass())); + extensions::EventRouter::Get(GetProfile()) + ->DispatchEventToExtension(FindCastExtension()->id(), event.Pass()); +} + +void CastConfigDelegateChromeos::StopCasting() { + scoped_ptr args = + extensions::api::cast_devices_private::StopCast::Create("user-stop"); + scoped_ptr event(new extensions::Event( + extensions::events::CAST_DEVICES_PRIVATE_ON_STOP_CAST, + extensions::api::cast_devices_private::StopCast::kEventName, + args.Pass())); + extensions::EventRouter::Get(GetProfile()) + ->DispatchEventToExtension(FindCastExtension()->id(), event.Pass()); +} + +void CastConfigDelegateChromeos::LaunchCastOptions() { + chrome::NavigateParams params( + ProfileManager::GetActiveUserProfile(), + FindCastExtension()->GetResourceURL("options.html"), + ui::PAGE_TRANSITION_LINK); + params.disposition = NEW_FOREGROUND_TAB; + params.window_action = chrome::NavigateParams::SHOW_WINDOW; + chrome::Navigate(¶ms); +} + +} // namespace chromeos diff --git a/chrome/browser/ui/ash/cast_config_delegate_chromeos.h b/chrome/browser/ui/ash/cast_config_delegate_chromeos.h index c77afebdd8a4..bb01bbf19a4c 100644 --- a/chrome/browser/ui/ash/cast_config_delegate_chromeos.h +++ b/chrome/browser/ui/ash/cast_config_delegate_chromeos.h @@ -24,8 +24,9 @@ class CastConfigDelegateChromeos : public ash::CastConfigDelegate { // CastConfigDelegate: bool HasCastExtension() const override; - void GetReceiversAndActivities( + DeviceUpdateSubscription RegisterDeviceUpdateObserver( const ReceiversAndActivitesCallback& callback) override; + void RequestDeviceRefresh() override; void CastToReceiver(const std::string& receiver_id) override; void StopCasting() override; void LaunchCastOptions() override; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 6b0532a659e0..5c1484364e18 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -5,6 +5,8 @@ { 'variables': { 'chrome_browser_extensions_chromeos_sources': [ + 'browser/extensions/api/cast_devices_private/cast_devices_private_api.cc', + 'browser/extensions/api/cast_devices_private/cast_devices_private_api.h', 'browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc', 'browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h', 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc', diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 7ca3e7dde881..01d501d2adfc 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -172,6 +172,10 @@ "dependencies": ["permission:cast"], "contexts": ["blessed_extension"] }, + "cast.devicesPrivate": { + "dependencies": ["permission:cast"], + "contexts": ["blessed_extension"] + }, "cast.streaming.rtpStream": { "dependencies": ["permission:cast.streaming"], "contexts": ["blessed_extension"] diff --git a/chrome/common/extensions/api/cast_devices_private.idl b/chrome/common/extensions/api/cast_devices_private.idl new file mode 100644 index 000000000000..2903a58c326b --- /dev/null +++ b/chrome/common/extensions/api/cast_devices_private.idl @@ -0,0 +1,63 @@ +// Copyright 2015 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. + +// The chrome.cast.devicesPrivate API manages starting and stopping +// casts based on receiver names. It also provides a list of available +// receivers. +namespace cast.devicesPrivate { + // A cast receiver is an actual cast device. It has a unique |id| and a + // non-unique |name| which is how people identify it. + dictionary Receiver { + // The unique id of a receiver. + DOMString id; + + // The human-readable name of the receiver. + DOMString name; + }; + + // An activity represents what a receiver is currently doing. + dictionary Activity { + // The unique id for the activity. + DOMString id; + + // The human-readable title of the activity. + DOMString title; + + // Where did this activity originate from? For >=0 values, then this is the + // tab identifier. For <0 values, each value has a special meaning. If the + // cast was started on another device, there is no tabId. + long? tabId; + }; + + // A receiver with its associated activity. This is a 1-1 mapping. + dictionary ReceiverActivity { + // The current receiver. + Receiver receiver; + + // Its associated activity, if any. + Activity? activity; + }; + + interface Functions { + // Update the state of all of the cast devices and their associated + // activities. + // + // |devices|: Every single receivers with its associated activities, if any. + static void updateDevices(ReceiverActivity[] devices); + }; + + interface Events { + // Request a refresh of the device states. + [maxListeners=1] static void updateDevicesRequested(); + + // Stops a cast. This should always be a user-initiated stop. + static void stopCast(DOMString reason); + + // Starts a new cast. + // + // |receiverId|: The receiver ID to initiate a cast on. The implementation + // will handle selecting an appropriate activity. + static void startCast(DOMString receiverId); + }; +}; diff --git a/chrome/common/extensions/api/schemas.gypi b/chrome/common/extensions/api/schemas.gypi index f9451d2a293e..59638ce5fd6d 100644 --- a/chrome/common/extensions/api/schemas.gypi +++ b/chrome/common/extensions/api/schemas.gypi @@ -106,6 +106,7 @@ # ChromeOS-specific schemas. 'chromeos_schema_files': [ + 'cast_devices_private.idl', 'echo_private.json', 'enterprise_device_attributes.idl', 'enterprise_platform_keys.idl', diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h index ea9191bd4a98..c3df48854e08 100644 --- a/extensions/browser/extension_event_histogram_value.h +++ b/extensions/browser/extension_event_histogram_value.h @@ -398,6 +398,9 @@ enum HistogramValue { LANGUAGE_SETTINGS_PRIVATE_ON_INPUT_METHOD_REMOVED, LANGUAGE_SETTINGS_PRIVATE_ON_SPELLCHECK_DICTIONARIES_CHANGED, LANGUAGE_SETTINGS_PRIVATE_ON_CUSTOM_DICTIONARY_CHANGED, + CAST_DEVICES_PRIVATE_ON_UPDATE_DEVICES_REQUESTED, + CAST_DEVICES_PRIVATE_ON_START_CAST, + CAST_DEVICES_PRIVATE_ON_STOP_CAST, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 6c0e6ff37129..41081ddb3031 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1133,6 +1133,7 @@ enum HistogramValue { NETWORKINGPRIVATE_UNLOCKCELLULARSIM, NETWORKINGPRIVATE_SETCELLULARSIMSTATE, ENTERPRISE_DEVICEATTRIBUTES_GETDIRECTORYDEVICEID, + CASTDEVICESPRIVATE_UPDATEDEVICES, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json index b0d1f18ccac1..6d71a271f9de 100644 --- a/extensions/common/api/_permission_features.json +++ b/extensions/common/api/_permission_features.json @@ -159,6 +159,7 @@ "channel": "stable", "extension_types": ["extension", "legacy_packaged_app", "platform_app"], "whitelist": [ + "9448CAB302F268FB4917D06F70703893DCDA26C4", // Cast Test Extension "63ED55E43214C211F82122ED56407FF1A807F2A3", // Dev "FA01E0B81978950F2BC5A50512FD769725F57510", // Beta "B11A93E7E5B541F8010245EBDE2C74647D6C14B9", // Canary diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index b3a28e4f2a9c..7691707cc663 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -56921,6 +56921,9 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. label="LANGUAGE_SETTINGS_PRIVATE_ON_SPELLCHECK_DICTIONARIES_CHANGED"/> + + + @@ -58039,6 +58042,7 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. + -- 2.11.4.GIT