diff --git a/athena/activity/public/activity.h b/athena/activity/public/activity.h index 74a6c4112361..041449b6721c 100644 --- a/athena/activity/public/activity.h +++ b/athena/activity/public/activity.h @@ -9,6 +9,10 @@ #include "athena/athena_export.h" +namespace aura { +class Window; +} + namespace athena { class ActivityViewModel; @@ -72,6 +76,10 @@ class ATHENA_EXPORT Activity { // Returns the current media state. virtual ActivityMediaState GetMediaState() = 0; + + // Returns the window for the activity. This can be used to determine the + // stacking order of this activity against others. + virtual aura::Window* GetWindow() = 0; }; } // namespace athena diff --git a/athena/athena.gyp b/athena/athena.gyp index 448ba4e30594..6335641564b1 100644 --- a/athena/athena.gyp +++ b/athena/athena.gyp @@ -115,22 +115,39 @@ 'ATHENA_IMPLEMENTATION', ], 'sources': [ + 'content/app_activity.cc', + 'content/app_activity.h', + 'content/app_activity_proxy.cc', + 'content/app_activity_proxy.h', + 'content/app_activity_registry.cc', + 'content/app_activity_registry.h', + 'content/app_registry_impl.cc', + 'content/content_activity_factory.cc', + 'content/content_app_model_builder.cc', + 'content/public/app_registry.h', 'content/public/content_activity_factory.h', 'content/public/content_app_model_builder.h', 'content/public/web_contents_view_delegate_creator.h', - 'content/content_activity_factory.cc', - 'content/content_app_model_builder.cc', - 'content/app_activity.h', - 'content/app_activity.cc', 'content/render_view_context_menu_impl.cc', 'content/render_view_context_menu_impl.h', - 'content/web_activity.h', 'content/web_activity.cc', + 'content/web_activity.h', 'content/web_contents_view_delegate_factory_impl.cc', 'virtual_keyboard/public/virtual_keyboard_manager.h', 'virtual_keyboard/virtual_keyboard_manager_impl.cc', ], }, + { + 'target_name': 'athena_content_support_lib', + 'type': 'static_library', + 'dependencies': [ + '../content/content.gyp:content_browser', + ], + 'sources': [ + 'content/delegate/app_content_control_delegate_impl.cc', + 'content/public/app_content_control_delegate.h', + ], + }, { 'target_name': 'athena_test_support', 'type': 'static_library', @@ -152,6 +169,7 @@ 'resources/athena_resources.gyp:athena_resources', ], 'sources': [ + 'content/public/app_content_control_delegate.h', 'test/athena_test_base.cc', 'test/athena_test_base.h', 'test/athena_test_helper.cc', @@ -160,6 +178,7 @@ 'test/sample_activity.h', 'test/sample_activity_factory.cc', 'test/sample_activity_factory.h', + 'test/test_app_content_control_delegate_impl.cc', 'test/test_app_model_builder.cc', 'test/test_app_model_builder.h', 'test/test_screen_manager_delegate.cc', @@ -178,11 +197,12 @@ 'resources/athena_resources.gyp:athena_pak', ], 'sources': [ - 'test/athena_unittests.cc', 'activity/activity_manager_unittest.cc', + 'content/app_activity_unittest.cc', 'home/home_card_unittest.cc', 'input/accelerator_manager_unittest.cc', 'screen/screen_manager_unittest.cc', + 'test/athena_unittests.cc', 'wm/split_view_controller_unittest.cc', 'wm/window_list_provider_impl_unittest.cc', 'wm/window_manager_unittest.cc', diff --git a/athena/content/OWNERS b/athena/content/OWNERS new file mode 100644 index 000000000000..b03b46b0118b --- /dev/null +++ b/athena/content/OWNERS @@ -0,0 +1,4 @@ +skuhne@chromium.org +oshima@chromium.org +mukai@chromium.org + diff --git a/athena/content/app_activity.cc b/athena/content/app_activity.cc index c90daffa625e..309454ab401e 100644 --- a/athena/content/app_activity.cc +++ b/athena/content/app_activity.cc @@ -5,9 +5,13 @@ #include "athena/content/app_activity.h" #include "athena/activity/public/activity_manager.h" +#include "athena/content/app_activity_registry.h" +#include "athena/content/public/app_content_control_delegate.h" +#include "athena/content/public/app_registry.h" #include "content/public/browser/web_contents.h" #include "extensions/shell/browser/shell_app_window.h" #include "ui/views/controls/webview/webview.h" +#include "ui/views/widget/widget.h" namespace athena { @@ -15,13 +19,14 @@ namespace athena { AppActivity::AppActivity(extensions::ShellAppWindow* app_window) : app_window_(app_window), web_view_(NULL), - current_state_(ACTIVITY_UNLOADED) { - DCHECK(app_window_); + current_state_(ACTIVITY_UNLOADED), + app_activity_registry_(NULL) { } AppActivity::~AppActivity() { - if (GetCurrentState() != ACTIVITY_UNLOADED) - SetCurrentState(ACTIVITY_UNLOADED); + // If this activity is registered, we unregister it now. + if (app_activity_registry_) + app_activity_registry_->UnregisterAppActivity(this); } ActivityViewModel* AppActivity::GetActivityViewModel() { @@ -29,35 +34,41 @@ ActivityViewModel* AppActivity::GetActivityViewModel() { } void AppActivity::SetCurrentState(Activity::ActivityState state) { + ActivityState current_state = state; + // Remember the last requested state now so that a call to GetCurrentState() + // returns the new state. + current_state_ = state; + switch (state) { case ACTIVITY_VISIBLE: // Fall through (for the moment). case ACTIVITY_INVISIBLE: // By clearing the overview mode image we allow the content to be shown. overview_mode_image_ = gfx::ImageSkia(); - // TODO(skuhne): Find out how to reload an app from the extension system. + // Note: A reload from the unloaded state will be performed through the + // |AppActivityProxy| object and no further action isn't necessary here. break; case ACTIVITY_BACKGROUND_LOW_PRIORITY: - DCHECK(ACTIVITY_VISIBLE == current_state_ || - ACTIVITY_INVISIBLE == current_state_); + DCHECK(ACTIVITY_VISIBLE == current_state || + ACTIVITY_INVISIBLE == current_state); // TODO(skuhne): Do this. break; case ACTIVITY_PERSISTENT: - DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY, current_state_); + DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY, current_state); // TODO(skuhne): Do this. break; case ACTIVITY_UNLOADED: - DCHECK_NE(ACTIVITY_UNLOADED, current_state_); - // TODO(skuhne): Find out how to evict an app from the extension system. - // web_view_->EvictContent(); + DCHECK_NE(ACTIVITY_UNLOADED, current_state); + // This will cause the application to shut down, close its windows and + // delete this object. Instead a |AppActivityProxy| will be created as + // place holder. + if (app_activity_registry_) + app_activity_registry_->Unload(); break; } - // Remember the last requested state. - current_state_ = state; } Activity::ActivityState AppActivity::GetCurrentState() { - // TODO(skuhne): Check here also eviction status. if (!web_view_) { DCHECK_EQ(ACTIVITY_UNLOADED, current_state_); return ACTIVITY_UNLOADED; @@ -82,6 +93,10 @@ Activity::ActivityMediaState AppActivity::GetMediaState() { return Activity::ACTIVITY_MEDIA_STATE_NONE; } +aura::Window* AppActivity::GetWindow() { + return !web_view_ ? NULL : web_view_->GetWidget()->GetNativeWindow(); +} + void AppActivity::Init() { } @@ -130,4 +145,26 @@ void AppActivity::DidUpdateFaviconURL( ActivityManager::Get()->UpdateActivity(this); } +void AppActivity::DidStartNavigationToPendingEntry( + const GURL& url, + content::NavigationController::ReloadType reload_type) { + if (!app_activity_registry_) + RegisterActivity(); +} + +// Register an |activity| with an application. +// Note: This should only get called once for an |app_window| of the +// |activity|. +void AppActivity::RegisterActivity() { + content::WebContents* web_contents = app_window_->GetAssociatedWebContents(); + AppRegistry* app_registry = AppRegistry::Get(); + // Get the application's registry. + app_activity_registry_ = app_registry->GetAppActivityRegistry( + app_registry->GetDelegate()->GetApplicationID(web_contents), + web_contents->GetBrowserContext()); + DCHECK(app_activity_registry_); + // Register the activity. + app_activity_registry_->RegisterAppActivity(this); +} + } // namespace athena diff --git a/athena/content/app_activity.h b/athena/content/app_activity.h index 4f6ef2b3d47a..105f7285bfc7 100644 --- a/athena/content/app_activity.h +++ b/athena/content/app_activity.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef ATHENA_CONTENT_PUBLIC_APP_ACTIVITY_H_ -#define ATHENA_CONTENT_PUBLIC_APP_ACTIVITY_H_ +#ifndef ATHENA_CONTENT_APP_ACTIVITY_H_ +#define ATHENA_CONTENT_APP_ACTIVITY_H_ #include "athena/activity/public/activity.h" #include "athena/activity/public/activity_view_model.h" @@ -20,6 +20,9 @@ class WebView; namespace athena { +class AppActivityRegistry; + +// The activity object for a hosted V2 application. class AppActivity : public Activity, public ActivityViewModel, public content::WebContentsObserver { @@ -27,13 +30,13 @@ class AppActivity : public Activity, explicit AppActivity(extensions::ShellAppWindow* app_window); virtual ~AppActivity(); - protected: // Activity: virtual athena::ActivityViewModel* GetActivityViewModel() OVERRIDE; virtual void SetCurrentState(Activity::ActivityState state) OVERRIDE; virtual ActivityState GetCurrentState() OVERRIDE; virtual bool IsVisible() OVERRIDE; virtual ActivityMediaState GetMediaState() OVERRIDE; + virtual aura::Window* GetWindow() OVERRIDE; // ActivityViewModel: virtual void Init() OVERRIDE; @@ -44,13 +47,20 @@ class AppActivity : public Activity, virtual void CreateOverviewModeImage() OVERRIDE; virtual gfx::ImageSkia GetOverviewModeImage() OVERRIDE; + protected: // content::WebContentsObserver: virtual void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) OVERRIDE; virtual void DidUpdateFaviconURL( const std::vector& candidates) OVERRIDE; + virtual void DidStartNavigationToPendingEntry( + const GURL& url, + content::NavigationController::ReloadType reload_type) OVERRIDE; private: + // Register this activity with its application. + void RegisterActivity(); + scoped_ptr app_window_; views::WebView* web_view_; @@ -60,6 +70,11 @@ class AppActivity : public Activity, // The image which will be used in overview mode. gfx::ImageSkia overview_mode_image_; + // If known the registry which holds all activities for the associated app. + // This object is owned by |AppRegistry| and will be a valid pointer as long + // as this object lives. + AppActivityRegistry* app_activity_registry_; + DISALLOW_COPY_AND_ASSIGN(AppActivity); }; diff --git a/athena/content/app_activity_proxy.cc b/athena/content/app_activity_proxy.cc new file mode 100644 index 000000000000..222e697b38ba --- /dev/null +++ b/athena/content/app_activity_proxy.cc @@ -0,0 +1,84 @@ +// Copyright 2014 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 "athena/content/app_activity_proxy.h" + +#include "athena/content/app_activity_registry.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace athena { + +AppActivityProxy::AppActivityProxy(ActivityViewModel* view_model, + AppActivityRegistry* creator) : + app_activity_registry_(creator), + title_(view_model->GetTitle()), + image_(view_model->GetOverviewModeImage()), + color_(view_model->GetRepresentativeColor()), + // TODO(skuhne): We probably need to do something better with the view + // (e.g. showing the image). + view_(new views::View()) {} + +AppActivityProxy::~AppActivityProxy() { + app_activity_registry_->ProxyDestroyed(this); +} + +ActivityViewModel* AppActivityProxy::GetActivityViewModel() { + return this; +} + +void AppActivityProxy::SetCurrentState(ActivityState state) { + // We ignore all calls which try to re-load the application at a lower than + // running invisible state. + if (state != ACTIVITY_VISIBLE && state != ACTIVITY_INVISIBLE) + return; + app_activity_registry_->RestartApplication(this); + // Note: This object is now destroyed. +} + +Activity::ActivityState AppActivityProxy::GetCurrentState() { + return ACTIVITY_UNLOADED; +} + +bool AppActivityProxy::IsVisible() { + return true; +} + +Activity::ActivityMediaState AppActivityProxy::GetMediaState() { + // This proxy has never any media playing. + return ACTIVITY_MEDIA_STATE_NONE; +} + +aura::Window* AppActivityProxy::GetWindow() { + return view_->GetWidget()->GetNativeWindow(); +} + +void AppActivityProxy::Init() { +} + +SkColor AppActivityProxy::GetRepresentativeColor() const { + return color_; +} + +base::string16 AppActivityProxy::GetTitle() const { + return title_; +} + +bool AppActivityProxy::UsesFrame() const { + return true; +} + +views::View* AppActivityProxy::GetContentsView() { + return view_; +} + +void AppActivityProxy::CreateOverviewModeImage() { + // Nothing we can do here. +} + +gfx::ImageSkia AppActivityProxy::GetOverviewModeImage() { + return image_; +} + +} // namespace athena diff --git a/athena/content/app_activity_proxy.h b/athena/content/app_activity_proxy.h new file mode 100644 index 000000000000..c5092e865129 --- /dev/null +++ b/athena/content/app_activity_proxy.h @@ -0,0 +1,63 @@ +// Copyright 2014 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 ATHENA_CONTENT_APP_ACTIVITY_PROXY_H_ +#define ATHENA_CONTENT_APP_ACTIVITY_PROXY_H_ + +#include + +#include "athena/activity/public/activity.h" +#include "athena/activity/public/activity_view_model.h" +#include "athena/content/app_activity_proxy.h" +#include "ui/gfx/image/image_skia.h" + +namespace athena { + +class AppActivityRegistry; + +// This activity object is a proxy placeholder for the application while it is +// unloaded. When selected it will launch the applciation again and destroy +// itself indirectly. +class AppActivityProxy : public Activity, + public ActivityViewModel { + public: + AppActivityProxy(ActivityViewModel* view_model, AppActivityRegistry* creator); + virtual ~AppActivityProxy(); + + // Activity overrides: + virtual ActivityViewModel* GetActivityViewModel() OVERRIDE; + virtual void SetCurrentState(ActivityState state) OVERRIDE; + virtual ActivityState GetCurrentState() OVERRIDE; + virtual bool IsVisible() OVERRIDE; + virtual ActivityMediaState GetMediaState() OVERRIDE; + virtual aura::Window* GetWindow() OVERRIDE; + + // ActivityViewModel overrides: + virtual void Init() OVERRIDE; + virtual SkColor GetRepresentativeColor() const OVERRIDE; + virtual base::string16 GetTitle() const OVERRIDE; + virtual bool UsesFrame() const OVERRIDE; + virtual views::View* GetContentsView() OVERRIDE; + virtual void CreateOverviewModeImage() OVERRIDE; + virtual gfx::ImageSkia GetOverviewModeImage() OVERRIDE; + + private: + // The creator of this object which needs to be informed if the object gets + // destroyed or the application should get restarted. + AppActivityRegistry* app_activity_registry_; + + // The presentation values. + const base::string16 title_; + const gfx::ImageSkia image_; + const SkColor color_; + + // The associated view. + views::View* view_; + + DISALLOW_COPY_AND_ASSIGN(AppActivityProxy); +}; + +} // namespace athena + +#endif // ATHENA_CONTENT_APP_ACTIVITY_PROXY_H_ diff --git a/athena/content/app_activity_registry.cc b/athena/content/app_activity_registry.cc new file mode 100644 index 000000000000..dc7bce9c75cc --- /dev/null +++ b/athena/content/app_activity_registry.cc @@ -0,0 +1,146 @@ +// Copyright 2014 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 "athena/content/app_activity_registry.h" + +#include "athena/activity/public/activity_manager.h" +#include "athena/content/app_activity.h" +#include "athena/content/app_activity_proxy.h" +#include "athena/content/public/app_content_control_delegate.h" +#include "athena/content/public/app_registry.h" +#include "ui/aura/window.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace athena { + +AppActivityRegistry::AppActivityRegistry( + const std::string& app_id, + content::BrowserContext* browser_context) : + app_id_(app_id), + browser_context_(browser_context), + unloaded_activity_proxy_(NULL) {} + +AppActivityRegistry::~AppActivityRegistry() { + CHECK(activity_list_.empty()); + if (unloaded_activity_proxy_) + ActivityManager::Get()->RemoveActivity(unloaded_activity_proxy_); + DCHECK(!unloaded_activity_proxy_); +} + +void AppActivityRegistry::RegisterAppActivity(AppActivity* app_activity) { + if (unloaded_activity_proxy_) { + // Since we add an application window, the activity isn't unloaded anymore. + ActivityManager::Get()->RemoveActivity(unloaded_activity_proxy_); + // With the removal the object should have been deleted and we should have + // been informed of the object's destruction. + DCHECK(!unloaded_activity_proxy_); + } + // The same window should never be added twice. + CHECK(std::find(activity_list_.begin(), + activity_list_.end(), + app_activity) == activity_list_.end()); + activity_list_.push_back(app_activity); +} + +void AppActivityRegistry::UnregisterAppActivity(AppActivity* app_activity) { + // It is possible that a detach gets called without ever being attached. + std::vector::iterator it = + std::find(activity_list_.begin(), activity_list_.end(), app_activity); + if (it == activity_list_.end()) + return; + + activity_list_.erase(it); + // When the last window gets destroyed and there is no proxy to restart, we + // delete ourselves. + if (activity_list_.empty() && !unloaded_activity_proxy_) { + AppRegistry::Get()->RemoveAppActivityRegistry(this); + // after this call this object should be gone. + } +} + +AppActivity* AppActivityRegistry::GetAppActivityAt(size_t index) { + if (index >= activity_list_.size()) + return NULL; + return activity_list_[index]; +} + +void AppActivityRegistry::Unload() { + CHECK(!unloaded_activity_proxy_); + DCHECK(!activity_list_.empty()); + + // In order to allow an entire application to unload we require that all of + // its activities are marked as unloaded. + for (std::vector::iterator it = activity_list_.begin(); + it != activity_list_.end(); ++it) { + if ((*it)->GetCurrentState() != Activity::ACTIVITY_UNLOADED) + return; + } + + // Create an activity proxy which can be used to re-activate the app. Insert + // the proxy then into the activity stream at the location of the (newest) + // current activity. + unloaded_activity_proxy_ = + new AppActivityProxy(activity_list_[0]->GetActivityViewModel(), this); + ActivityManager::Get()->AddActivity(unloaded_activity_proxy_); + // The new activity should be in the place of the most recently used app + // window. To get it there, we get the most recently used application window + // and place the proxy activities window in front or behind, so that when the + // activity disappears it takes its place. + MoveBeforeMruApplicationWindow(unloaded_activity_proxy_->GetWindow()); + + // Unload the application. This operation will be asynchronous. + if (!AppRegistry::Get()->GetDelegate()->UnloadApplication(app_id_, + browser_context_)) { + while(!activity_list_.empty()) + delete activity_list_.back(); + } +} + +void AppActivityRegistry::ProxyDestroyed(AppActivityProxy* proxy) { + DCHECK_EQ(unloaded_activity_proxy_, proxy); + unloaded_activity_proxy_ = NULL; + if (activity_list_.empty()) { + AppRegistry::Get()->RemoveAppActivityRegistry(this); + // |This| is gone now. + } +} + +void AppActivityRegistry::RestartApplication(AppActivityProxy* proxy) { + DCHECK_EQ(unloaded_activity_proxy_, proxy); + // Restart the application. + AppRegistry::Get()->GetDelegate()->RestartApplication(app_id_, + browser_context_); + // Remove the activity from the Activity manager. + ActivityManager::Get()->RemoveActivity(unloaded_activity_proxy_); + delete unloaded_activity_proxy_; // Will call ProxyDestroyed. + // After this call |this| might be gone if the app did not open a window yet. +} + +void AppActivityRegistry::MoveBeforeMruApplicationWindow(aura::Window* window) { + DCHECK(activity_list_.size()); + // TODO(skuhne): This needs to be changed to some kind of delegate which + // resides in the window manager. + const aura::Window::Windows children = + activity_list_[0]->GetWindow()->parent()->children();; + // Find the first window in the container which is part of the application. + for (aura::Window::Windows::const_iterator child_iterator = children.begin(); + child_iterator != children.end(); ++child_iterator) { + for (std::vector::iterator app_iterator = + activity_list_.begin(); + app_iterator != activity_list_.end(); ++app_iterator) { + if (*child_iterator == (*app_iterator)->GetWindow()) { + // Since "StackChildBelow" does not change the order if the window + // if the window is below - but not immediately behind - the target + // window, we re-stack both ways. + window->parent()->StackChildBelow(window, *child_iterator); + window->parent()->StackChildBelow(*child_iterator, window); + return; + } + } + } + NOTREACHED() << "The application does not get tracked by the mru list"; +} + +} // namespace athena diff --git a/athena/content/app_activity_registry.h b/athena/content/app_activity_registry.h new file mode 100644 index 000000000000..14ef7de3945b --- /dev/null +++ b/athena/content/app_activity_registry.h @@ -0,0 +1,103 @@ +// Copyright 2014 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 ATHENA_CONTENT_APP_ACTIVITY_REGISTRY_H_ +#define ATHENA_CONTENT_APP_ACTIVITY_REGISTRY_H_ + +#include + +#include "athena/activity/public/activity_view_model.h" +#include "athena/content/app_activity_proxy.h" + +namespace aura { +class Window; +} + +namespace content { +class BrowserContext; +} + +namespace athena { + +class AppActivity; + +// This class keeps track of all existing |AppActivity|s and shuts down all of +// them when the application gets unloaded to save memory. It will then replace +// the |AppActivity| in the Activity list as proxy to allow restarting of the +// application. +class ATHENA_EXPORT AppActivityRegistry { + public: + AppActivityRegistry(const std::string& app_id, + content::BrowserContext* browser_context); + virtual ~AppActivityRegistry(); + + // Register an |AppActivity| with this application. + void RegisterAppActivity(AppActivity* app_activity); + + // Unregister a previously attached |AppActivity|. + // Note that detaching the last |AppActivity| will delete this object - unless + // the resource manager was trying to unload the application. + // Note furthermore that Detach can be called without ever being registered. + void UnregisterAppActivity(AppActivity* app_activity); + + // Returns the number of activities/windows with this application. + int NumberOfActivities() const { return activity_list_.size(); } + + // Returns the |AppActivity| at |index|. It will return NULL if an invalid + // index was specified. + AppActivity* GetAppActivityAt(size_t index); + + // Unload all application associated activities to save resources. + void Unload(); + + // Returns true if the application is in the unloaded state. + bool IsUnloaded() { return unloaded_activity_proxy_ != NULL; } + + content::BrowserContext* browser_context() const { return browser_context_; } + const std::string& app_id() const { return app_id_; } + + AppActivityProxy* unloaded_activity_proxy_for_test() { + return unloaded_activity_proxy_; + } + + protected: + friend AppActivityProxy; + + // When the |AppActivityProxy| gets destroyed it should call this function + // to disconnect from this object. This call might destroy |this|. + void ProxyDestroyed(AppActivityProxy* proxy); + + // When called by the |AppActivityProxy| to restart the application, it can + // cause the application to restart. When that happens the proxy will get + // destroyed. After this call |this| might be destroyed. + void RestartApplication(AppActivityProxy* proxy); + + private: + // Move the window before the most recently used application window. + void MoveBeforeMruApplicationWindow(aura::Window* window); + + // A list of all activities associated with this application. + std::vector activity_list_; + + // The application id for this proxy. + std::string app_id_; + + // The browser context of the user. + content::BrowserContext* browser_context_; + + // When the activity is unloaded this is the AppActivityProxy. The object is + // owned the the ActivityManager. + AppActivityProxy* unloaded_activity_proxy_; + + // The presentation values. + SkColor color_; + base::string16 title_; + gfx::ImageSkia image_; + + DISALLOW_COPY_AND_ASSIGN(AppActivityRegistry); +}; + +} // namespace athena + +#endif // ATHENA_CONTENT_APP_ACTIVITY_REGISTRY_H_ diff --git a/athena/content/app_activity_unittest.cc b/athena/content/app_activity_unittest.cc new file mode 100644 index 000000000000..f94cc85f1626 --- /dev/null +++ b/athena/content/app_activity_unittest.cc @@ -0,0 +1,412 @@ +/// Copyright 2014 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 "athena/activity/public/activity_factory.h" +#include "athena/activity/public/activity_manager.h" +#include "athena/content/app_activity.h" +#include "athena/content/app_activity_registry.h" +#include "athena/content/public/app_content_control_delegate.h" +#include "athena/content/public/app_registry.h" +#include "athena/test/athena_test_base.h" +#include "extensions/shell/browser/shell_app_window.h" +#include "ui/aura/window.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + + +namespace content { +class BrowserContext; +} + +namespace athena { +namespace test { + +namespace { + +// An identifier for the running apps. +const char kDummyApp1[] = "aaaaaaa"; +const char kDummyApp2[] = "bbbbbbb"; + +// A dummy test app activity which works without content / ShellAppWindow. +class TestAppActivity : public AppActivity { + public: + explicit TestAppActivity(const std::string& app_id) : + AppActivity(NULL), + app_id_(app_id), + view_(new views::View()), + current_state_(ACTIVITY_VISIBLE) { + app_activity_registry_ = + AppRegistry::Get()->GetAppActivityRegistry(app_id, NULL); + app_activity_registry_->RegisterAppActivity(this); + } + virtual ~TestAppActivity() { + app_activity_registry_->UnregisterAppActivity(this); + } + + AppActivityRegistry* app_activity_registry() { + return app_activity_registry_; + } + + // Activity: + virtual ActivityViewModel* GetActivityViewModel() OVERRIDE { + return this; + } + virtual void SetCurrentState(Activity::ActivityState state) OVERRIDE { + current_state_ = state; + } + virtual ActivityState GetCurrentState() OVERRIDE { + return current_state_; + } + virtual bool IsVisible() OVERRIDE { + return true; + } + virtual ActivityMediaState GetMediaState() OVERRIDE { + return Activity::ACTIVITY_MEDIA_STATE_NONE; + } + virtual aura::Window* GetWindow() OVERRIDE { + return view_->GetWidget()->GetNativeWindow(); + } + + // ActivityViewModel: + virtual void Init() OVERRIDE {} + virtual SkColor GetRepresentativeColor() const OVERRIDE { return 0; } + virtual base::string16 GetTitle() const OVERRIDE { return title_; } + virtual bool UsesFrame() const OVERRIDE { return true; } + virtual views::View* GetContentsView() OVERRIDE { return view_; } + virtual void CreateOverviewModeImage() OVERRIDE {} + + private: + // If known the registry which holds all activities for the associated app. + AppActivityRegistry* app_activity_registry_; + + // The application ID. + const std::string& app_id_; + + // The title of the activity. + base::string16 title_; + + // Our view. + views::View* view_; + + // The current state for this activity. + ActivityState current_state_; + + DISALLOW_COPY_AND_ASSIGN(TestAppActivity); +}; + +// An AppContentDelegateClass which we can query for call stats. +class TestAppContentControlDelegate : public AppContentControlDelegate { + public: + TestAppContentControlDelegate() : unload_called_(0), + restart_called_(0) {} + virtual ~TestAppContentControlDelegate() {} + + int unload_called() { return unload_called_; } + int restart_called() { return restart_called_; } + void SetExtensionID(const std::string& extension_id) { + extension_id_to_return_ = extension_id; + } + + // Unload an application. Returns true when unloaded. + virtual bool UnloadApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE { + unload_called_++; + // Since we did not close anything we let the framework clean up. + return false; + } + // Restarts an application. Returns true when the restart was initiated. + virtual bool RestartApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE { + restart_called_++; + return true; + } + // Returns the application ID (or an empty string) for a given web content. + virtual std::string GetApplicationID( + content::WebContents* web_contents) OVERRIDE { + return extension_id_to_return_; + } + + private: + int unload_called_; + int restart_called_; + std::string extension_id_to_return_; + + DISALLOW_COPY_AND_ASSIGN(TestAppContentControlDelegate); +}; + +} // namespace + +// Our testing base. +class AppActivityTest : public AthenaTestBase { + public: + AppActivityTest() : test_app_content_control_delegate_(NULL) {} + virtual ~AppActivityTest() {} + + // AthenaTestBase: + virtual void SetUp() OVERRIDE { + AthenaTestBase::SetUp(); + // Create and install our TestAppContentDelegate with instrumentation. + test_app_content_control_delegate_ = new TestAppContentControlDelegate(); + AppRegistry::Get()->SetDelegate(test_app_content_control_delegate_); + } + + // A function to create an Activity. + TestAppActivity* CreateAppActivity(const std::string& app_id) { + TestAppActivity* activity = new TestAppActivity(app_id); + ActivityManager::Get()->AddActivity(activity); + return activity; + } + + void CloseActivity(Activity* activity) { + delete activity; + RunAllPendingInMessageLoop(); + } + + // Get the position of the activity in the navigation history. + int GetActivityPosition(Activity* activity) { + aura::Window* window = activity->GetActivityViewModel()->GetContentsView() + ->GetWidget()->GetNativeWindow(); + aura::Window::Windows windows = activity->GetWindow()->parent()->children(); + for (size_t i = 0; i < windows.size(); i++) { + if (windows[i] == window) + return i; + } + return -1; + } + + protected: + TestAppContentControlDelegate* test_app_content_control_delegate() { + return test_app_content_control_delegate_; + } + + private: + TestAppContentControlDelegate* test_app_content_control_delegate_; + + DISALLOW_COPY_AND_ASSIGN(AppActivityTest); +}; + +// Only creates one activity and destroys it. +TEST_F(AppActivityTest, OneAppActivity) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + { + TestAppActivity* app_activity = CreateAppActivity(kDummyApp1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, app_activity->app_activity_registry()->NumberOfActivities()); + EXPECT_EQ(AppRegistry::Get()->GetAppActivityRegistry(kDummyApp1, NULL), + app_activity->app_activity_registry()); + CloseActivity(app_activity); + } + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + EXPECT_EQ(0, test_app_content_control_delegate()->restart_called()); +} + +// Test running of two applications. +TEST_F(AppActivityTest, TwoAppsWithOneActivityEach) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + { + TestAppActivity* app_activity1 = CreateAppActivity(kDummyApp1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, app_activity1->app_activity_registry()->NumberOfActivities()); + TestAppActivity* app_activity2 = CreateAppActivity(kDummyApp2); + EXPECT_EQ(2, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, app_activity2->app_activity_registry()->NumberOfActivities()); + EXPECT_EQ(1, app_activity1->app_activity_registry()->NumberOfActivities()); + CloseActivity(app_activity1); + CloseActivity(app_activity2); + } + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + EXPECT_EQ(0, test_app_content_control_delegate()->restart_called()); +} + +// Create and destroy two activities for the same application. +TEST_F(AppActivityTest, TwoAppActivities) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + { + TestAppActivity* app_activity1 = CreateAppActivity(kDummyApp1); + TestAppActivity* app_activity2 = CreateAppActivity(kDummyApp1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(2, app_activity1->app_activity_registry()->NumberOfActivities()); + EXPECT_EQ(app_activity1->app_activity_registry(), + app_activity2->app_activity_registry()); + CloseActivity(app_activity1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, app_activity2->app_activity_registry()->NumberOfActivities()); + CloseActivity(app_activity2); + } + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + { + TestAppActivity* app_activity1 = CreateAppActivity(kDummyApp1); + TestAppActivity* app_activity2 = CreateAppActivity(kDummyApp1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(2, app_activity1->app_activity_registry()->NumberOfActivities()); + EXPECT_EQ(app_activity1->app_activity_registry(), + app_activity2->app_activity_registry()); + CloseActivity(app_activity2); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, app_activity1->app_activity_registry()->NumberOfActivities()); + CloseActivity(app_activity1); + } + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + EXPECT_EQ(0, test_app_content_control_delegate()->restart_called()); +} + +// Test unload and the creation of the proxy, then "closing the activity". +TEST_F(AppActivityTest, TestUnloadFollowedByClose) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + + TestAppActivity* app_activity = CreateAppActivity(kDummyApp1); + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + AppActivityRegistry* app_activity_registry = + app_activity->app_activity_registry(); + EXPECT_EQ(1, app_activity_registry->NumberOfActivities()); + EXPECT_EQ(Activity::ACTIVITY_VISIBLE, app_activity->GetCurrentState()); + + // Calling Unload now should not do anything since at least one activity in + // the registry is still visible. + app_activity_registry->Unload(); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + + // After setting our activity to unloaded however the application should get + // unloaded as requested. + app_activity->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity_registry->Unload(); + EXPECT_EQ(1, test_app_content_control_delegate()->unload_called()); + + // Check that our created application is gone, and instead a proxy got + // created. + ASSERT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + ASSERT_EQ(app_activity_registry, + AppRegistry::Get()->GetAppActivityRegistry(kDummyApp1, NULL)); + EXPECT_EQ(0, app_activity_registry->NumberOfActivities()); + Activity* activity_proxy = + app_activity_registry->unloaded_activity_proxy_for_test(); + ASSERT_TRUE(activity_proxy); + EXPECT_NE(app_activity, activity_proxy); + EXPECT_EQ(Activity::ACTIVITY_UNLOADED, activity_proxy->GetCurrentState()); + + // Close the proxy object and make sure that nothing bad happens. + CloseActivity(activity_proxy); + + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, test_app_content_control_delegate()->unload_called()); + EXPECT_EQ(0, test_app_content_control_delegate()->restart_called()); +} + +// Test that when unloading an app while multiple apps / activities are present, +// the proxy gets created in the correct location. +TEST_F(AppActivityTest, TestUnloadProxyLocation) { + // Set up some activities for some applications. + TestAppActivity* app_activity1a = CreateAppActivity(kDummyApp1); + TestAppActivity* app_activity2a = CreateAppActivity(kDummyApp2); + TestAppActivity* app_activity2b = CreateAppActivity(kDummyApp2); + TestAppActivity* app_activity1b = CreateAppActivity(kDummyApp1); + EXPECT_EQ(3, GetActivityPosition(app_activity1b)); + EXPECT_EQ(2, GetActivityPosition(app_activity2b)); + EXPECT_EQ(1, GetActivityPosition(app_activity2a)); + EXPECT_EQ(0, GetActivityPosition(app_activity1a)); + + // Unload an app and make sure that the proxy is in the newest activity slot. + AppActivityRegistry* app_activity_registry = + app_activity2a->app_activity_registry(); + app_activity2a->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity2b->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity2a->app_activity_registry()->Unload(); + EXPECT_EQ(0, app_activity_registry->NumberOfActivities()); + Activity* activity_proxy = + app_activity_registry->unloaded_activity_proxy_for_test(); + RunAllPendingInMessageLoop(); + + EXPECT_EQ(2, GetActivityPosition(app_activity1b)); + EXPECT_EQ(1, GetActivityPosition(activity_proxy)); + EXPECT_EQ(0, GetActivityPosition(app_activity1a)); + + CloseActivity(activity_proxy); + CloseActivity(app_activity1b); + CloseActivity(app_activity1a); +} + +// Test that an unload with multiple activities of the same app will only unload +// when all activities were marked for unloading. +TEST_F(AppActivityTest, TestMultipleActivityUnloadLock) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + + TestAppActivity* app_activity1 = CreateAppActivity(kDummyApp1); + TestAppActivity* app_activity2 = CreateAppActivity(kDummyApp1); + TestAppActivity* app_activity3 = CreateAppActivity(kDummyApp1); + + // Check that we have 3 activities of the same application. + EXPECT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + AppActivityRegistry* app_activity_registry = + app_activity1->app_activity_registry(); + EXPECT_EQ(app_activity_registry, app_activity2->app_activity_registry()); + EXPECT_EQ(app_activity_registry, app_activity3->app_activity_registry()); + EXPECT_EQ(3, app_activity_registry->NumberOfActivities()); + EXPECT_EQ(Activity::ACTIVITY_VISIBLE, app_activity1->GetCurrentState()); + EXPECT_EQ(Activity::ACTIVITY_VISIBLE, app_activity2->GetCurrentState()); + EXPECT_EQ(Activity::ACTIVITY_VISIBLE, app_activity3->GetCurrentState()); + + // After setting all activities to UNLOADED the application should unload. + app_activity1->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity1->app_activity_registry()->Unload(); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + app_activity2->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity2->app_activity_registry()->Unload(); + EXPECT_EQ(0, test_app_content_control_delegate()->unload_called()); + app_activity3->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity3->app_activity_registry()->Unload(); + EXPECT_EQ(1, test_app_content_control_delegate()->unload_called()); + + // Now there should only be the proxy activity left. + ASSERT_EQ(1, AppRegistry::Get()->NumberOfApplications()); + ASSERT_EQ(app_activity_registry, + AppRegistry::Get()->GetAppActivityRegistry(kDummyApp1, NULL)); + EXPECT_EQ(0, app_activity_registry->NumberOfActivities()); + Activity* activity_proxy = + app_activity_registry->unloaded_activity_proxy_for_test(); + ASSERT_TRUE(activity_proxy); + EXPECT_NE(app_activity1, activity_proxy); + EXPECT_NE(app_activity2, activity_proxy); + EXPECT_NE(app_activity3, activity_proxy); + EXPECT_EQ(Activity::ACTIVITY_UNLOADED, activity_proxy->GetCurrentState()); + + // Close the proxy object and make sure that nothing bad happens. + CloseActivity(activity_proxy); + + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + EXPECT_EQ(1, test_app_content_control_delegate()->unload_called()); + EXPECT_EQ(0, test_app_content_control_delegate()->restart_called()); +} + +// Test that activating the proxy will reload the application. +TEST_F(AppActivityTest, TestUnloadWithReload) { + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); + + TestAppActivity* app_activity = CreateAppActivity(kDummyApp1); + AppActivityRegistry* app_activity_registry = + app_activity->app_activity_registry(); + + // Unload the activity. + app_activity->SetCurrentState(Activity::ACTIVITY_UNLOADED); + app_activity_registry->Unload(); + EXPECT_EQ(1, test_app_content_control_delegate()->unload_called()); + + // Try to activate the activity again. This will force the application to + // reload. + Activity* activity_proxy = + app_activity_registry->unloaded_activity_proxy_for_test(); + activity_proxy->SetCurrentState(Activity::ACTIVITY_VISIBLE); + EXPECT_EQ(1, test_app_content_control_delegate()->restart_called()); + + // However - the restart in this test framework does not really restart and + // all objects should be gone now. + EXPECT_EQ(0, AppRegistry::Get()->NumberOfApplications()); +} + +} // namespace test +} // namespace athena diff --git a/athena/content/app_registry_impl.cc b/athena/content/app_registry_impl.cc new file mode 100644 index 000000000000..cd32719c9a1f --- /dev/null +++ b/athena/content/app_registry_impl.cc @@ -0,0 +1,108 @@ +// Copyright 2014 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 "athena/content/public/app_registry.h" + +#include "athena/content/app_activity_registry.h" +#include "athena/content/public/app_content_control_delegate.h" +#include "base/logging.h" + +namespace athena { + +class AppRegistryImpl : public AppRegistry { + public: + AppRegistryImpl(); + virtual ~AppRegistryImpl(); + + // AppRegistry: + virtual void SetDelegate(AppContentControlDelegate* delegate) OVERRIDE; + virtual AppContentControlDelegate* GetDelegate() OVERRIDE; + virtual AppActivityRegistry* GetAppActivityRegistry( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE; + virtual int NumberOfApplications() const OVERRIDE { return app_list_.size(); } + + private: + virtual void RemoveAppActivityRegistry( + AppActivityRegistry* registry) OVERRIDE; + + std::vector app_list_; + + scoped_ptr delegate_; + + DISALLOW_COPY_AND_ASSIGN(AppRegistryImpl); +}; + +namespace { + +AppRegistryImpl* instance = NULL; + +} // namespace + +AppRegistryImpl::AppRegistryImpl() : + delegate_(AppContentControlDelegate::CreateAppContentControlDelegate()) {} + +AppRegistryImpl::~AppRegistryImpl() { + DCHECK(app_list_.empty()); +} + +void AppRegistryImpl::SetDelegate(AppContentControlDelegate* delegate) { + DCHECK(delegate); + delegate_.reset(delegate); +} + +AppContentControlDelegate* AppRegistryImpl::GetDelegate() { + return delegate_.get(); +} + +AppActivityRegistry* AppRegistryImpl::GetAppActivityRegistry( + const std::string& app_id, + content::BrowserContext* browser_context) { + // Search for an existing proxy. + for (std::vector::iterator it = app_list_.begin(); + it != app_list_.end(); ++it) { + if ((*it)->app_id() == app_id && + (*it)->browser_context() == browser_context) + return *it; + } + + // Create and return a new application object. + AppActivityRegistry* app_activity_registry = + new AppActivityRegistry(app_id, browser_context); + app_list_.push_back(app_activity_registry); + return app_activity_registry; +} + +void AppRegistryImpl::RemoveAppActivityRegistry(AppActivityRegistry* registry) { + std::vector::iterator item = + std::find(app_list_.begin(), app_list_.end(), registry); + CHECK(item != app_list_.end()); + app_list_.erase(item); +} + +// static +void AppRegistry::Create() { + DCHECK(!instance); + instance = new AppRegistryImpl(); +} + +// static +AppRegistry* AppRegistry::Get() { + DCHECK(instance); + return instance; +} + +// static +void AppRegistry::ShutDown() { + DCHECK(instance); + delete instance; +} + +AppRegistry::AppRegistry() {} + +AppRegistry::~AppRegistry() { + instance = NULL; +} + +} // namespace athena diff --git a/athena/content/delegate/app_content_control_delegate_impl.cc b/athena/content/delegate/app_content_control_delegate_impl.cc new file mode 100644 index 000000000000..967be8f15fb2 --- /dev/null +++ b/athena/content/delegate/app_content_control_delegate_impl.cc @@ -0,0 +1,82 @@ +// Copyright 2014 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 "athena/content/public/app_content_control_delegate.h" + +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/site_instance.h" +#include "content/public/browser/web_contents.h" +#include "extensions/browser/api/app_runtime/app_runtime_api.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/common/constants.h" + +namespace athena { + +class AppContentControlDelegateImpl : public AppContentControlDelegate { + public: + AppContentControlDelegateImpl() {} + virtual ~AppContentControlDelegateImpl() {} + + // AppContentControlDelegate: + virtual bool UnloadApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE; + virtual bool RestartApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE; + virtual std::string GetApplicationID( + content::WebContents* web_contents) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AppContentControlDelegateImpl); +}; + +bool AppContentControlDelegateImpl::UnloadApplication( + const std::string& app_id, + content::BrowserContext* browser_context) { + // TODO(skuhne): Use the extension system to unload + // (|ExtensionService::TerminateExtension|) once it becomes available in + // Athena. + return false; +} + +bool AppContentControlDelegateImpl::RestartApplication( + const std::string& app_id, + content::BrowserContext* browser_context) { + // TODO(skuhne): As soon as the ExtensionSystem can be used, we should use the + // proper commands here for restarting. + const extensions::Extension* extension = + extensions::ExtensionRegistry::Get(browser_context)->GetExtensionById( + app_id, extensions::ExtensionRegistry::EVERYTHING); + DCHECK(extension); + extensions::AppRuntimeEventRouter::DispatchOnLaunchedEvent(browser_context, + extension); + return true; +} + +// Get the extension Id from a given |web_contents|. +std::string AppContentControlDelegateImpl::GetApplicationID( + content::WebContents* web_contents) { + content::RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); + // This works for both apps and extensions because the site has been + // normalized to the extension URL for hosted apps. + content::SiteInstance* site_instance = render_view_host->GetSiteInstance(); + if (!site_instance) + return std::string(); + + const GURL& site_url = site_instance->GetSiteURL(); + + if (!site_url.SchemeIs(extensions::kExtensionScheme)) + return std::string(); + + return site_url.host(); +} + +// static +AppContentControlDelegate* +AppContentControlDelegate::CreateAppContentControlDelegate() { + return new AppContentControlDelegateImpl; +} + +} // namespace athena diff --git a/athena/content/public/app_content_control_delegate.h b/athena/content/public/app_content_control_delegate.h new file mode 100644 index 000000000000..ad3e5538fe5e --- /dev/null +++ b/athena/content/public/app_content_control_delegate.h @@ -0,0 +1,40 @@ +// Copyright 2014 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 ATHENA_CONTENT_PUBLIC_APP_CONTENT_CONTROL_DLEGATE_H_ +#define ATHENA_CONTENT_PUBLIC_APP_CONTENT_CONTROL_DLEGATE_H_ + +#include + +#include "base/macros.h" + +namespace content { +class BrowserContext; +class WebContents; +} + +namespace athena { + +// The application content delegate which can be overwritten for unit tests to +// eliminate dependencies to the content / browser system. +class AppContentControlDelegate { + public: + static AppContentControlDelegate* CreateAppContentControlDelegate(); + + AppContentControlDelegate() {} + virtual ~AppContentControlDelegate() {} + + // Unload an application. Returns true when unloaded. + virtual bool UnloadApplication(const std::string& app_id, + content::BrowserContext* browser_context) = 0; + // Restarts an application. Returns true when the restart was initiated. + virtual bool RestartApplication(const std::string& app_id, + content::BrowserContext* browser_context) = 0; + // Returns the application ID (or an empty string) for a given web content. + virtual std::string GetApplicationID(content::WebContents* web_contents) = 0; +}; + +} // namespace athena + +#endif // ATHENA_CONTENT_PUBLIC_APP_CONTENT_CONTROL_DLEGATE_H_ diff --git a/athena/content/public/app_registry.h b/athena/content/public/app_registry.h new file mode 100644 index 000000000000..c296f2e3b780 --- /dev/null +++ b/athena/content/public/app_registry.h @@ -0,0 +1,72 @@ +// Copyright 2014 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 ATHENA_CONTENT_PUBLIC_APP_REGISTRY_H_ +#define ATHENA_CONTENT_PUBLIC_APP_REGISTRY_H_ + +#include +#include + +#include "athena/athena_export.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace content { +class BrowserContext; +} + +namespace athena { + +class AppActivityRegistry; +class AppContentControlDelegate; +class AppRegistryImpl; + +// This class holds for each application, held by a user, a list of activities. +// The list of activities can be retrieved as |AppActivityRegistry|. It is used +// to associate activities with applications and allow the resource manager to +// (re)start and stop applications. +class ATHENA_EXPORT AppRegistry { + public: + // Creates the AppRegistry instance. + static void Create(); + + // Gets the instance of the controller. + static AppRegistry* Get(); + + // Shuts down the registry (all applications should be shut down by then). + static void ShutDown(); + + // Overrides the used AppContentDelegate. This function will own it + // afterwards. A value of NULL is invalid. + virtual void SetDelegate(AppContentControlDelegate* delegate) = 0; + + // Retrieves the application content delegate. The ownership remains with this + // class. + virtual AppContentControlDelegate* GetDelegate() = 0; + + // Returns an |AppActivityRegistry| for a given activity |app_id| and + // |browser_context|. + virtual AppActivityRegistry* GetAppActivityRegistry( + const std::string& app_id, + content::BrowserContext* browser_context) = 0; + + // Returns the number of registered applications. + virtual int NumberOfApplications() const = 0; + + protected: + // Only the |AppActivityRegistry| can remove itself. + friend AppActivityRegistry; + + // Removes an activity registry for an application from the list of known + // applications. + virtual void RemoveAppActivityRegistry(AppActivityRegistry* registry) = 0; + + // Constructor and destructor can only be called by the implementing class. + AppRegistry(); + virtual ~AppRegistry(); +}; + +} // namespace athena + +#endif // ATHENA_CONTENT_PUBLIC_APP_REGISTRY_H_ diff --git a/athena/content/web_activity.cc b/athena/content/web_activity.cc index 35e0d71553d0..e4eb00688ee4 100644 --- a/athena/content/web_activity.cc +++ b/athena/content/web_activity.cc @@ -347,6 +347,10 @@ Activity::ActivityMediaState WebActivity::GetMediaState() { return Activity::ACTIVITY_MEDIA_STATE_NONE; } +aura::Window* WebActivity::GetWindow() { + return !web_view_ ? NULL : web_view_->GetWidget()->GetNativeWindow(); +} + void WebActivity::Init() { DCHECK(web_view_); web_view_->InstallAccelerators(); diff --git a/athena/content/web_activity.h b/athena/content/web_activity.h index 6e3f41ba0e0e..e421f6f621e6 100644 --- a/athena/content/web_activity.h +++ b/athena/content/web_activity.h @@ -39,6 +39,7 @@ class WebActivity : public Activity, virtual ActivityState GetCurrentState() OVERRIDE; virtual bool IsVisible() OVERRIDE; virtual ActivityMediaState GetMediaState() OVERRIDE; + virtual aura::Window* GetWindow() OVERRIDE; // ActivityViewModel: virtual void Init() OVERRIDE; diff --git a/athena/main/athena_launcher.cc b/athena/main/athena_launcher.cc index de4dbc748c62..21faf6bb7f69 100644 --- a/athena/main/athena_launcher.cc +++ b/athena/main/athena_launcher.cc @@ -6,6 +6,7 @@ #include "athena/activity/public/activity_factory.h" #include "athena/activity/public/activity_manager.h" +#include "athena/content/public/app_registry.h" #include "athena/content/public/content_activity_factory.h" #include "athena/content/public/content_app_model_builder.h" #include "athena/home/public/home_card.h" @@ -119,6 +120,7 @@ void StartAthenaEnv(aura::Window* root_window, athena::InputManager::Create()->OnRootWindowCreated(root_window); athena::ScreenManager::Create(delegate, root_window); athena::WindowManager::Create(); + athena::AppRegistry::Create(); SetupBackgroundImage(); athena::ScreenManager::Get()->GetContext()->SetProperty( @@ -151,6 +153,7 @@ void ShutdownAthena() { athena::ActivityFactory::Shutdown(); athena::ActivityManager::Shutdown(); athena::HomeCard::Shutdown(); + athena::AppRegistry::ShutDown(); athena::WindowManager::Shutdown(); athena::ScreenManager::Shutdown(); athena::InputManager::Shutdown(); diff --git a/athena/main/athena_main.gyp b/athena/main/athena_main.gyp index c4641a88493d..b9a075f66179 100644 --- a/athena/main/athena_main.gyp +++ b/athena/main/athena_main.gyp @@ -13,6 +13,7 @@ 'dependencies': [ '../athena.gyp:athena_lib', '../athena.gyp:athena_content_lib', + '../athena.gyp:athena_content_support_lib', '../resources/athena_resources.gyp:athena_resources', # debug_widow.cc depends on this. Remove this once debug_window # is removed. diff --git a/athena/test/DEPS b/athena/test/DEPS index ded0bbd43c8c..e938483b40a3 100644 --- a/athena/test/DEPS +++ b/athena/test/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+athena/activity", + "+athena/content/public", "+athena/home/public", "+athena/main", "+athena/screen/public", diff --git a/athena/test/sample_activity.cc b/athena/test/sample_activity.cc index 6c4c06486c90..8e36ea069bc3 100644 --- a/athena/test/sample_activity.cc +++ b/athena/test/sample_activity.cc @@ -6,6 +6,7 @@ #include "ui/views/background.h" #include "ui/views/view.h" +#include "ui/views/widget/widget.h" namespace athena { namespace test { @@ -43,6 +44,11 @@ Activity::ActivityMediaState SampleActivity::GetMediaState() { return Activity::ACTIVITY_MEDIA_STATE_NONE; } +aura::Window* SampleActivity::GetWindow() { + return + !contents_view_ ? NULL : contents_view_->GetWidget()->GetNativeWindow(); +} + void SampleActivity::Init() { } diff --git a/athena/test/sample_activity.h b/athena/test/sample_activity.h index 7f7432a3c430..a43d60986d03 100644 --- a/athena/test/sample_activity.h +++ b/athena/test/sample_activity.h @@ -28,6 +28,7 @@ class SampleActivity : public Activity, virtual ActivityState GetCurrentState() OVERRIDE; virtual bool IsVisible() OVERRIDE; virtual ActivityMediaState GetMediaState() OVERRIDE; + virtual aura::Window* GetWindow() OVERRIDE; // athena::ActivityViewModel: virtual void Init() OVERRIDE; diff --git a/athena/test/test_app_content_control_delegate_impl.cc b/athena/test/test_app_content_control_delegate_impl.cc new file mode 100644 index 000000000000..0cecfecd6e2b --- /dev/null +++ b/athena/test/test_app_content_control_delegate_impl.cc @@ -0,0 +1,53 @@ +// Copyright 2014 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 "athena/content/public/app_content_control_delegate.h" + +namespace athena { + +class AppContentControlDelegateImpl : public AppContentControlDelegate { + public: + AppContentControlDelegateImpl() {} + virtual ~AppContentControlDelegateImpl() {} + + virtual bool UnloadApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE; + virtual bool RestartApplication( + const std::string& app_id, + content::BrowserContext* browser_context) OVERRIDE; + virtual std::string GetApplicationID( + content::WebContents* web_contents) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AppContentControlDelegateImpl); +}; + +bool AppContentControlDelegateImpl::UnloadApplication( + const std::string& app_id, + content::BrowserContext* browser_context) { + // TODO(skuhne): Use the extension system to unload + // (|ExtensionService::TerminateExtension|) once it becomes available in + // Athena. + return false; +} + +bool AppContentControlDelegateImpl::RestartApplication( + const std::string& app_id, + content::BrowserContext* browser_context) { + return false; +} + +std::string AppContentControlDelegateImpl::GetApplicationID( + content::WebContents* web_contents) { + return std::string(); +} + +// static +AppContentControlDelegate* +AppContentControlDelegate::CreateAppContentControlDelegate() { + return new AppContentControlDelegateImpl; +} + +} // namespace athena