diff options
author | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-21 22:57:10 +0000 |
---|---|---|
committer | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-21 22:58:56 +0000 |
commit | 23e145ef38c3dc1ebadc7674ae3e295bec8fb302 (patch) | |
tree | 2371de1911f242912d3169380c9ae74e77d8c049 /athena | |
parent | 65582b360cf09240904808836072cb218315b6d3 (diff) | |
download | chromium_src-23e145ef38c3dc1ebadc7674ae3e295bec8fb302.zip chromium_src-23e145ef38c3dc1ebadc7674ae3e295bec8fb302.tar.gz chromium_src-23e145ef38c3dc1ebadc7674ae3e295bec8fb302.tar.bz2 |
Athena: Adding basic resource management framework (un-/re-loading) of V2 applications
Functionality:
The |AppRegistry| has for each running application an |AppActivityRegistry|.
The |AppActivityRegistry| knows all activities associated with the application it represents.
It can furthermore shut the app entirely down upon resource manager request. It will then create
an |AppActivityProxy| for the overview mode which shows a placeholder for an unloaded app. This
placeholder can then ask the |AppActivityRegistry| to restart the application again.
A shutdown request for the application is only performed when all activities were marked for
UNLOAD.
If there were multiple activities upon shutdown for one app, the app has to take care of
re-creating all windows and thus re-creating all activities. Since an activity match cannot
be performed, the |AppActivityProxy| will only be shown once and it will show in the location
of the most recently used activity of that app. If we later on find an app which really uses
multiple windows and it is imperative to keep the history for all of them tact & the app is
recreating them properly, (a lot of if's) we can revisit the single |AppActivityProxy| and
try to address it in a cleaner way, but at this time that seems rather un-useful since it is
not known if required.
BUG=388085
TEST=AppActivityTest.*
Review URL: https://codereview.chromium.org/477523002
Cr-Commit-Position: refs/heads/master@{#291221}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@291221 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'athena')
22 files changed, 1287 insertions, 23 deletions
diff --git a/athena/activity/public/activity.h b/athena/activity/public/activity.h index 74a6c41..041449b 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 448ba4e..6335641 100644 --- a/athena/athena.gyp +++ b/athena/athena.gyp @@ -115,23 +115,40 @@ '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', 'dependencies': [ @@ -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 0000000..b03b46b --- /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 c90daff..309454a 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 4f6ef2b..105f728 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<content::FaviconURL>& 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<extensions::ShellAppWindow> 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 0000000..222e697 --- /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 0000000..c5092e8 --- /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 <vector> + +#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 0000000..dc7bce9 --- /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<AppActivity*>::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<AppActivity*>::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<AppActivity*>::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 0000000..14ef7de --- /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 <vector> + +#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<AppActivity*> 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 0000000..f94cc85 --- /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 0000000..cd32719 --- /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<AppActivityRegistry*> app_list_; + + scoped_ptr<AppContentControlDelegate> 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<AppActivityRegistry*>::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<AppActivityRegistry*>::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 0000000..967be8f --- /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 0000000..ad3e553 --- /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 <string> + +#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 0000000..c296f2e --- /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 <string> +#include <vector> + +#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 35e0d71..e4eb006 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 6e3f41b..e421f6f 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 de4dbc7..21faf6b 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 c4641a8..b9a075f 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 ded0bbd..e938483 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 6c4c064..8e36ea0 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 7f7432a..a43d609 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 0000000..0cecfec --- /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 |