summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ash/ash_switches.cc3
-rw-r--r--ash/ash_switches.h1
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/about_flags.cc7
-rw-r--r--chrome/browser/ui/ash/chrome_shell_delegate.cc2
-rw-r--r--chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc8
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc1081
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller.h261
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc1107
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h341
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc (renamed from chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc)34
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc1115
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h343
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc280
-rw-r--r--chrome/chrome_browser_ui.gypi4
-rw-r--r--chrome/chrome_tests_unit.gypi3
16 files changed, 3328 insertions, 1268 deletions
diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc
index f9a1ff7..7f93803 100644
--- a/ash/ash_switches.cc
+++ b/ash/ash_switches.cc
@@ -51,6 +51,9 @@ const char kAshEnableAdvancedGestures[] = "ash-enable-advanced-gestures";
const char kAshEnableMemoryMonitor[] = "ash-enable-memory-monitor";
#endif
+// Enable the per application grouping version of the launcher.
+const char kAshEnablePerAppLauncher[] = "ash-enable-per-app-launcher";
+
// Enables the Oak tree viewer.
const char kAshEnableOak[] = "ash-enable-oak";
diff --git a/ash/ash_switches.h b/ash/ash_switches.h
index f31f079..89518ca 100644
--- a/ash/ash_switches.h
+++ b/ash/ash_switches.h
@@ -30,6 +30,7 @@ ASH_EXPORT extern const char kAshEnableAdvancedGestures[];
#if defined(OS_LINUX)
ASH_EXPORT extern const char kAshEnableMemoryMonitor[];
#endif
+ASH_EXPORT extern const char kAshEnablePerAppLauncher[];
ASH_EXPORT extern const char kAshEnableOak[];
ASH_EXPORT extern const char kAshEnableTrayDragging[];
ASH_EXPORT extern const char kAshImmersive[];
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8e2d15f..f6a4a70 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6701,6 +6701,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_ASH_AUTO_WINDOW_PLACEMENT_DESCRIPTION" desc="Description for the option to enable/disable the auto window placement functionality.">
Disable automatic window placement for one and two browser / app windows.
</message>
+ <message name="IDS_FLAGS_ASH_ENABLE_PER_APP_LAUNCHER_NAME" desc="Name for the option to enable/disable the per application sorting launcher functionality.">
+ Per application sorting in the launcher.
+ </message>
+ <message name="IDS_FLAGS_ASH_ENABLE_PER_APP_LAUNCHER_DESCRIPTION" desc="Description for the option to enable/disable the per application sorting launcher functionality.">
+ Enable the per application sorting mode of the launcher.
+ </message>
<message name="IDS_FLAGS_AURA_DISABLE_HOLD_MOUSE_MOVES_NAME" desc="Title for the flag to disable throttling the rate of window resize.">
Throttle the rate of window resize.
</message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8ca2a1c..7c72719 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -659,6 +659,13 @@ const Experiment kExperiments[] = {
kOsWin | kOsLinux | kOsCrOS,
SINGLE_VALUE_TYPE(ash::switches::kAshDisableAutoWindowPlacement)
},
+ {
+ "ash-enable-per-app-launcher",
+ IDS_FLAGS_ASH_ENABLE_PER_APP_LAUNCHER_NAME,
+ IDS_FLAGS_ASH_ENABLE_PER_APP_LAUNCHER_DESCRIPTION,
+ kOsWin | kOsLinux | kOsCrOS,
+ SINGLE_VALUE_TYPE(ash::switches::kAshEnablePerAppLauncher)
+ },
#endif
{
"per-tile-painting",
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index 6c555a6..d911fca 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -393,7 +393,7 @@ ash::LauncherDelegate* ChromeShellDelegate::CreateLauncherDelegate(
// Refactor so that there is just one launcher delegate in the
// shell.
if (!launcher_delegate_) {
- launcher_delegate_ = new ChromeLauncherController(NULL, model);
+ launcher_delegate_ = ChromeLauncherController::CreateInstance(NULL, model);
launcher_delegate_->Init();
}
return launcher_delegate_;
diff --git a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc
index 538e642..b197d57 100644
--- a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc
@@ -115,7 +115,8 @@ class BrowserLauncherItemControllerTest
new aura::test::TestActivationClient(root_window()));
launcher_model_.reset(new ash::LauncherModel);
launcher_delegate_.reset(
- new ChromeLauncherController(profile(), launcher_model_.get()));
+ ChromeLauncherController::CreateInstance(profile(),
+ launcher_model_.get()));
app_tab_helper_ = new AppTabHelperImpl;
app_icon_loader_ = new AppIconLoaderImpl;
launcher_delegate_->SetAppTabHelperForTest(app_tab_helper_);
@@ -179,7 +180,7 @@ class BrowserLauncherItemControllerTest
};
const std::string& GetAppID(ash::LauncherID id) const {
- return launcher_delegate_->id_to_item_controller_map_[id]->app_id();
+ return launcher_delegate_->GetAppIdFromLauncherIdForTest(id);
}
void ResetAppTabHelper() {
@@ -319,7 +320,8 @@ TEST_F(BrowserLauncherItemControllerTest, PersistPinned) {
EXPECT_EQ(initial_size + 1, launcher_model_->items().size());
launcher_delegate_.reset(
- new ChromeLauncherController(profile(), launcher_model_.get()));
+ ChromeLauncherController::CreateInstance(profile(),
+ launcher_model_.get()));
app_tab_helper_ = new AppTabHelperImpl;
app_tab_helper_->SetAppID(tab1.get(), "1");
ResetAppTabHelper();
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index f48a815..da647f4 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -4,1079 +4,30 @@
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include <vector>
-
-#include "ash/launcher/launcher_model.h"
-#include "ash/shell.h"
-#include "ash/wm/window_util.h"
+#include "ash/ash_switches.h"
#include "base/command_line.h"
-#include "base/values.h"
-#include "chrome/browser/defaults.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/prefs/pref_service.h"
-#include "chrome/browser/prefs/scoped_user_pref_update.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/ash/app_sync_ui_state.h"
-#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
-#include "chrome/browser/ui/ash/extension_utils.h"
-#include "chrome/browser/ui/ash/launcher/launcher_app_icon_loader.h"
-#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
-#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
-#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
-#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/tab_contents/tab_contents.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/common/chrome_notification_types.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/extension.h"
-#include "chrome/common/extensions/extension_resource.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/common/url_constants.h"
-#include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/common/url_pattern.h"
-#include "grit/theme_resources.h"
-#include "ui/aura/window.h"
-
-using extensions::Extension;
-
-namespace {
-
-// Item controller for an app shortcut. Shortcuts track app and launcher ids,
-// but do not have any associated windows (opening a shortcut will replace the
-// item with the appropriate LauncherItemController type).
-class AppShortcutLauncherItemController : public LauncherItemController {
- public:
- AppShortcutLauncherItemController(const std::string& app_id,
- ChromeLauncherController* controller)
- : LauncherItemController(TYPE_SHORTCUT, app_id, controller) {
- // Google Drive should just refocus to it's main app UI.
- // TODO(davemoore): Generalize this for other applications.
- if (app_id == "apdfllckaahabafndbhieahigkjlhalf") {
- const Extension* extension =
- launcher_controller()->GetExtensionForAppID(app_id);
- refocus_url_ = GURL(extension->launch_web_url() + "*");
- }
- }
-
- virtual ~AppShortcutLauncherItemController() {}
-
- // LauncherItemController overrides:
- virtual string16 GetTitle() OVERRIDE {
- return GetAppTitle();
- }
-
- virtual bool HasWindow(aura::Window* window) const OVERRIDE {
- return false;
- }
-
- virtual bool IsOpen() const OVERRIDE {
- return false;
- }
-
- virtual void Launch(int event_flags) OVERRIDE {
- launcher_controller()->LaunchApp(app_id(), event_flags);
- }
-
- virtual void Activate() OVERRIDE {
- launcher_controller()->ActivateApp(app_id(), ui::EF_NONE);
- }
-
- virtual void Close() OVERRIDE {
- // TODO: maybe should treat as unpin?
- }
-
- virtual void Clicked() OVERRIDE {
- Activate();
- }
-
- virtual void OnRemoved() OVERRIDE {
- // AppShortcutLauncherItemController is unowned; delete on removal.
- delete this;
- }
-
- virtual void LauncherItemChanged(int model_index,
- const ash::LauncherItem& old_item) OVERRIDE {
- }
-
- // Stores the optional refocus url pattern for this item.
- const GURL& refocus_url() const { return refocus_url_; }
- void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; }
-
- private:
- GURL refocus_url_;
- DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController);
-};
-
-// If the value of the pref at |local_path is not empty, it is returned
-// otherwise the value of the pref at |synced_path| is returned.
-std::string GetLocalOrRemotePref(PrefService* pref_service,
- const char* local_path,
- const char* synced_path) {
- const std::string value(pref_service->GetString(local_path));
- return value.empty() ? pref_service->GetString(synced_path) : value;
-}
-
-// If prefs have synced and the pref value at |local_path| is empty the value
-// from |synced_path| is copied to |local_path|.
-void MaybePropagatePrefToLocal(PrefService* pref_service,
- const char* local_path,
- const char* synced_path) {
- if (pref_service->GetString(local_path).empty() &&
- pref_service->IsSyncing()) {
- // First time the user is using this machine, propagate from remote to
- // local.
- pref_service->SetString(local_path, pref_service->GetString(synced_path));
- }
-}
-
-} // namespace
-
-// ChromeLauncherController ----------------------------------------------------
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h"
// statics
ChromeLauncherController* ChromeLauncherController::instance_ = NULL;
-ChromeLauncherController::ChromeLauncherController(Profile* profile,
- ash::LauncherModel* model)
- : model_(model),
- profile_(profile),
- app_sync_ui_state_(NULL) {
- if (!profile_) {
- // Use the original profile as on chromeos we may get a temporary off the
- // record profile.
- profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile();
-
- app_sync_ui_state_ = AppSyncUIState::Get(profile_);
- if (app_sync_ui_state_)
- app_sync_ui_state_->AddObserver(this);
- }
-
- instance_ = this;
- model_->AddObserver(this);
- // TODO(stevenjb): Find a better owner for shell_window_controller_?
- shell_window_controller_.reset(new ShellWindowLauncherController(this));
- app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
- app_icon_loader_.reset(new LauncherAppIconLoader(profile_, this));
-
- notification_registrar_.Add(this,
- chrome::NOTIFICATION_EXTENSION_LOADED,
- content::Source<Profile>(profile_));
- notification_registrar_.Add(this,
- chrome::NOTIFICATION_EXTENSION_UNLOADED,
- content::Source<Profile>(profile_));
- pref_change_registrar_.Init(profile_->GetPrefs());
- pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
+// static
+ChromeLauncherController* ChromeLauncherController::CreateInstance(
+ Profile* profile,
+ ash::LauncherModel* model) {
+ // We do not check here for re-creation of the ChromeLauncherController since
+ // it appears that it might be intentional that the ChromeLauncherController
+ // can be re-created.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kAshEnablePerAppLauncher))
+ instance_ = new ChromeLauncherControllerPerBrowser(profile, model);
+ else
+ instance_ = new ChromeLauncherControllerPerApp(profile, model);
+ return instance_;
}
ChromeLauncherController::~ChromeLauncherController() {
- // Reset the shell window controller here since it has a weak pointer to this.
- shell_window_controller_.reset();
-
- model_->RemoveObserver(this);
- for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ++i) {
- i->second->OnRemoved();
- model_->RemoveItemAt(model_->ItemIndexByID(i->first));
- }
if (instance_ == this)
instance_ = NULL;
-
- if (ash::Shell::HasInstance())
- ash::Shell::GetInstance()->RemoveShellObserver(this);
-
- if (app_sync_ui_state_)
- app_sync_ui_state_->RemoveObserver(this);
-
- profile_->GetPrefs()->RemoveObserver(this);
-}
-
-void ChromeLauncherController::Init() {
- // TODO(xiyuan): Remove migration code and kUseDefaultPinnedApp after M20.
- // Migration cases:
- // - Users that unpin all apps:
- // - have default pinned apps
- // - kUseDefaultPinnedApps set to false
- // Migrate them by setting an empty list for kPinnedLauncherApps.
- //
- // - Users that have customized pinned apps:
- // - have non-default non-empty pinned apps list
- // - kUseDefaultPinnedApps set to false
- // Nothing needs to be done because customized pref overrides default.
- //
- // - Users that have default apps (i.e. new user or never pin/unpin):
- // - have default pinned apps
- // - kUseDefaultPinnedApps is still true
- // Nothing needs to be done because they should get the default.
- if (profile_->GetPrefs()->FindPreference(
- prefs::kPinnedLauncherApps)->IsDefaultValue() &&
- !profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) {
- ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
- updater.Get()->Clear();
- }
-
- UpdateAppLaunchersFromPref();
-
- // TODO(sky): update unit test so that this test isn't necessary.
- if (ash::Shell::HasInstance()) {
- SetShelfAutoHideBehaviorFromPrefs();
- SetShelfAlignmentFromPrefs();
- PrefService* prefs = profile_->GetPrefs();
- if (prefs->GetString(prefs::kShelfAlignmentLocal).empty() ||
- prefs->GetString(prefs::kShelfAutoHideBehaviorLocal).empty()) {
- prefs->AddObserver(this);
- }
- ash::Shell::GetInstance()->AddShellObserver(this);
- }
-}
-
-ash::LauncherID ChromeLauncherController::CreateTabbedLauncherItem(
- LauncherItemController* controller,
- IncognitoState is_incognito,
- ash::LauncherItemStatus status) {
- ash::LauncherID id = model_->next_id();
- DCHECK(!HasItemController(id));
- DCHECK(controller);
- id_to_item_controller_map_[id] = controller;
- controller->set_launcher_id(id);
-
- ash::LauncherItem item;
- item.type = ash::TYPE_TABBED;
- item.is_incognito = (is_incognito == STATE_INCOGNITO);
- item.status = status;
- model_->Add(item);
- return id;
-}
-
-ash::LauncherID ChromeLauncherController::CreateAppLauncherItem(
- LauncherItemController* controller,
- const std::string& app_id,
- ash::LauncherItemStatus status) {
- DCHECK(controller);
- return InsertAppLauncherItem(controller, app_id, status,
- model_->item_count());
-}
-
-void ChromeLauncherController::SetItemStatus(ash::LauncherID id,
- ash::LauncherItemStatus status) {
- int index = model_->ItemIndexByID(id);
- DCHECK_GE(index, 0);
- ash::LauncherItem item = model_->items()[index];
- item.status = status;
- model_->Set(index, item);
-}
-
-void ChromeLauncherController::SetItemController(
- ash::LauncherID id,
- LauncherItemController* controller) {
- IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
- DCHECK(iter != id_to_item_controller_map_.end());
- iter->second->OnRemoved();
- iter->second = controller;
- controller->set_launcher_id(id);
-}
-
-void ChromeLauncherController::CloseLauncherItem(ash::LauncherID id) {
- if (IsPinned(id)) {
- // Create a new shortcut controller.
- IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
- DCHECK(iter != id_to_item_controller_map_.end());
- SetItemStatus(id, ash::STATUS_CLOSED);
- std::string app_id = iter->second->app_id();
- iter->second->OnRemoved();
- iter->second = new AppShortcutLauncherItemController(app_id, this);
- iter->second->set_launcher_id(id);
- } else {
- LauncherItemClosed(id);
- }
-}
-
-void ChromeLauncherController::Unpin(ash::LauncherID id) {
- DCHECK(HasItemController(id));
-
- LauncherItemController* controller = id_to_item_controller_map_[id];
- if (controller->type() == LauncherItemController::TYPE_APP) {
- int index = model_->ItemIndexByID(id);
- ash::LauncherItem item = model_->items()[index];
- item.type = ash::TYPE_PLATFORM_APP;
- model_->Set(index, item);
- } else {
- LauncherItemClosed(id);
- }
- if (CanPin())
- PersistPinnedState();
-}
-
-void ChromeLauncherController::Pin(ash::LauncherID id) {
- DCHECK(HasItemController(id));
-
- int index = model_->ItemIndexByID(id);
- ash::LauncherItem item = model_->items()[index];
-
- if (item.type != ash::TYPE_PLATFORM_APP)
- return;
-
- item.type = ash::TYPE_APP_SHORTCUT;
- model_->Set(index, item);
-
- if (CanPin())
- PersistPinnedState();
-}
-
-bool ChromeLauncherController::IsPinned(ash::LauncherID id) {
- int index = model_->ItemIndexByID(id);
- ash::LauncherItemType type = model_->items()[index].type;
- return type == ash::TYPE_APP_SHORTCUT;
-}
-
-void ChromeLauncherController::TogglePinned(ash::LauncherID id) {
- if (!HasItemController(id))
- return; // May happen if item closed with menu open.
-
- if (IsPinned(id))
- Unpin(id);
- else
- Pin(id);
-}
-
-bool ChromeLauncherController::IsPinnable(ash::LauncherID id) const {
- int index = model_->ItemIndexByID(id);
- if (index == -1)
- return false;
-
- ash::LauncherItemType type = model_->items()[index].type;
- return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) &&
- CanPin());
-}
-
-void ChromeLauncherController::Launch(ash::LauncherID id, int event_flags) {
- if (!HasItemController(id))
- return; // In case invoked from menu and item closed while menu up.
- id_to_item_controller_map_[id]->Launch(event_flags);
-}
-
-void ChromeLauncherController::Close(ash::LauncherID id) {
- if (!HasItemController(id))
- return; // May happen if menu closed.
- id_to_item_controller_map_[id]->Close();
-}
-
-bool ChromeLauncherController::IsOpen(ash::LauncherID id) {
- if (!HasItemController(id))
- return false;
- return id_to_item_controller_map_[id]->IsOpen();
-}
-
-bool ChromeLauncherController::IsPlatformApp(ash::LauncherID id) {
- if (!HasItemController(id))
- return false;
-
- std::string app_id = GetAppIDForLauncherID(id);
- const Extension* extension = GetExtensionForAppID(app_id);
- DCHECK(extension);
- return extension->is_platform_app();
-}
-
-void ChromeLauncherController::LaunchApp(const std::string& app_id,
- int event_flags) {
- const Extension* extension = GetExtensionForAppID(app_id);
- extension_utils::OpenExtension(GetProfileForNewWindows(),
- extension,
- event_flags);
-}
-
-void ChromeLauncherController::ActivateApp(const std::string& app_id,
- int event_flags) {
- if (app_id == extension_misc::kChromeAppId) {
- OnBrowserShortcutClicked(event_flags);
- return;
- }
-
- // If there is an existing non-shortcut controller for this app, open it.
- ash::LauncherID id = GetLauncherIDForAppID(app_id);
- URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
- refocus_pattern.SetMatchAllURLs(true);
-
- if (id > 0) {
- LauncherItemController* controller = id_to_item_controller_map_[id];
- if (controller->type() != LauncherItemController::TYPE_SHORTCUT) {
- controller->Activate();
- return;
- }
-
- AppShortcutLauncherItemController* app_controller =
- static_cast<AppShortcutLauncherItemController*>(controller);
- const GURL refocus_url = app_controller->refocus_url();
-
- if (!refocus_url.is_empty())
- refocus_pattern.Parse(refocus_url.spec());
- }
-
- // Check if there are any open tabs for this app.
- AppIDToTabContentsListMap::iterator app_i =
- app_id_to_tab_contents_list_.find(app_id);
- if (app_i != app_id_to_tab_contents_list_.end()) {
- for (TabContentsList::iterator tab_i = app_i->second.begin();
- tab_i != app_i->second.end();
- ++tab_i) {
- TabContents* tab = *tab_i;
- const GURL tab_url = tab->web_contents()->GetURL();
- if (refocus_pattern.MatchesURL(tab_url)) {
- Browser* browser = browser::FindBrowserWithWebContents(
- tab->web_contents());
- TabStripModel* tab_strip = browser->tab_strip_model();
- int index = tab_strip->GetIndexOfTabContents(tab);
- DCHECK_NE(TabStripModel::kNoTab, index);
- tab_strip->ActivateTabAt(index, false);
- browser->window()->Show();
- ash::wm::ActivateWindow(browser->window()->GetNativeWindow());
- return;
- }
- }
- }
-
- LaunchApp(app_id, event_flags);
-}
-
-extensions::ExtensionPrefs::LaunchType ChromeLauncherController::GetLaunchType(
- ash::LauncherID id) {
- DCHECK(HasItemController(id));
-
- const Extension* extension = GetExtensionForAppID(
- id_to_item_controller_map_[id]->app_id());
- return profile_->GetExtensionService()->extension_prefs()->GetLaunchType(
- extension,
- extensions::ExtensionPrefs::LAUNCH_DEFAULT);
-}
-
-std::string ChromeLauncherController::GetAppID(content::WebContents* tab) {
- return app_tab_helper_->GetAppID(tab);
-}
-
-ash::LauncherID ChromeLauncherController::GetLauncherIDForAppID(
- const std::string& app_id) {
- for (IDToItemControllerMap::const_iterator i =
- id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ++i) {
- if (i->second->app_id() == app_id)
- return i->first;
- }
- return 0;
-}
-
-std::string ChromeLauncherController::GetAppIDForLauncherID(
- ash::LauncherID id) {
- DCHECK(HasItemController(id));
- return id_to_item_controller_map_[id]->app_id();
-}
-
-void ChromeLauncherController::SetAppImage(const std::string& id,
- const gfx::ImageSkia& image) {
- // TODO: need to get this working for shortcuts.
-
- for (IDToItemControllerMap::const_iterator i =
- id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ++i) {
- if (i->second->app_id() != id)
- continue;
-
- // Panel items may share the same app_id as the app that created them,
- // but they set their icon image in
- // BrowserLauncherItemController::UpdateLauncher(), so do not set panel
- // images here.
- if (i->second->type() == LauncherItemController::TYPE_EXTENSION_PANEL)
- continue;
-
- int index = model_->ItemIndexByID(i->first);
- ash::LauncherItem item = model_->items()[index];
- item.image = image;
- model_->Set(index, item);
- // It's possible we're waiting on more than one item, so don't break.
- }
-}
-
-bool ChromeLauncherController::IsAppPinned(const std::string& app_id) {
- for (IDToItemControllerMap::const_iterator i =
- id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ++i) {
- if (IsPinned(i->first) && i->second->app_id() == app_id)
- return true;
- }
- return false;
-}
-
-void ChromeLauncherController::PinAppWithID(const std::string& app_id) {
- if (CanPin())
- DoPinAppWithID(app_id);
- else
- NOTREACHED();
-}
-
-void ChromeLauncherController::SetLaunchType(
- ash::LauncherID id,
- extensions::ExtensionPrefs::LaunchType launch_type) {
- if (!HasItemController(id))
- return;
-
- return profile_->GetExtensionService()->extension_prefs()->SetLaunchType(
- id_to_item_controller_map_[id]->app_id(), launch_type);
-}
-
-void ChromeLauncherController::UnpinAppsWithID(const std::string& app_id) {
- if (CanPin())
- DoUnpinAppsWithID(app_id);
- else
- NOTREACHED();
-}
-
-bool ChromeLauncherController::IsLoggedInAsGuest() {
- return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord();
-}
-
-void ChromeLauncherController::CreateNewWindow() {
- chrome::NewEmptyWindow(
- GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH);
-}
-
-void ChromeLauncherController::CreateNewIncognitoWindow() {
- chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile());
-}
-
-bool ChromeLauncherController::CanPin() const {
- const PrefService::Preference* pref =
- profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
- return pref && pref->IsUserModifiable();
-}
-
-void ChromeLauncherController::SetAutoHideBehavior(
- ash::ShelfAutoHideBehavior behavior,
- aura::RootWindow* root_window) {
- ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
- behavior,
- root_window);
- // TODO(oshima): Support multiple launcher.
- if (root_window != ash::Shell::GetPrimaryRootWindow())
- return;
-
- const char* value = NULL;
- switch (behavior) {
- case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
- value = ash::kShelfAutoHideBehaviorAlways;
- break;
- case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
- value = ash::kShelfAutoHideBehaviorNever;
- break;
- }
- // See comment in |kShelfAlignment| about why we have two prefs here.
- profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
- profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
-}
-
-void ChromeLauncherController::RemoveTabFromRunningApp(
- TabContents* tab,
- const std::string& app_id) {
- tab_contents_to_app_id_.erase(tab);
- AppIDToTabContentsListMap::iterator i_app_id =
- app_id_to_tab_contents_list_.find(app_id);
- if (i_app_id != app_id_to_tab_contents_list_.end()) {
- TabContentsList* tab_list = &i_app_id->second;
- tab_list->remove(tab);
- if (tab_list->empty()) {
- app_id_to_tab_contents_list_.erase(i_app_id);
- i_app_id = app_id_to_tab_contents_list_.end();
- ash::LauncherID id = GetLauncherIDForAppID(app_id);
- if (id > 0)
- SetItemStatus(id, ash::STATUS_CLOSED);
- }
- }
-}
-
-void ChromeLauncherController::UpdateAppState(content::WebContents* contents,
- AppState app_state) {
- std::string app_id = GetAppID(contents);
-
- // Check the old |app_id| for a tab. If the contents has changed we need to
- // remove it from the previous app.
- TabContents* tab = TabContents::FromWebContents(contents);
- if (tab_contents_to_app_id_.find(tab) != tab_contents_to_app_id_.end()) {
- std::string last_app_id = tab_contents_to_app_id_[tab];
- if (last_app_id != app_id)
- RemoveTabFromRunningApp(tab, last_app_id);
- }
-
- if (app_id.empty())
- return;
-
- tab_contents_to_app_id_[tab] = app_id;
-
- if (app_state == APP_STATE_REMOVED) {
- // The tab has gone away.
- RemoveTabFromRunningApp(tab, app_id);
- } else {
- TabContentsList& tab_list(app_id_to_tab_contents_list_[app_id]);
-
- if (app_state == APP_STATE_INACTIVE) {
- TabContentsList::const_iterator i_tab =
- std::find(tab_list.begin(), tab_list.end(), tab);
- if (i_tab == tab_list.end())
- tab_list.push_back(tab);
- if (i_tab != tab_list.begin()) {
- // Going inactive, but wasn't the front tab, indicating that a new
- // tab has already become active.
- return;
- }
- } else {
- tab_list.remove(tab);
- tab_list.push_front(tab);
- }
- ash::LauncherID id = GetLauncherIDForAppID(app_id);
- if (id > 0) {
- // If the window is active, mark the app as active.
- SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
- ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
- }
- }
-}
-
-void ChromeLauncherController::SetRefocusURLPattern(
- ash::LauncherID id,
- const GURL& url) {
- DCHECK(HasItemController(id));
- LauncherItemController* controller = id_to_item_controller_map_[id];
-
- int index = model_->ItemIndexByID(id);
- if (index == -1) {
- NOTREACHED() << "Invalid launcher id";
- return;
- }
-
- ash::LauncherItemType type = model_->items()[index].type;
- if (type == ash::TYPE_APP_SHORTCUT) {
- AppShortcutLauncherItemController* app_controller =
- static_cast<AppShortcutLauncherItemController*>(controller);
- app_controller->set_refocus_url(url);
- } else {
- NOTREACHED() << "Invalid launcher type";
- }
-}
-
-const Extension* ChromeLauncherController::GetExtensionForAppID(
- const std::string& app_id) {
- return profile_->GetExtensionService()->GetInstalledExtension(app_id);
-}
-
-void ChromeLauncherController::OnBrowserShortcutClicked(int event_flags) {
- if (event_flags & ui::EF_CONTROL_DOWN) {
- CreateNewWindow();
- return;
- }
-
- Browser* last_browser = browser::FindTabbedBrowser(
- GetProfileForNewWindows(), true, chrome::HOST_DESKTOP_TYPE_ASH);
-
- if (!last_browser) {
- CreateNewWindow();
- return;
- }
-
- aura::Window* window = last_browser->window()->GetNativeWindow();
- window->Show();
- ash::wm::ActivateWindow(window);
-}
-
-void ChromeLauncherController::ItemClicked(const ash::LauncherItem& item,
- int event_flags) {
- DCHECK(HasItemController(item.id));
- id_to_item_controller_map_[item.id]->Clicked();
-}
-
-int ChromeLauncherController::GetBrowserShortcutResourceId() {
- return IDR_PRODUCT_LOGO_32;
-}
-
-string16 ChromeLauncherController::GetTitle(const ash::LauncherItem& item) {
- DCHECK(HasItemController(item.id));
- return id_to_item_controller_map_[item.id]->GetTitle();
-}
-
-ui::MenuModel* ChromeLauncherController::CreateContextMenu(
- const ash::LauncherItem& item,
- aura::RootWindow* root_window) {
- return new LauncherContextMenu(this, &item, root_window);
-}
-
-ash::LauncherID ChromeLauncherController::GetIDByWindow(
- aura::Window* window) {
- for (IDToItemControllerMap::const_iterator i =
- id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ++i) {
- if (i->second->HasWindow(window))
- return i->first;
- }
- return 0;
-}
-
-bool ChromeLauncherController::IsDraggable(const ash::LauncherItem& item) {
- return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true;
-}
-
-void ChromeLauncherController::LauncherItemAdded(int index) {
-}
-
-void ChromeLauncherController::LauncherItemRemoved(int index,
- ash::LauncherID id) {
-}
-
-void ChromeLauncherController::LauncherItemMoved(
- int start_index,
- int target_index) {
- ash::LauncherID id = model_->items()[target_index].id;
- if (HasItemController(id) && IsPinned(id))
- PersistPinnedState();
-}
-
-void ChromeLauncherController::LauncherItemChanged(
- int index,
- const ash::LauncherItem& old_item) {
- ash::LauncherID id = model_->items()[index].id;
- id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item);
-}
-
-void ChromeLauncherController::LauncherStatusChanged() {
-}
-
-void ChromeLauncherController::Observe(
- int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) {
- switch (type) {
- case chrome::NOTIFICATION_EXTENSION_LOADED: {
- UpdateAppLaunchersFromPref();
- break;
- }
- case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
- const content::Details<extensions::UnloadedExtensionInfo> unload_info(
- details);
- const Extension* extension = unload_info->extension;
- if (IsAppPinned(extension->id()))
- DoUnpinAppsWithID(extension->id());
- app_icon_loader_->ClearImage(extension->id());
- break;
- }
- default:
- NOTREACHED() << "Unexpected notification type=" << type;
- }
-}
-
-void ChromeLauncherController::OnPreferenceChanged(
- PrefServiceBase* service,
- const std::string& pref_name) {
- if (pref_name == prefs::kPinnedLauncherApps) {
- UpdateAppLaunchersFromPref();
- } else if (pref_name == prefs::kShelfAlignmentLocal) {
- SetShelfAlignmentFromPrefs();
- } else if (pref_name == prefs::kShelfAutoHideBehaviorLocal) {
- SetShelfAutoHideBehaviorFromPrefs();
- } else {
- NOTREACHED() << "Unexpected pref change for " << pref_name;
- }
-}
-
-void ChromeLauncherController::OnShelfAlignmentChanged() {
- const char* pref_value = NULL;
- // TODO(oshima): Support multiple displays.
- switch (ash::Shell::GetInstance()->GetShelfAlignment(
- ash::Shell::GetPrimaryRootWindow())) {
- case ash::SHELF_ALIGNMENT_BOTTOM:
- pref_value = ash::kShelfAlignmentBottom;
- break;
- case ash::SHELF_ALIGNMENT_LEFT:
- pref_value = ash::kShelfAlignmentLeft;
- break;
- case ash::SHELF_ALIGNMENT_RIGHT:
- pref_value = ash::kShelfAlignmentRight;
- break;
- }
- // See comment in |kShelfAlignment| about why we have two prefs here.
- profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
- profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
-}
-
-void ChromeLauncherController::OnIsSyncingChanged() {
- MaybePropagatePrefToLocal(profile_->GetPrefs(),
- prefs::kShelfAlignmentLocal,
- prefs::kShelfAlignment);
- MaybePropagatePrefToLocal(profile_->GetPrefs(),
- prefs::kShelfAutoHideBehaviorLocal,
- prefs::kShelfAutoHideBehavior);
-}
-
-void ChromeLauncherController::OnAppSyncUIStatusChanged() {
- if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
- model_->SetStatus(ash::LauncherModel::STATUS_LOADING);
- else
- model_->SetStatus(ash::LauncherModel::STATUS_NORMAL);
-}
-
-void ChromeLauncherController::PersistPinnedState() {
- // It is a coding error to call PersistPinnedState() if the pinned apps are
- // not user-editable. The code should check earlier and not perform any
- // modification actions that trigger persisting the state.
- if (!CanPin()) {
- NOTREACHED() << "Can't pin but pinned state being updated";
- return;
- }
-
- // Set kUseDefaultPinnedApps to false and use pinned apps list from prefs
- // from now on.
- profile_->GetPrefs()->SetBoolean(prefs::kUseDefaultPinnedApps, false);
-
- // Mutating kPinnedLauncherApps is going to notify us and trigger us to
- // process the change. We don't want that to happen so remove ourselves as a
- // listener.
- pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
- {
- ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
- updater->Clear();
- for (size_t i = 0; i < model_->items().size(); ++i) {
- if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
- ash::LauncherID id = model_->items()[i].id;
- if (HasItemController(id) && IsPinned(id)) {
- base::DictionaryValue* app_value = ash::CreateAppDict(
- id_to_item_controller_map_[id]->app_id());
- if (app_value)
- updater->Append(app_value);
- }
- }
- }
- }
- pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
-}
-
-void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) {
- app_tab_helper_.reset(helper);
-}
-
-void ChromeLauncherController::SetAppIconLoaderForTest(AppIconLoader* loader) {
- app_icon_loader_.reset(loader);
-}
-
-Profile* ChromeLauncherController::GetProfileForNewWindows() {
- return ProfileManager::GetDefaultProfileOrOffTheRecord();
-}
-
-void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) {
- IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
- DCHECK(iter != id_to_item_controller_map_.end());
- app_icon_loader_->ClearImage(iter->second->app_id());
- iter->second->OnRemoved();
- id_to_item_controller_map_.erase(iter);
- model_->RemoveItemAt(model_->ItemIndexByID(id));
-}
-
-void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) {
- // If there is an item, do nothing and return.
- if (IsAppPinned(app_id))
- return;
-
- ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
- if (launcher_id) {
- // App item exists, pin it
- Pin(launcher_id);
- } else {
- // Otherwise, create a shortcut item for it.
- CreateAppShortcutLauncherItem(app_id, model_->item_count());
- if (CanPin())
- PersistPinnedState();
- }
-}
-
-void ChromeLauncherController::DoUnpinAppsWithID(const std::string& app_id) {
- for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
- i != id_to_item_controller_map_.end(); ) {
- IDToItemControllerMap::iterator current(i);
- ++i;
- if (current->second->app_id() == app_id && IsPinned(current->first))
- Unpin(current->first);
- }
-}
-
-void ChromeLauncherController::UpdateAppLaunchersFromPref() {
- // Construct a vector representation of to-be-pinned apps from the pref.
- std::vector<std::string> pinned_apps;
- const base::ListValue* pinned_apps_pref =
- profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
- for (base::ListValue::const_iterator it(pinned_apps_pref->begin());
- it != pinned_apps_pref->end(); ++it) {
- DictionaryValue* app = NULL;
- std::string app_id;
- if ((*it)->GetAsDictionary(&app) &&
- app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
- std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
- pinned_apps.end() &&
- app_tab_helper_->IsValidID(app_id)) {
- pinned_apps.push_back(app_id);
- }
- }
-
- // Walk the model and |pinned_apps| from the pref lockstep, adding and
- // removing items as necessary. NB: This code uses plain old indexing instead
- // of iterators because of model mutations as part of the loop.
- std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
- int index = 0;
- for (; index < model_->item_count() && pref_app_id != pinned_apps.end();
- ++index) {
- // If the next app launcher according to the pref is present in the model,
- // delete all app launcher entries in between.
- if (IsAppPinned(*pref_app_id)) {
- for (; index < model_->item_count(); ++index) {
- const ash::LauncherItem& item(model_->items()[index]);
- if (item.type != ash::TYPE_APP_SHORTCUT)
- continue;
-
- IDToItemControllerMap::const_iterator entry =
- id_to_item_controller_map_.find(item.id);
- if (entry != id_to_item_controller_map_.end() &&
- entry->second->app_id() == *pref_app_id) {
- ++pref_app_id;
- break;
- } else {
- LauncherItemClosed(item.id);
- --index;
- }
- }
- // If the item wasn't found, that means id_to_item_controller_map_
- // is out of sync.
- DCHECK(index < model_->item_count());
- } else {
- // This app wasn't pinned before, insert a new entry.
- ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index);
- index = model_->ItemIndexByID(id);
- ++pref_app_id;
- }
- }
-
- // Remove any trailing existing items.
- while (index < model_->item_count()) {
- const ash::LauncherItem& item(model_->items()[index]);
- if (item.type == ash::TYPE_APP_SHORTCUT)
- LauncherItemClosed(item.id);
- else
- ++index;
- }
-
- // Append unprocessed items from the pref to the end of the model.
- for (; pref_app_id != pinned_apps.end(); ++pref_app_id)
- DoPinAppWithID(*pref_app_id);
-}
-
-void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() {
- // See comment in |kShelfAlignment| as to why we consider two prefs.
- const std::string behavior_value(
- GetLocalOrRemotePref(profile_->GetPrefs(),
- prefs::kShelfAutoHideBehaviorLocal,
- prefs::kShelfAutoHideBehavior));
-
- // Note: To maintain sync compatibility with old images of chrome/chromeos
- // the set of values that may be encountered includes the now-extinct
- // "Default" as well as "Never" and "Always", "Default" should now
- // be treated as "Never".
- // (http://code.google.com/p/chromium/issues/detail?id=146773)
- ash::ShelfAutoHideBehavior behavior =
- ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
- if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
- behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
- // TODO(oshima): Support multiple displays.
- ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
- behavior, ash::Shell::GetPrimaryRootWindow());
-}
-
-void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
- if (!CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kShowLauncherAlignmentMenu))
- return;
-
- // See comment in |kShelfAlignment| as to why we consider two prefs.
- const std::string alignment_value(
- GetLocalOrRemotePref(profile_->GetPrefs(),
- prefs::kShelfAlignmentLocal,
- prefs::kShelfAlignment));
- ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
- if (alignment_value == ash::kShelfAlignmentLeft)
- alignment = ash::SHELF_ALIGNMENT_LEFT;
- else if (alignment_value == ash::kShelfAlignmentRight)
- alignment = ash::SHELF_ALIGNMENT_RIGHT;
- // TODO(oshima): Support multiple displays.
- ash::Shell::GetInstance()->SetShelfAlignment(
- alignment, ash::Shell::GetPrimaryRootWindow());
-}
-
-TabContents* ChromeLauncherController::GetLastActiveTabContents(
- const std::string& app_id) {
- AppIDToTabContentsListMap::const_iterator i =
- app_id_to_tab_contents_list_.find(app_id);
- if (i == app_id_to_tab_contents_list_.end())
- return NULL;
- DCHECK_GT(i->second.size(), 0u);
- return *i->second.begin();
-}
-
-ash::LauncherID ChromeLauncherController::InsertAppLauncherItem(
- LauncherItemController* controller,
- const std::string& app_id,
- ash::LauncherItemStatus status,
- int index) {
- ash::LauncherID id = model_->next_id();
- DCHECK(!HasItemController(id));
- DCHECK(controller);
- id_to_item_controller_map_[id] = controller;
- controller->set_launcher_id(id);
-
- ash::LauncherItem item;
- item.type = controller->GetLauncherItemType();
- item.is_incognito = false;
- item.image = Extension::GetDefaultIcon(true);
-
- TabContents* active_tab = GetLastActiveTabContents(app_id);
- if (active_tab) {
- Browser* browser = browser::FindBrowserWithWebContents(
- active_tab->web_contents());
- DCHECK(browser);
- if (browser->window()->IsActive())
- status = ash::STATUS_ACTIVE;
- else
- status = ash::STATUS_RUNNING;
- }
- item.status = status;
-
- model_->AddAt(index, item);
-
- if (controller->type() != LauncherItemController::TYPE_EXTENSION_PANEL)
- app_icon_loader_->FetchImage(app_id);
-
- return id;
-}
-
-ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItem(
- const std::string& app_id,
- int index) {
- AppShortcutLauncherItemController* controller =
- new AppShortcutLauncherItemController(app_id, this);
- ash::LauncherID launcher_id = InsertAppLauncherItem(
- controller, app_id, ash::STATUS_CLOSED, index);
- return launcher_id;
-}
-
-bool ChromeLauncherController::HasItemController(ash::LauncherID id) const {
- return id_to_item_controller_map_.find(id) !=
- id_to_item_controller_map_.end();
}
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
index 2171031..0c85646 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h
@@ -5,33 +5,16 @@
#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_
#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_
-#include <list>
-#include <map>
#include <string>
#include "ash/launcher/launcher_delegate.h"
-#include "ash/launcher/launcher_model_observer.h"
#include "ash/launcher/launcher_types.h"
-#include "ash/shell_observer.h"
#include "ash/wm/shelf_types.h"
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/prefs/public/pref_change_registrar.h"
-#include "base/prefs/public/pref_observer.h"
#include "chrome/browser/extensions/extension_prefs.h"
-#include "chrome/browser/prefs/pref_service_observer.h"
-#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-#include "ui/aura/window_observer.h"
-
-class AppSyncUIState;
-class Browser;
+
class BrowserLauncherItemControllerTest;
class LauncherItemController;
class Profile;
-class ShellWindowLauncherController;
class TabContents;
namespace ash {
@@ -40,6 +23,7 @@ class LauncherModel;
namespace aura {
class Window;
+class RootWindow;
}
namespace content {
@@ -48,19 +32,16 @@ class WebContents;
// ChromeLauncherController manages the launcher items needed for content
// windows. Launcher items have a type, an optional app id, and a controller.
+// ChromeLauncherController will furthermore create the particular
+// implementation of interest - either sorting by application (new) or sorting
+// by browser (old).
// * Tabbed browsers and browser app windows have BrowserLauncherItemController,
// owned by the BrowserView instance.
// * App shell windows have ShellWindowLauncherItemController, owned by
// ShellWindowLauncherController.
// * Shortcuts have no LauncherItemController.
class ChromeLauncherController
- : public ash::LauncherDelegate,
- public ash::LauncherModelObserver,
- public ash::ShellObserver,
- public content::NotificationObserver,
- public PrefObserver,
- public PrefServiceObserver,
- public AppSyncUIStateObserver {
+ : public ash::LauncherDelegate {
public:
// Indicates if a launcher item is incognito or not.
enum IncognitoState {
@@ -104,269 +85,187 @@ class ChromeLauncherController
virtual void ClearImage(const std::string& id) = 0;
};
- ChromeLauncherController(Profile* profile, ash::LauncherModel* model);
+ ChromeLauncherController() {}
virtual ~ChromeLauncherController();
// Initializes this ChromeLauncherController.
- void Init();
+ virtual void Init() = 0;
+
+ // Creates an instance.
+ static ChromeLauncherController* CreateInstance(Profile* profile,
+ ash::LauncherModel* model);
// Returns the single ChromeLauncherController instance.
static ChromeLauncherController* instance() { return instance_; }
// Creates a new tabbed item on the launcher for |controller|.
- ash::LauncherID CreateTabbedLauncherItem(
+ virtual ash::LauncherID CreateTabbedLauncherItem(
LauncherItemController* controller,
IncognitoState is_incognito,
- ash::LauncherItemStatus status);
+ ash::LauncherItemStatus status) = 0;
// Creates a new app item on the launcher for |controller|.
- ash::LauncherID CreateAppLauncherItem(
+ virtual ash::LauncherID CreateAppLauncherItem(
LauncherItemController* controller,
const std::string& app_id,
- ash::LauncherItemStatus status);
+ ash::LauncherItemStatus status) = 0;
// Updates the running status of an item.
- void SetItemStatus(ash::LauncherID id, ash::LauncherItemStatus status);
+ virtual void SetItemStatus(ash::LauncherID id,
+ ash::LauncherItemStatus status) = 0;
// Updates the controller associated with id (which should be a shortcut).
// |controller| remains owned by caller.
- void SetItemController(ash::LauncherID id,
- LauncherItemController* controller);
+ virtual void SetItemController(ash::LauncherID id,
+ LauncherItemController* controller) = 0;
// Closes or unpins the launcher item.
- void CloseLauncherItem(ash::LauncherID id);
+ virtual void CloseLauncherItem(ash::LauncherID id) = 0;
// Pins the specified id. Currently only supports platform apps.
- void Pin(ash::LauncherID id);
+ virtual void Pin(ash::LauncherID id) = 0;
// Unpins the specified id, closing if not running.
- void Unpin(ash::LauncherID id);
+ virtual void Unpin(ash::LauncherID id) = 0;
// Returns true if the item identified by |id| is pinned.
- bool IsPinned(ash::LauncherID id);
+ virtual bool IsPinned(ash::LauncherID id) = 0;
// Pins/unpins the specified id.
- void TogglePinned(ash::LauncherID id);
+ virtual void TogglePinned(ash::LauncherID id) = 0;
// Returns true if the specified item can be pinned or unpinned. Only apps can
// be pinned.
- bool IsPinnable(ash::LauncherID id) const;
+ virtual bool IsPinnable(ash::LauncherID id) const = 0;
// Requests that the launcher item controller specified by |id| open a new
// instance of the app. |event_flags| holds the flags of the event which
// triggered this command.
- void Launch(ash::LauncherID id, int event_flags);
+ virtual void Launch(ash::LauncherID id, int event_flags) = 0;
// Closes the specified item.
- void Close(ash::LauncherID id);
+ virtual void Close(ash::LauncherID id) = 0;
// Returns true if the specified item is open.
- bool IsOpen(ash::LauncherID id);
+ virtual bool IsOpen(ash::LauncherID id) = 0;
// Returns true if the specified item is for a platform app.
- bool IsPlatformApp(ash::LauncherID id);
+ virtual bool IsPlatformApp(ash::LauncherID id) = 0;
// Opens a new instance of the application identified by |app_id|.
// Used by the app-list, and by pinned-app launcher items.
- void LaunchApp(const std::string& app_id, int event_flags);
+ virtual void LaunchApp(const std::string& app_id, int event_flags) = 0;
// If |app_id| is running, reactivates the app's most recently active window,
// otherwise launches and activates the app.
// Used by the app-list, and by pinned-app launcher items.
- void ActivateApp(const std::string& app_id, int event_flags);
+ virtual void ActivateApp(const std::string& app_id, int event_flags) = 0;
// Returns the launch type of app for the specified id.
- extensions::ExtensionPrefs::LaunchType GetLaunchType(ash::LauncherID id);
+ virtual extensions::ExtensionPrefs::LaunchType GetLaunchType(
+ ash::LauncherID id) = 0;
// Returns the id of the app for the specified tab.
- std::string GetAppID(content::WebContents* tab);
+ virtual std::string GetAppID(content::WebContents* tab) = 0;
- ash::LauncherID GetLauncherIDForAppID(const std::string& app_id);
- std::string GetAppIDForLauncherID(ash::LauncherID id);
+ virtual ash::LauncherID GetLauncherIDForAppID(const std::string& app_id) = 0;
+ virtual std::string GetAppIDForLauncherID(ash::LauncherID id) = 0;
// Sets the image for an app tab. This is intended to be invoked from the
// AppIconLoader.
- void SetAppImage(const std::string& app_id, const gfx::ImageSkia& image);
+ virtual void SetAppImage(const std::string& app_id,
+ const gfx::ImageSkia& image) = 0;
// Returns true if a pinned launcher item with given |app_id| could be found.
- bool IsAppPinned(const std::string& app_id);
+ virtual bool IsAppPinned(const std::string& app_id) = 0;
// Pins an app with |app_id| to launcher. If there is a running instance in
// launcher, the running instance is pinned. If there is no running instance,
// a new launcher item is created and pinned.
- void PinAppWithID(const std::string& app_id);
+ virtual void PinAppWithID(const std::string& app_id) = 0;
// Updates the launche type of the app for the specified id to |launch_type|.
- void SetLaunchType(ash::LauncherID id,
- extensions::ExtensionPrefs::LaunchType launch_type);
+ virtual void SetLaunchType(
+ ash::LauncherID id,
+ extensions::ExtensionPrefs::LaunchType launch_type) = 0;
// Unpins any app items whose id is |app_id|.
- void UnpinAppsWithID(const std::string& app_id);
+ virtual void UnpinAppsWithID(const std::string& app_id) = 0;
// Returns true if the user is currently logged in as a guest.
- bool IsLoggedInAsGuest();
+ virtual bool IsLoggedInAsGuest() = 0;
// Invoked when user clicks on button in the launcher and there is no last
// used window (or CTRL is held with the click).
- void CreateNewWindow();
+ virtual void CreateNewWindow() = 0;
// Invoked when the user clicks on button in the launcher to create a new
// incognito window.
- void CreateNewIncognitoWindow();
+ virtual void CreateNewIncognitoWindow() = 0;
// Checks whether the user is allowed to pin apps. Pinning may be disallowed
// by policy in case there is a pre-defined set of pinned apps.
- bool CanPin() const;
+ virtual bool CanPin() const = 0;
// Updates the pinned pref state. The pinned state consists of a list pref.
// Each item of the list is a dictionary. The key |kAppIDPath| gives the
// id of the app.
- void PersistPinnedState();
+ virtual void PersistPinnedState() = 0;
- ash::LauncherModel* model() { return model_; }
+ virtual ash::LauncherModel* model() = 0;
- Profile* profile() { return profile_; }
+ virtual Profile* profile() = 0;
- void SetAutoHideBehavior(ash::ShelfAutoHideBehavior behavior,
- aura::RootWindow* root_window);
+ virtual void SetAutoHideBehavior(ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) = 0;
// The tab no longer represents its previously identified application.
- void RemoveTabFromRunningApp(TabContents* tab, const std::string& app_id);
+ virtual void RemoveTabFromRunningApp(TabContents* tab,
+ const std::string& app_id) = 0;
// Notify the controller that the state of an non platform app's tabs
// have changed,
- void UpdateAppState(content::WebContents* contents, AppState app_state);
+ virtual void UpdateAppState(content::WebContents* contents,
+ AppState app_state) = 0;
// Limits application refocusing to urls that match |url| for |id|.
- void SetRefocusURLPattern(ash::LauncherID id, const GURL& url);
+ virtual void SetRefocusURLPattern(ash::LauncherID id, const GURL& url) = 0;
// Returns the extension identified by |app_id|.
- const extensions::Extension* GetExtensionForAppID(const std::string& app_id);
+ virtual const extensions::Extension* GetExtensionForAppID(
+ const std::string& app_id) = 0;
// ash::LauncherDelegate overrides:
- virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE;
+ virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE = 0;
virtual void ItemClicked(const ash::LauncherItem& item,
- int event_flags) OVERRIDE;
- virtual int GetBrowserShortcutResourceId() OVERRIDE;
- virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE;
+ int event_flags) OVERRIDE = 0;
+ virtual int GetBrowserShortcutResourceId() OVERRIDE = 0;
+ virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE = 0;
virtual ui::MenuModel* CreateContextMenu(
- const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE;
- virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE;
- virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE;
-
- // ash::LauncherModelObserver overrides:
- virtual void LauncherItemAdded(int index) OVERRIDE;
- virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE;
- virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE;
- virtual void LauncherItemChanged(int index,
- const ash::LauncherItem& old_item) OVERRIDE;
- virtual void LauncherStatusChanged() OVERRIDE;
-
- // Overridden from content::NotificationObserver:
- virtual void Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) OVERRIDE;
-
- // Overridden from PrefObserver:
- virtual void OnPreferenceChanged(PrefServiceBase* service,
- const std::string& pref_name) OVERRIDE;
-
- // Overridden from ash::ShellObserver:
- virtual void OnShelfAlignmentChanged() OVERRIDE;
+ const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE = 0;
+ virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE = 0;
+ virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE = 0;
- // Overridden from PrefServiceObserver:
- virtual void OnIsSyncingChanged() OVERRIDE;
-
- // Overridden from AppSyncUIStateObserver
- virtual void OnAppSyncUIStatusChanged() OVERRIDE;
-
- private:
+ protected:
friend class BrowserLauncherItemControllerTest;
- friend class ChromeLauncherControllerTest;
- friend class LauncherAppBrowserTest;
friend class LauncherPlatformAppBrowserTest;
-
- typedef std::map<ash::LauncherID, LauncherItemController*>
- IDToItemControllerMap;
- typedef std::list<TabContents*> TabContentsList;
- typedef std::map<std::string, TabContentsList> AppIDToTabContentsListMap;
- typedef std::map<TabContents*, std::string> TabContentsToAppIDMap;
-
- // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class.
- // These are intended for testing.
- void SetAppTabHelperForTest(AppTabHelper* helper);
- void SetAppIconLoaderForTest(AppIconLoader* loader);
-
- // Returns the profile used for new windows.
- Profile* GetProfileForNewWindows();
-
- // Invoked when the associated browser or app is closed.
- void LauncherItemClosed(ash::LauncherID id);
-
- // Internal helpers for pinning and unpinning that handle both
- // client-triggered and internal pinning operations.
- void DoPinAppWithID(const std::string& app_id);
- void DoUnpinAppsWithID(const std::string& app_id);
-
- // Re-syncs launcher model with prefs::kPinnedLauncherApps.
- void UpdateAppLaunchersFromPref();
-
- // Sets the shelf auto-hide behavior from prefs.
- void SetShelfAutoHideBehaviorFromPrefs();
-
- // Sets the shelf alignment from prefs.
- void SetShelfAlignmentFromPrefs();
-
- // Returns the most recently active tab contents for an app.
- TabContents* GetLastActiveTabContents(const std::string& app_id);
-
- // Creates an app launcher to insert at |index|. Note that |index| may be
- // adjusted by the model to meet ordering constraints.
- ash::LauncherID InsertAppLauncherItem(
- LauncherItemController* controller,
- const std::string& app_id,
- ash::LauncherItemStatus status,
- int index);
+ friend class LauncherAppBrowserTest;
// Creates a new app shortcut item and controller on the launcher at |index|.
// Use kInsertItemAtEnd to add a shortcut as the last item.
- ash::LauncherID CreateAppShortcutLauncherItem(const std::string& app_id,
- int index);
+ virtual ash::LauncherID CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) = 0;
- bool HasItemController(ash::LauncherID id) const;
+ // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class.
+ // These are intended for testing.
+ virtual void SetAppTabHelperForTest(AppTabHelper* helper) = 0;
+ virtual void SetAppIconLoaderForTest(AppIconLoader* loader) = 0;
+ virtual const std::string& GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) = 0;
+ private:
static ChromeLauncherController* instance_;
-
- ash::LauncherModel* model_;
-
- // Profile used for prefs and loading extensions. This is NOT necessarily the
- // profile new windows are created with.
- Profile* profile_;
-
- IDToItemControllerMap id_to_item_controller_map_;
-
- // Maintains activation order of tab contents for each app.
- AppIDToTabContentsListMap app_id_to_tab_contents_list_;
-
- // Direct access to app_id for a tab contents.
- TabContentsToAppIDMap tab_contents_to_app_id_;
-
- // Used to track shell windows.
- scoped_ptr<ShellWindowLauncherController> shell_window_controller_;
-
- // Used to get app info for tabs.
- scoped_ptr<AppTabHelper> app_tab_helper_;
-
- // Used to load the image for an app item.
- scoped_ptr<AppIconLoader> app_icon_loader_;
-
- content::NotificationRegistrar notification_registrar_;
-
- PrefChangeRegistrar pref_change_registrar_;
-
- AppSyncUIState* app_sync_ui_state_;
-
- DISALLOW_COPY_AND_ASSIGN(ChromeLauncherController);
};
#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc
new file mode 100644
index 0000000..37d2509
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc
@@ -0,0 +1,1107 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h"
+
+#include <vector>
+
+#include "ash/launcher/launcher_model.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/values.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/prefs/scoped_user_pref_update.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/app_sync_ui_state.h"
+#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
+#include "chrome/browser/ui/ash/extension_utils.h"
+#include "chrome/browser/ui/ash/launcher/launcher_app_icon_loader.h"
+#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
+#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
+#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
+#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tab_contents/tab_contents.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/url_pattern.h"
+#include "grit/theme_resources.h"
+#include "ui/aura/window.h"
+
+using extensions::Extension;
+
+namespace {
+
+// Item controller for an app shortcut. Shortcuts track app and launcher ids,
+// but do not have any associated windows (opening a shortcut will replace the
+// item with the appropriate LauncherItemController type).
+class AppShortcutLauncherItemController : public LauncherItemController {
+ public:
+ AppShortcutLauncherItemController(const std::string& app_id,
+ ChromeLauncherControllerPerApp* controller)
+ : LauncherItemController(TYPE_SHORTCUT, app_id, controller) {
+ // Google Drive should just refocus to it's main app UI.
+ // TODO(davemoore): Generalize this for other applications.
+ if (app_id == "apdfllckaahabafndbhieahigkjlhalf") {
+ const Extension* extension =
+ launcher_controller()->GetExtensionForAppID(app_id);
+ refocus_url_ = GURL(extension->launch_web_url() + "*");
+ }
+ }
+
+ virtual ~AppShortcutLauncherItemController() {}
+
+ // LauncherItemController overrides:
+ virtual string16 GetTitle() OVERRIDE {
+ return GetAppTitle();
+ }
+
+ virtual bool HasWindow(aura::Window* window) const OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsOpen() const OVERRIDE {
+ return false;
+ }
+
+ virtual void Launch(int event_flags) OVERRIDE {
+ launcher_controller()->LaunchApp(app_id(), event_flags);
+ }
+
+ virtual void Activate() OVERRIDE {
+ launcher_controller()->ActivateApp(app_id(), ui::EF_NONE);
+ }
+
+ virtual void Close() OVERRIDE {
+ // TODO: maybe should treat as unpin?
+ }
+
+ virtual void Clicked() OVERRIDE {
+ Activate();
+ }
+
+ virtual void OnRemoved() OVERRIDE {
+ // AppShortcutLauncherItemController is unowned; delete on removal.
+ delete this;
+ }
+
+ virtual void LauncherItemChanged(
+ int model_index,
+ const ash::LauncherItem& old_item) OVERRIDE {
+ }
+
+ // Stores the optional refocus url pattern for this item.
+ const GURL& refocus_url() const { return refocus_url_; }
+ void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; }
+
+ private:
+ GURL refocus_url_;
+ DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController);
+};
+
+// If the value of the pref at |local_path is not empty, it is returned
+// otherwise the value of the pref at |synced_path| is returned.
+std::string GetLocalOrRemotePref(PrefService* pref_service,
+ const char* local_path,
+ const char* synced_path) {
+ const std::string value(pref_service->GetString(local_path));
+ return value.empty() ? pref_service->GetString(synced_path) : value;
+}
+
+// If prefs have synced and the pref value at |local_path| is empty the value
+// from |synced_path| is copied to |local_path|.
+void MaybePropagatePrefToLocal(PrefService* pref_service,
+ const char* local_path,
+ const char* synced_path) {
+ if (pref_service->GetString(local_path).empty() &&
+ pref_service->IsSyncing()) {
+ // First time the user is using this machine, propagate from remote to
+ // local.
+ pref_service->SetString(local_path, pref_service->GetString(synced_path));
+ }
+}
+
+} // namespace
+
+// ChromeLauncherControllerPerApp ---------------------------------------------
+
+ChromeLauncherControllerPerApp::ChromeLauncherControllerPerApp(
+ Profile* profile,
+ ash::LauncherModel* model)
+ : model_(model),
+ profile_(profile),
+ app_sync_ui_state_(NULL) {
+ if (!profile_) {
+ // Use the original profile as on chromeos we may get a temporary off the
+ // record profile.
+ profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile();
+
+ app_sync_ui_state_ = AppSyncUIState::Get(profile_);
+ if (app_sync_ui_state_)
+ app_sync_ui_state_->AddObserver(this);
+ }
+
+ model_->AddObserver(this);
+ // TODO(stevenjb): Find a better owner for shell_window_controller_?
+ shell_window_controller_.reset(new ShellWindowLauncherController(this));
+ app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
+ app_icon_loader_.reset(new LauncherAppIconLoader(profile_, this));
+
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_LOADED,
+ content::Source<Profile>(profile_));
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(profile_));
+ pref_change_registrar_.Init(profile_->GetPrefs());
+ pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
+}
+
+ChromeLauncherControllerPerApp::~ChromeLauncherControllerPerApp() {
+ // Reset the shell window controller here since it has a weak pointer to this.
+ shell_window_controller_.reset();
+
+ model_->RemoveObserver(this);
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ i->second->OnRemoved();
+ model_->RemoveItemAt(model_->ItemIndexByID(i->first));
+ }
+
+ if (ash::Shell::HasInstance())
+ ash::Shell::GetInstance()->RemoveShellObserver(this);
+
+ if (app_sync_ui_state_)
+ app_sync_ui_state_->RemoveObserver(this);
+
+ profile_->GetPrefs()->RemoveObserver(this);
+}
+
+void ChromeLauncherControllerPerApp::Init() {
+ // TODO(xiyuan): Remove migration code and kUseDefaultPinnedApp after M20.
+ // Migration cases:
+ // - Users that unpin all apps:
+ // - have default pinned apps
+ // - kUseDefaultPinnedApps set to false
+ // Migrate them by setting an empty list for kPinnedLauncherApps.
+ //
+ // - Users that have customized pinned apps:
+ // - have non-default non-empty pinned apps list
+ // - kUseDefaultPinnedApps set to false
+ // Nothing needs to be done because customized pref overrides default.
+ //
+ // - Users that have default apps (i.e. new user or never pin/unpin):
+ // - have default pinned apps
+ // - kUseDefaultPinnedApps is still true
+ // Nothing needs to be done because they should get the default.
+ if (profile_->GetPrefs()->FindPreference(
+ prefs::kPinnedLauncherApps)->IsDefaultValue() &&
+ !profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) {
+ ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
+ updater.Get()->Clear();
+ }
+
+ UpdateAppLaunchersFromPref();
+
+ // TODO(sky): update unit test so that this test isn't necessary.
+ if (ash::Shell::HasInstance()) {
+ SetShelfAutoHideBehaviorFromPrefs();
+ SetShelfAlignmentFromPrefs();
+ PrefService* prefs = profile_->GetPrefs();
+ if (prefs->GetString(prefs::kShelfAlignmentLocal).empty() ||
+ prefs->GetString(prefs::kShelfAutoHideBehaviorLocal).empty()) {
+ prefs->AddObserver(this);
+ }
+ ash::Shell::GetInstance()->AddShellObserver(this);
+ }
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::CreateTabbedLauncherItem(
+ LauncherItemController* controller,
+ IncognitoState is_incognito,
+ ash::LauncherItemStatus status) {
+ ash::LauncherID id = model_->next_id();
+ DCHECK(!HasItemController(id));
+ DCHECK(controller);
+ id_to_item_controller_map_[id] = controller;
+ controller->set_launcher_id(id);
+
+ ash::LauncherItem item;
+ item.type = ash::TYPE_TABBED;
+ item.is_incognito = (is_incognito == STATE_INCOGNITO);
+ item.status = status;
+ model_->Add(item);
+ return id;
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::CreateAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status) {
+ DCHECK(controller);
+ return InsertAppLauncherItem(controller, app_id, status,
+ model_->item_count());
+}
+
+void ChromeLauncherControllerPerApp::SetItemStatus(
+ ash::LauncherID id,
+ ash::LauncherItemStatus status) {
+ int index = model_->ItemIndexByID(id);
+ DCHECK_GE(index, 0);
+ ash::LauncherItem item = model_->items()[index];
+ item.status = status;
+ model_->Set(index, item);
+}
+
+void ChromeLauncherControllerPerApp::SetItemController(
+ ash::LauncherID id,
+ LauncherItemController* controller) {
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ iter->second->OnRemoved();
+ iter->second = controller;
+ controller->set_launcher_id(id);
+}
+
+void ChromeLauncherControllerPerApp::CloseLauncherItem(ash::LauncherID id) {
+ if (IsPinned(id)) {
+ // Create a new shortcut controller.
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ SetItemStatus(id, ash::STATUS_CLOSED);
+ std::string app_id = iter->second->app_id();
+ iter->second->OnRemoved();
+ iter->second = new AppShortcutLauncherItemController(app_id, this);
+ iter->second->set_launcher_id(id);
+ } else {
+ LauncherItemClosed(id);
+ }
+}
+
+void ChromeLauncherControllerPerApp::Unpin(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ if (controller->type() == LauncherItemController::TYPE_APP) {
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItem item = model_->items()[index];
+ item.type = ash::TYPE_PLATFORM_APP;
+ model_->Set(index, item);
+ } else {
+ LauncherItemClosed(id);
+ }
+ if (CanPin())
+ PersistPinnedState();
+}
+
+void ChromeLauncherControllerPerApp::Pin(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItem item = model_->items()[index];
+
+ if (item.type != ash::TYPE_PLATFORM_APP)
+ return;
+
+ item.type = ash::TYPE_APP_SHORTCUT;
+ model_->Set(index, item);
+
+ if (CanPin())
+ PersistPinnedState();
+}
+
+bool ChromeLauncherControllerPerApp::IsPinned(ash::LauncherID id) {
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItemType type = model_->items()[index].type;
+ return type == ash::TYPE_APP_SHORTCUT;
+}
+
+void ChromeLauncherControllerPerApp::TogglePinned(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return; // May happen if item closed with menu open.
+
+ if (IsPinned(id))
+ Unpin(id);
+ else
+ Pin(id);
+}
+
+bool ChromeLauncherControllerPerApp::IsPinnable(ash::LauncherID id) const {
+ int index = model_->ItemIndexByID(id);
+ if (index == -1)
+ return false;
+
+ ash::LauncherItemType type = model_->items()[index].type;
+ return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) &&
+ CanPin());
+}
+
+void ChromeLauncherControllerPerApp::Launch(ash::LauncherID id,
+ int event_flags) {
+ if (!HasItemController(id))
+ return; // In case invoked from menu and item closed while menu up.
+ id_to_item_controller_map_[id]->Launch(event_flags);
+}
+
+void ChromeLauncherControllerPerApp::Close(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return; // May happen if menu closed.
+ id_to_item_controller_map_[id]->Close();
+}
+
+bool ChromeLauncherControllerPerApp::IsOpen(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return false;
+ return id_to_item_controller_map_[id]->IsOpen();
+}
+
+bool ChromeLauncherControllerPerApp::IsPlatformApp(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return false;
+
+ std::string app_id = GetAppIDForLauncherID(id);
+ const Extension* extension = GetExtensionForAppID(app_id);
+ DCHECK(extension);
+ return extension->is_platform_app();
+}
+
+void ChromeLauncherControllerPerApp::LaunchApp(const std::string& app_id,
+ int event_flags) {
+ const Extension* extension = GetExtensionForAppID(app_id);
+ extension_utils::OpenExtension(GetProfileForNewWindows(),
+ extension,
+ event_flags);
+}
+
+void ChromeLauncherControllerPerApp::ActivateApp(const std::string& app_id,
+ int event_flags) {
+ if (app_id == extension_misc::kChromeAppId) {
+ OnBrowserShortcutClicked(event_flags);
+ return;
+ }
+
+ // If there is an existing non-shortcut controller for this app, open it.
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
+ refocus_pattern.SetMatchAllURLs(true);
+
+ if (id > 0) {
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ if (controller->type() != LauncherItemController::TYPE_SHORTCUT) {
+ controller->Activate();
+ return;
+ }
+
+ AppShortcutLauncherItemController* app_controller =
+ static_cast<AppShortcutLauncherItemController*>(controller);
+ const GURL refocus_url = app_controller->refocus_url();
+
+ if (!refocus_url.is_empty())
+ refocus_pattern.Parse(refocus_url.spec());
+ }
+
+ // Check if there are any open tabs for this app.
+ AppIDToTabContentsListMap::iterator app_i =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (app_i != app_id_to_tab_contents_list_.end()) {
+ for (TabContentsList::iterator tab_i = app_i->second.begin();
+ tab_i != app_i->second.end();
+ ++tab_i) {
+ TabContents* tab = *tab_i;
+ const GURL tab_url = tab->web_contents()->GetURL();
+ if (refocus_pattern.MatchesURL(tab_url)) {
+ Browser* browser = browser::FindBrowserWithWebContents(
+ tab->web_contents());
+ TabStripModel* tab_strip = browser->tab_strip_model();
+ int index = tab_strip->GetIndexOfTabContents(tab);
+ DCHECK_NE(TabStripModel::kNoTab, index);
+ tab_strip->ActivateTabAt(index, false);
+ browser->window()->Show();
+ ash::wm::ActivateWindow(browser->window()->GetNativeWindow());
+ return;
+ }
+ }
+ }
+
+ LaunchApp(app_id, event_flags);
+}
+
+extensions::ExtensionPrefs::LaunchType
+ ChromeLauncherControllerPerApp::GetLaunchType(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ const Extension* extension = GetExtensionForAppID(
+ id_to_item_controller_map_[id]->app_id());
+ return profile_->GetExtensionService()->extension_prefs()->GetLaunchType(
+ extension,
+ extensions::ExtensionPrefs::LAUNCH_DEFAULT);
+}
+
+std::string ChromeLauncherControllerPerApp::GetAppID(
+ content::WebContents* tab) {
+ return app_tab_helper_->GetAppID(tab);
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::GetLauncherIDForAppID(
+ const std::string& app_id) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->app_id() == app_id)
+ return i->first;
+ }
+ return 0;
+}
+
+std::string ChromeLauncherControllerPerApp::GetAppIDForLauncherID(
+ ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+ return id_to_item_controller_map_[id]->app_id();
+}
+
+void ChromeLauncherControllerPerApp::SetAppImage(
+ const std::string& id,
+ const gfx::ImageSkia& image) {
+ // TODO: need to get this working for shortcuts.
+
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->app_id() != id)
+ continue;
+
+ // Panel items may share the same app_id as the app that created them,
+ // but they set their icon image in
+ // BrowserLauncherItemController::UpdateLauncher(), so do not set panel
+ // images here.
+ if (i->second->type() == LauncherItemController::TYPE_EXTENSION_PANEL)
+ continue;
+
+ int index = model_->ItemIndexByID(i->first);
+ ash::LauncherItem item = model_->items()[index];
+ item.image = image;
+ model_->Set(index, item);
+ // It's possible we're waiting on more than one item, so don't break.
+ }
+}
+
+bool ChromeLauncherControllerPerApp::IsAppPinned(const std::string& app_id) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (IsPinned(i->first) && i->second->app_id() == app_id)
+ return true;
+ }
+ return false;
+}
+
+void ChromeLauncherControllerPerApp::PinAppWithID(const std::string& app_id) {
+ if (CanPin())
+ DoPinAppWithID(app_id);
+ else
+ NOTREACHED();
+}
+
+void ChromeLauncherControllerPerApp::SetLaunchType(
+ ash::LauncherID id,
+ extensions::ExtensionPrefs::LaunchType launch_type) {
+ if (!HasItemController(id))
+ return;
+
+ return profile_->GetExtensionService()->extension_prefs()->SetLaunchType(
+ id_to_item_controller_map_[id]->app_id(), launch_type);
+}
+
+void ChromeLauncherControllerPerApp::UnpinAppsWithID(
+ const std::string& app_id) {
+ if (CanPin())
+ DoUnpinAppsWithID(app_id);
+ else
+ NOTREACHED();
+}
+
+bool ChromeLauncherControllerPerApp::IsLoggedInAsGuest() {
+ return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord();
+}
+
+void ChromeLauncherControllerPerApp::CreateNewWindow() {
+ chrome::NewEmptyWindow(
+ GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH);
+}
+
+void ChromeLauncherControllerPerApp::CreateNewIncognitoWindow() {
+ chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile());
+}
+
+bool ChromeLauncherControllerPerApp::CanPin() const {
+ const PrefService::Preference* pref =
+ profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
+ return pref && pref->IsUserModifiable();
+}
+
+void ChromeLauncherControllerPerApp::SetAutoHideBehavior(
+ ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) {
+ ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
+ behavior,
+ root_window);
+ // TODO(oshima): Support multiple launcher.
+ if (root_window != ash::Shell::GetPrimaryRootWindow())
+ return;
+
+ const char* value = NULL;
+ switch (behavior) {
+ case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
+ value = ash::kShelfAutoHideBehaviorAlways;
+ break;
+ case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
+ value = ash::kShelfAutoHideBehaviorNever;
+ break;
+ }
+ // See comment in |kShelfAlignment| about why we have two prefs here.
+ profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
+ profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
+}
+
+void ChromeLauncherControllerPerApp::RemoveTabFromRunningApp(
+ TabContents* tab,
+ const std::string& app_id) {
+ tab_contents_to_app_id_.erase(tab);
+ AppIDToTabContentsListMap::iterator i_app_id =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (i_app_id != app_id_to_tab_contents_list_.end()) {
+ TabContentsList* tab_list = &i_app_id->second;
+ tab_list->remove(tab);
+ if (tab_list->empty()) {
+ app_id_to_tab_contents_list_.erase(i_app_id);
+ i_app_id = app_id_to_tab_contents_list_.end();
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id > 0)
+ SetItemStatus(id, ash::STATUS_CLOSED);
+ }
+ }
+}
+
+void ChromeLauncherControllerPerApp::UpdateAppState(
+ content::WebContents* contents,
+ AppState app_state) {
+ std::string app_id = GetAppID(contents);
+
+ // Check the old |app_id| for a tab. If the contents has changed we need to
+ // remove it from the previous app.
+ TabContents* tab = TabContents::FromWebContents(contents);
+ if (tab_contents_to_app_id_.find(tab) != tab_contents_to_app_id_.end()) {
+ std::string last_app_id = tab_contents_to_app_id_[tab];
+ if (last_app_id != app_id)
+ RemoveTabFromRunningApp(tab, last_app_id);
+ }
+
+ if (app_id.empty())
+ return;
+
+ tab_contents_to_app_id_[tab] = app_id;
+
+ if (app_state == APP_STATE_REMOVED) {
+ // The tab has gone away.
+ RemoveTabFromRunningApp(tab, app_id);
+ } else {
+ TabContentsList& tab_list(app_id_to_tab_contents_list_[app_id]);
+
+ if (app_state == APP_STATE_INACTIVE) {
+ TabContentsList::const_iterator i_tab =
+ std::find(tab_list.begin(), tab_list.end(), tab);
+ if (i_tab == tab_list.end())
+ tab_list.push_back(tab);
+ if (i_tab != tab_list.begin()) {
+ // Going inactive, but wasn't the front tab, indicating that a new
+ // tab has already become active.
+ return;
+ }
+ } else {
+ tab_list.remove(tab);
+ tab_list.push_front(tab);
+ }
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id > 0) {
+ // If the window is active, mark the app as active.
+ SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
+ ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
+ }
+ }
+}
+
+void ChromeLauncherControllerPerApp::SetRefocusURLPattern(
+ ash::LauncherID id,
+ const GURL& url) {
+ DCHECK(HasItemController(id));
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+
+ int index = model_->ItemIndexByID(id);
+ if (index == -1) {
+ NOTREACHED() << "Invalid launcher id";
+ return;
+ }
+
+ ash::LauncherItemType type = model_->items()[index].type;
+ if (type == ash::TYPE_APP_SHORTCUT) {
+ AppShortcutLauncherItemController* app_controller =
+ static_cast<AppShortcutLauncherItemController*>(controller);
+ app_controller->set_refocus_url(url);
+ } else {
+ NOTREACHED() << "Invalid launcher type";
+ }
+}
+
+const Extension* ChromeLauncherControllerPerApp::GetExtensionForAppID(
+ const std::string& app_id) {
+ return profile_->GetExtensionService()->GetInstalledExtension(app_id);
+}
+
+void ChromeLauncherControllerPerApp::OnBrowserShortcutClicked(
+ int event_flags) {
+ if (event_flags & ui::EF_CONTROL_DOWN) {
+ CreateNewWindow();
+ return;
+ }
+
+ Browser* last_browser = browser::FindTabbedBrowser(
+ GetProfileForNewWindows(), true, chrome::HOST_DESKTOP_TYPE_ASH);
+
+ if (!last_browser) {
+ CreateNewWindow();
+ return;
+ }
+
+ aura::Window* window = last_browser->window()->GetNativeWindow();
+ window->Show();
+ ash::wm::ActivateWindow(window);
+}
+
+void ChromeLauncherControllerPerApp::ItemClicked(const ash::LauncherItem& item,
+ int event_flags) {
+ DCHECK(HasItemController(item.id));
+ id_to_item_controller_map_[item.id]->Clicked();
+}
+
+int ChromeLauncherControllerPerApp::GetBrowserShortcutResourceId() {
+ return IDR_PRODUCT_LOGO_32;
+}
+
+string16 ChromeLauncherControllerPerApp::GetTitle(
+ const ash::LauncherItem& item) {
+ DCHECK(HasItemController(item.id));
+ return id_to_item_controller_map_[item.id]->GetTitle();
+}
+
+ui::MenuModel* ChromeLauncherControllerPerApp::CreateContextMenu(
+ const ash::LauncherItem& item,
+ aura::RootWindow* root_window) {
+ return new LauncherContextMenu(this, &item, root_window);
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::GetIDByWindow(
+ aura::Window* window) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->HasWindow(window))
+ return i->first;
+ }
+ return 0;
+}
+
+bool ChromeLauncherControllerPerApp::IsDraggable(
+ const ash::LauncherItem& item) {
+ return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true;
+}
+
+void ChromeLauncherControllerPerApp::LauncherItemAdded(int index) {
+}
+
+void ChromeLauncherControllerPerApp::LauncherItemRemoved(
+ int index,
+ ash::LauncherID id) {
+}
+
+void ChromeLauncherControllerPerApp::LauncherItemMoved(
+ int start_index,
+ int target_index) {
+ ash::LauncherID id = model_->items()[target_index].id;
+ if (HasItemController(id) && IsPinned(id))
+ PersistPinnedState();
+}
+
+void ChromeLauncherControllerPerApp::LauncherItemChanged(
+ int index,
+ const ash::LauncherItem& old_item) {
+ ash::LauncherID id = model_->items()[index].id;
+ id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item);
+}
+
+void ChromeLauncherControllerPerApp::LauncherStatusChanged() {
+}
+
+void ChromeLauncherControllerPerApp::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_LOADED: {
+ UpdateAppLaunchersFromPref();
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+ const content::Details<extensions::UnloadedExtensionInfo> unload_info(
+ details);
+ const Extension* extension = unload_info->extension;
+ if (IsAppPinned(extension->id()))
+ DoUnpinAppsWithID(extension->id());
+ app_icon_loader_->ClearImage(extension->id());
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification type=" << type;
+ }
+}
+
+void ChromeLauncherControllerPerApp::OnPreferenceChanged(
+ PrefServiceBase* service,
+ const std::string& pref_name) {
+ if (pref_name == prefs::kPinnedLauncherApps) {
+ UpdateAppLaunchersFromPref();
+ } else if (pref_name == prefs::kShelfAlignmentLocal) {
+ SetShelfAlignmentFromPrefs();
+ } else if (pref_name == prefs::kShelfAutoHideBehaviorLocal) {
+ SetShelfAutoHideBehaviorFromPrefs();
+ } else {
+ NOTREACHED() << "Unexpected pref change for " << pref_name;
+ }
+}
+
+void ChromeLauncherControllerPerApp::OnShelfAlignmentChanged() {
+ const char* pref_value = NULL;
+ // TODO(oshima): Support multiple displays.
+ switch (ash::Shell::GetInstance()->GetShelfAlignment(
+ ash::Shell::GetPrimaryRootWindow())) {
+ case ash::SHELF_ALIGNMENT_BOTTOM:
+ pref_value = ash::kShelfAlignmentBottom;
+ break;
+ case ash::SHELF_ALIGNMENT_LEFT:
+ pref_value = ash::kShelfAlignmentLeft;
+ break;
+ case ash::SHELF_ALIGNMENT_RIGHT:
+ pref_value = ash::kShelfAlignmentRight;
+ break;
+ }
+ // See comment in |kShelfAlignment| about why we have two prefs here.
+ profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
+ profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
+}
+
+void ChromeLauncherControllerPerApp::OnIsSyncingChanged() {
+ MaybePropagatePrefToLocal(profile_->GetPrefs(),
+ prefs::kShelfAlignmentLocal,
+ prefs::kShelfAlignment);
+ MaybePropagatePrefToLocal(profile_->GetPrefs(),
+ prefs::kShelfAutoHideBehaviorLocal,
+ prefs::kShelfAutoHideBehavior);
+}
+
+void ChromeLauncherControllerPerApp::OnAppSyncUIStatusChanged() {
+ if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
+ model_->SetStatus(ash::LauncherModel::STATUS_LOADING);
+ else
+ model_->SetStatus(ash::LauncherModel::STATUS_NORMAL);
+}
+
+void ChromeLauncherControllerPerApp::PersistPinnedState() {
+ // It is a coding error to call PersistPinnedState() if the pinned apps are
+ // not user-editable. The code should check earlier and not perform any
+ // modification actions that trigger persisting the state.
+ if (!CanPin()) {
+ NOTREACHED() << "Can't pin but pinned state being updated";
+ return;
+ }
+
+ // Set kUseDefaultPinnedApps to false and use pinned apps list from prefs
+ // from now on.
+ profile_->GetPrefs()->SetBoolean(prefs::kUseDefaultPinnedApps, false);
+
+ // Mutating kPinnedLauncherApps is going to notify us and trigger us to
+ // process the change. We don't want that to happen so remove ourselves as a
+ // listener.
+ pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
+ {
+ ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
+ updater->Clear();
+ for (size_t i = 0; i < model_->items().size(); ++i) {
+ if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
+ ash::LauncherID id = model_->items()[i].id;
+ if (HasItemController(id) && IsPinned(id)) {
+ base::DictionaryValue* app_value = ash::CreateAppDict(
+ id_to_item_controller_map_[id]->app_id());
+ if (app_value)
+ updater->Append(app_value);
+ }
+ }
+ }
+ }
+ pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
+}
+
+ash::LauncherModel* ChromeLauncherControllerPerApp::model() {
+ return model_;
+}
+
+Profile* ChromeLauncherControllerPerApp::profile() {
+ return profile_;
+}
+
+Profile* ChromeLauncherControllerPerApp::GetProfileForNewWindows() {
+ return ProfileManager::GetDefaultProfileOrOffTheRecord();
+}
+
+void ChromeLauncherControllerPerApp::LauncherItemClosed(ash::LauncherID id) {
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ app_icon_loader_->ClearImage(iter->second->app_id());
+ iter->second->OnRemoved();
+ id_to_item_controller_map_.erase(iter);
+ model_->RemoveItemAt(model_->ItemIndexByID(id));
+}
+
+void ChromeLauncherControllerPerApp::DoPinAppWithID(
+ const std::string& app_id) {
+ // If there is an item, do nothing and return.
+ if (IsAppPinned(app_id))
+ return;
+
+ ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
+ if (launcher_id) {
+ // App item exists, pin it
+ Pin(launcher_id);
+ } else {
+ // Otherwise, create a shortcut item for it.
+ CreateAppShortcutLauncherItem(app_id, model_->item_count());
+ if (CanPin())
+ PersistPinnedState();
+ }
+}
+
+void ChromeLauncherControllerPerApp::DoUnpinAppsWithID(
+ const std::string& app_id) {
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ) {
+ IDToItemControllerMap::iterator current(i);
+ ++i;
+ if (current->second->app_id() == app_id && IsPinned(current->first))
+ Unpin(current->first);
+ }
+}
+
+void ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref() {
+ // Construct a vector representation of to-be-pinned apps from the pref.
+ std::vector<std::string> pinned_apps;
+ const base::ListValue* pinned_apps_pref =
+ profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
+ for (base::ListValue::const_iterator it(pinned_apps_pref->begin());
+ it != pinned_apps_pref->end(); ++it) {
+ DictionaryValue* app = NULL;
+ std::string app_id;
+ if ((*it)->GetAsDictionary(&app) &&
+ app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
+ std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
+ pinned_apps.end() &&
+ app_tab_helper_->IsValidID(app_id)) {
+ pinned_apps.push_back(app_id);
+ }
+ }
+
+ // Walk the model and |pinned_apps| from the pref lockstep, adding and
+ // removing items as necessary. NB: This code uses plain old indexing instead
+ // of iterators because of model mutations as part of the loop.
+ std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
+ int index = 0;
+ for (; index < model_->item_count() && pref_app_id != pinned_apps.end();
+ ++index) {
+ // If the next app launcher according to the pref is present in the model,
+ // delete all app launcher entries in between.
+ if (IsAppPinned(*pref_app_id)) {
+ for (; index < model_->item_count(); ++index) {
+ const ash::LauncherItem& item(model_->items()[index]);
+ if (item.type != ash::TYPE_APP_SHORTCUT)
+ continue;
+
+ IDToItemControllerMap::const_iterator entry =
+ id_to_item_controller_map_.find(item.id);
+ if (entry != id_to_item_controller_map_.end() &&
+ entry->second->app_id() == *pref_app_id) {
+ ++pref_app_id;
+ break;
+ } else {
+ LauncherItemClosed(item.id);
+ --index;
+ }
+ }
+ // If the item wasn't found, that means id_to_item_controller_map_
+ // is out of sync.
+ DCHECK(index < model_->item_count());
+ } else {
+ // This app wasn't pinned before, insert a new entry.
+ ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index);
+ index = model_->ItemIndexByID(id);
+ ++pref_app_id;
+ }
+ }
+
+ // Remove any trailing existing items.
+ while (index < model_->item_count()) {
+ const ash::LauncherItem& item(model_->items()[index]);
+ if (item.type == ash::TYPE_APP_SHORTCUT)
+ LauncherItemClosed(item.id);
+ else
+ ++index;
+ }
+
+ // Append unprocessed items from the pref to the end of the model.
+ for (; pref_app_id != pinned_apps.end(); ++pref_app_id)
+ DoPinAppWithID(*pref_app_id);
+}
+
+void ChromeLauncherControllerPerApp::SetShelfAutoHideBehaviorFromPrefs() {
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string behavior_value(
+ GetLocalOrRemotePref(profile_->GetPrefs(),
+ prefs::kShelfAutoHideBehaviorLocal,
+ prefs::kShelfAutoHideBehavior));
+
+ // Note: To maintain sync compatibility with old images of chrome/chromeos
+ // the set of values that may be encountered includes the now-extinct
+ // "Default" as well as "Never" and "Always", "Default" should now
+ // be treated as "Never".
+ // (http://code.google.com/p/chromium/issues/detail?id=146773)
+ ash::ShelfAutoHideBehavior behavior =
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
+ if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
+ behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+ // TODO(oshima): Support multiple displays.
+ ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
+ behavior, ash::Shell::GetPrimaryRootWindow());
+}
+
+void ChromeLauncherControllerPerApp::SetShelfAlignmentFromPrefs() {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kShowLauncherAlignmentMenu))
+ return;
+
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string alignment_value(
+ GetLocalOrRemotePref(profile_->GetPrefs(),
+ prefs::kShelfAlignmentLocal,
+ prefs::kShelfAlignment));
+ ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
+ if (alignment_value == ash::kShelfAlignmentLeft)
+ alignment = ash::SHELF_ALIGNMENT_LEFT;
+ else if (alignment_value == ash::kShelfAlignmentRight)
+ alignment = ash::SHELF_ALIGNMENT_RIGHT;
+ // TODO(oshima): Support multiple displays.
+ ash::Shell::GetInstance()->SetShelfAlignment(
+ alignment, ash::Shell::GetPrimaryRootWindow());
+}
+
+TabContents* ChromeLauncherControllerPerApp::GetLastActiveTabContents(
+ const std::string& app_id) {
+ AppIDToTabContentsListMap::const_iterator i =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (i == app_id_to_tab_contents_list_.end())
+ return NULL;
+ DCHECK_GT(i->second.size(), 0u);
+ return *i->second.begin();
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::InsertAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status,
+ int index) {
+ ash::LauncherID id = model_->next_id();
+ DCHECK(!HasItemController(id));
+ DCHECK(controller);
+ id_to_item_controller_map_[id] = controller;
+ controller->set_launcher_id(id);
+
+ ash::LauncherItem item;
+ item.type = controller->GetLauncherItemType();
+ item.is_incognito = false;
+ item.image = Extension::GetDefaultIcon(true);
+
+ TabContents* active_tab = GetLastActiveTabContents(app_id);
+ if (active_tab) {
+ Browser* browser = browser::FindBrowserWithWebContents(
+ active_tab->web_contents());
+ DCHECK(browser);
+ if (browser->window()->IsActive())
+ status = ash::STATUS_ACTIVE;
+ else
+ status = ash::STATUS_RUNNING;
+ }
+ item.status = status;
+
+ model_->AddAt(index, item);
+
+ if (controller->type() != LauncherItemController::TYPE_EXTENSION_PANEL)
+ app_icon_loader_->FetchImage(app_id);
+
+ return id;
+}
+
+bool ChromeLauncherControllerPerApp::HasItemController(
+ ash::LauncherID id) const {
+ return id_to_item_controller_map_.find(id) !=
+ id_to_item_controller_map_.end();
+}
+
+ash::LauncherID ChromeLauncherControllerPerApp::CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) {
+ AppShortcutLauncherItemController* controller =
+ new AppShortcutLauncherItemController(app_id, this);
+ ash::LauncherID launcher_id = InsertAppLauncherItem(
+ controller, app_id, ash::STATUS_CLOSED, index);
+ return launcher_id;
+}
+
+void ChromeLauncherControllerPerApp::SetAppTabHelperForTest(
+ AppTabHelper* helper) {
+ app_tab_helper_.reset(helper);
+}
+
+void ChromeLauncherControllerPerApp::SetAppIconLoaderForTest(
+ AppIconLoader* loader) {
+ app_icon_loader_.reset(loader);
+}
+
+const std::string&
+ChromeLauncherControllerPerApp::GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) {
+ return id_to_item_controller_map_[id]->app_id();
+}
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h
new file mode 100644
index 0000000..8722d79
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h
@@ -0,0 +1,341 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_
+#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "ash/launcher/launcher_model_observer.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/shelf_types.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/public/pref_change_registrar.h"
+#include "base/prefs/public/pref_observer.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/prefs/pref_service_observer.h"
+#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/aura/window_observer.h"
+
+class AppSyncUIState;
+class Browser;
+class BrowserLauncherItemControllerTest;
+class LauncherItemController;
+class Profile;
+class ShellWindowLauncherController;
+class TabContents;
+
+namespace ash {
+class LauncherModel;
+}
+
+namespace aura {
+class Window;
+}
+
+namespace content {
+class WebContents;
+}
+
+// ChromeLauncherControllerPerApp manages the launcher items needed for content
+// content windows. Launcher items have a type, an optional app id, and a
+// controller. This incarnation groups running tabs/windows in application
+// specific lists.
+// * Tabbed browsers and browser app windows have BrowserLauncherItemController,
+// owned by the BrowserView instance.
+// * App shell windows have ShellWindowLauncherItemController, owned by
+// ShellWindowLauncherController.
+// * Shortcuts have no LauncherItemController.
+class ChromeLauncherControllerPerApp
+ : public ash::LauncherModelObserver,
+ public ash::ShellObserver,
+ public ChromeLauncherController,
+ public content::NotificationObserver,
+ public PrefObserver,
+ public PrefServiceObserver,
+ public AppSyncUIStateObserver {
+ public:
+ ChromeLauncherControllerPerApp(Profile* profile, ash::LauncherModel* model);
+ virtual ~ChromeLauncherControllerPerApp();
+
+ // ChromeLauncherController overrides:
+
+ // Initializes this ChromeLauncherControllerPerApp.
+ virtual void Init() OVERRIDE;
+
+ // Creates a new tabbed item on the launcher for |controller|.
+ virtual ash::LauncherID CreateTabbedLauncherItem(
+ LauncherItemController* controller,
+ IncognitoState is_incognito,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Creates a new app item on the launcher for |controller|.
+ virtual ash::LauncherID CreateAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Updates the running status of an item.
+ virtual void SetItemStatus(ash::LauncherID id,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Updates the controller associated with id (which should be a shortcut).
+ // |controller| remains owned by caller.
+ virtual void SetItemController(ash::LauncherID id,
+ LauncherItemController* controller) OVERRIDE;
+
+ // Closes or unpins the launcher item.
+ virtual void CloseLauncherItem(ash::LauncherID id) OVERRIDE;
+
+ // Pins the specified id. Currently only supports platform apps.
+ virtual void Pin(ash::LauncherID id) OVERRIDE;
+
+ // Unpins the specified id, closing if not running.
+ virtual void Unpin(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the item identified by |id| is pinned.
+ virtual bool IsPinned(ash::LauncherID id) OVERRIDE;
+
+ // Pins/unpins the specified id.
+ virtual void TogglePinned(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item can be pinned or unpinned. Only apps can
+ // be pinned.
+ virtual bool IsPinnable(ash::LauncherID id) const OVERRIDE;
+
+ // Requests that the launcher item controller specified by |id| open a new
+ // instance of the app. |event_flags| holds the flags of the event which
+ // triggered this command.
+ virtual void Launch(ash::LauncherID id, int event_flags) OVERRIDE;
+
+ // Closes the specified item.
+ virtual void Close(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item is open.
+ virtual bool IsOpen(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item is for a platform app.
+ virtual bool IsPlatformApp(ash::LauncherID id) OVERRIDE;
+
+ // Opens a new instance of the application identified by |app_id|.
+ // Used by the app-list, and by pinned-app launcher items.
+ virtual void LaunchApp(const std::string& app_id, int event_flags) OVERRIDE;
+
+ // If |app_id| is running, reactivates the app's most recently active window,
+ // otherwise launches and activates the app.
+ // Used by the app-list, and by pinned-app launcher items.
+ virtual void ActivateApp(const std::string& app_id, int event_flags) OVERRIDE;
+
+ // Returns the launch type of app for the specified id.
+ virtual extensions::ExtensionPrefs::LaunchType GetLaunchType(
+ ash::LauncherID id) OVERRIDE;
+
+ // Returns the id of the app for the specified tab.
+ virtual std::string GetAppID(content::WebContents* tab) OVERRIDE;
+
+ virtual ash::LauncherID GetLauncherIDForAppID(
+ const std::string& app_id) OVERRIDE;
+ virtual std::string GetAppIDForLauncherID(ash::LauncherID id) OVERRIDE;
+
+ // Sets the image for an app tab. This is intended to be invoked from the
+ // AppIconLoader.
+ virtual void SetAppImage(const std::string& app_id,
+ const gfx::ImageSkia& image) OVERRIDE;
+
+ // Returns true if a pinned launcher item with given |app_id| could be found.
+ virtual bool IsAppPinned(const std::string& app_id) OVERRIDE;
+
+ // Pins an app with |app_id| to launcher. If there is a running instance in
+ // launcher, the running instance is pinned. If there is no running instance,
+ // a new launcher item is created and pinned.
+ virtual void PinAppWithID(const std::string& app_id) OVERRIDE;
+
+ // Updates the launche type of the app for the specified id to |launch_type|.
+ virtual void SetLaunchType(
+ ash::LauncherID id,
+ extensions::ExtensionPrefs::LaunchType launch_type) OVERRIDE;
+
+ // Unpins any app items whose id is |app_id|.
+ virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE;
+
+ // Returns true if the user is currently logged in as a guest.
+ virtual bool IsLoggedInAsGuest() OVERRIDE;
+
+ // Invoked when user clicks on button in the launcher and there is no last
+ // used window (or CTRL is held with the click).
+ virtual void CreateNewWindow() OVERRIDE;
+
+ // Invoked when the user clicks on button in the launcher to create a new
+ // incognito window.
+ virtual void CreateNewIncognitoWindow() OVERRIDE;
+
+ // Checks whether the user is allowed to pin apps. Pinning may be disallowed
+ // by policy in case there is a pre-defined set of pinned apps.
+ virtual bool CanPin() const OVERRIDE;
+
+ // Updates the pinned pref state. The pinned state consists of a list pref.
+ // Each item of the list is a dictionary. The key |kAppIDPath| gives the
+ // id of the app.
+ virtual void PersistPinnedState() OVERRIDE;
+
+ virtual ash::LauncherModel* model() OVERRIDE;
+
+ virtual Profile* profile() OVERRIDE;
+
+ virtual void SetAutoHideBehavior(ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) OVERRIDE;
+
+ // The tab no longer represents its previously identified application.
+ virtual void RemoveTabFromRunningApp(TabContents* tab,
+ const std::string& app_id) OVERRIDE;
+
+ // Notify the controller that the state of an non platform app's tabs
+ // have changed,
+ virtual void UpdateAppState(content::WebContents* contents,
+ AppState app_state) OVERRIDE;
+
+ // Limits application refocusing to urls that match |url| for |id|.
+ virtual void SetRefocusURLPattern(ash::LauncherID id,
+ const GURL& url) OVERRIDE;
+
+ // Returns the extension identified by |app_id|.
+ virtual const extensions::Extension* GetExtensionForAppID(
+ const std::string& app_id) OVERRIDE;
+
+ // ash::LauncherDelegate overrides:
+ virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE;
+ virtual void ItemClicked(const ash::LauncherItem& item,
+ int event_flags) OVERRIDE;
+ virtual int GetBrowserShortcutResourceId() OVERRIDE;
+ virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE;
+ virtual ui::MenuModel* CreateContextMenu(
+ const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE;
+ virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE;
+ virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE;
+
+ // ash::LauncherModelObserver overrides:
+ virtual void LauncherItemAdded(int index) OVERRIDE;
+ virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE;
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE;
+ virtual void LauncherItemChanged(int index,
+ const ash::LauncherItem& old_item) OVERRIDE;
+ virtual void LauncherStatusChanged() OVERRIDE;
+
+ // Overridden from content::NotificationObserver:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Overridden from PrefObserver:
+ virtual void OnPreferenceChanged(PrefServiceBase* service,
+ const std::string& pref_name) OVERRIDE;
+
+ // Overridden from ash::ShellObserver:
+ virtual void OnShelfAlignmentChanged() OVERRIDE;
+
+ // Overridden from PrefServiceObserver:
+ virtual void OnIsSyncingChanged() OVERRIDE;
+
+ // Overridden from AppSyncUIStateObserver
+ virtual void OnAppSyncUIStatusChanged() OVERRIDE;
+
+ protected:
+ // ChromeLauncherController overrides:
+
+ // Creates a new app shortcut item and controller on the launcher at |index|.
+ // Use kInsertItemAtEnd to add a shortcut as the last item.
+ virtual ash::LauncherID CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) OVERRIDE;
+
+ // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class.
+ // These are intended for testing.
+ virtual void SetAppTabHelperForTest(AppTabHelper* helper) OVERRIDE;
+ virtual void SetAppIconLoaderForTest(AppIconLoader* loader) OVERRIDE;
+ virtual const std::string& GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) OVERRIDE;
+
+ private:
+ friend class ChromeLauncherControllerPerAppTest;
+
+ typedef std::map<ash::LauncherID, LauncherItemController*>
+ IDToItemControllerMap;
+ typedef std::list<TabContents*> TabContentsList;
+ typedef std::map<std::string, TabContentsList> AppIDToTabContentsListMap;
+ typedef std::map<TabContents*, std::string> TabContentsToAppIDMap;
+
+ // Returns the profile used for new windows.
+ Profile* GetProfileForNewWindows();
+
+ // Invoked when the associated browser or app is closed.
+ void LauncherItemClosed(ash::LauncherID id);
+
+ // Internal helpers for pinning and unpinning that handle both
+ // client-triggered and internal pinning operations.
+ void DoPinAppWithID(const std::string& app_id);
+ void DoUnpinAppsWithID(const std::string& app_id);
+
+ // Re-syncs launcher model with prefs::kPinnedLauncherApps.
+ void UpdateAppLaunchersFromPref();
+
+ // Sets the shelf auto-hide behavior from prefs.
+ void SetShelfAutoHideBehaviorFromPrefs();
+
+ // Sets the shelf alignment from prefs.
+ void SetShelfAlignmentFromPrefs();
+
+ // Returns the most recently active tab contents for an app.
+ TabContents* GetLastActiveTabContents(const std::string& app_id);
+
+ // Creates an app launcher to insert at |index|. Note that |index| may be
+ // adjusted by the model to meet ordering constraints.
+ ash::LauncherID InsertAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status,
+ int index);
+
+ bool HasItemController(ash::LauncherID id) const;
+
+ ash::LauncherModel* model_;
+
+ // Profile used for prefs and loading extensions. This is NOT necessarily the
+ // profile new windows are created with.
+ Profile* profile_;
+
+ IDToItemControllerMap id_to_item_controller_map_;
+
+ // Maintains activation order of tab contents for each app.
+ AppIDToTabContentsListMap app_id_to_tab_contents_list_;
+
+ // Direct access to app_id for a tab contents.
+ TabContentsToAppIDMap tab_contents_to_app_id_;
+
+ // Used to track shell windows.
+ scoped_ptr<ShellWindowLauncherController> shell_window_controller_;
+
+ // Used to get app info for tabs.
+ scoped_ptr<AppTabHelper> app_tab_helper_;
+
+ // Used to load the image for an app item.
+ scoped_ptr<AppIconLoader> app_icon_loader_;
+
+ content::NotificationRegistrar notification_registrar_;
+
+ PrefChangeRegistrar pref_change_registrar_;
+
+ AppSyncUIState* app_sync_ui_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerApp);
+};
+
+#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc
index 5b4231b..dc6b2e7 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h"
#include <algorithm>
#include <string>
@@ -32,9 +32,9 @@ namespace {
const int kExpectedAppIndex = 1;
}
-class ChromeLauncherControllerTest : public testing::Test {
+class ChromeLauncherControllerPerAppTest : public testing::Test {
protected:
- ChromeLauncherControllerTest()
+ ChromeLauncherControllerPerAppTest()
: ui_thread_(content::BrowserThread::UI, &loop_),
file_thread_(content::BrowserThread::FILE, &loop_),
profile_(new TestingProfile()),
@@ -86,13 +86,13 @@ class ChromeLauncherControllerTest : public testing::Test {
}
// Gets the currently configured app launchers from the controller.
- void GetAppLaunchers(ChromeLauncherController* controller,
+ void GetAppLaunchers(ChromeLauncherControllerPerApp* controller,
std::vector<std::string>* launchers) {
launchers->clear();
for (ash::LauncherItems::const_iterator iter(model_.items().begin());
iter != model_.items().end(); ++iter) {
- ChromeLauncherController::IDToItemControllerMap::const_iterator entry(
- controller->id_to_item_controller_map_.find(iter->id));
+ ChromeLauncherControllerPerApp::IDToItemControllerMap::const_iterator
+ entry(controller->id_to_item_controller_map_.find(iter->id));
if (iter->type == ash::TYPE_APP_SHORTCUT &&
entry != controller->id_to_item_controller_map_.end()) {
launchers->push_back(entry->second->app_id());
@@ -114,11 +114,11 @@ class ChromeLauncherControllerTest : public testing::Test {
ExtensionService* extension_service_;
- DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerTest);
+ DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerAppTest);
};
-TEST_F(ChromeLauncherControllerTest, DefaultApps) {
- ChromeLauncherController launcher_controller(profile_.get(), &model_);
+TEST_F(ChromeLauncherControllerPerAppTest, DefaultApps) {
+ ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_);
launcher_controller.Init();
// Model should only contain the browser shortcut and app list items.
@@ -136,7 +136,7 @@ TEST_F(ChromeLauncherControllerTest, DefaultApps) {
EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id()));
}
-TEST_F(ChromeLauncherControllerTest, Policy) {
+TEST_F(ChromeLauncherControllerPerAppTest, Policy) {
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension3_.get());
@@ -149,7 +149,7 @@ TEST_F(ChromeLauncherControllerTest, Policy) {
// Only |extension1_| should get pinned. |extension2_| is specified but not
// installed, and |extension3_| is part of the default set, but that shouldn't
// take effect when the policy override is in place.
- ChromeLauncherController launcher_controller(profile_.get(), &model_);
+ ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_);
launcher_controller.Init();
EXPECT_EQ(3, model_.item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type);
@@ -177,11 +177,11 @@ TEST_F(ChromeLauncherControllerTest, Policy) {
EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
}
-TEST_F(ChromeLauncherControllerTest, UnpinWithUninstall) {
+TEST_F(ChromeLauncherControllerPerAppTest, UnpinWithUninstall) {
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
- ChromeLauncherController launcher_controller(profile_.get(), &model_);
+ ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_);
launcher_controller.Init();
EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id()));
@@ -194,11 +194,11 @@ TEST_F(ChromeLauncherControllerTest, UnpinWithUninstall) {
EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id()));
}
-TEST_F(ChromeLauncherControllerTest, PrefUpdates) {
+TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) {
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
- ChromeLauncherController controller(profile_.get(), &model_);
+ ChromeLauncherControllerPerApp controller(profile_.get(), &model_);
std::vector<std::string> expected_launchers;
std::vector<std::string> actual_launchers;
@@ -249,10 +249,10 @@ TEST_F(ChromeLauncherControllerTest, PrefUpdates) {
EXPECT_EQ(expected_launchers, actual_launchers);
}
-TEST_F(ChromeLauncherControllerTest, PendingInsertionOrder) {
+TEST_F(ChromeLauncherControllerPerAppTest, PendingInsertionOrder) {
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension3_.get());
- ChromeLauncherController controller(profile_.get(), &model_);
+ ChromeLauncherControllerPerApp controller(profile_.get(), &model_);
base::ListValue pref_value;
InsertPrefValue(&pref_value, 0, extension1_->id());
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc
new file mode 100644
index 0000000..4248c15
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc
@@ -0,0 +1,1115 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h"
+
+#include <vector>
+
+#include "ash/launcher/launcher_model.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/values.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/prefs/scoped_user_pref_update.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/app_sync_ui_state.h"
+#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
+#include "chrome/browser/ui/ash/extension_utils.h"
+#include "chrome/browser/ui/ash/launcher/launcher_app_icon_loader.h"
+#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
+#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
+#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
+#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tab_contents/tab_contents.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/url_pattern.h"
+#include "grit/theme_resources.h"
+#include "ui/aura/window.h"
+
+using extensions::Extension;
+
+namespace {
+
+// Item controller for an app shortcut. Shortcuts track app and launcher ids,
+// but do not have any associated windows (opening a shortcut will replace the
+// item with the appropriate LauncherItemController type).
+class AppShortcutLauncherItemController : public LauncherItemController {
+ public:
+ AppShortcutLauncherItemController(
+ const std::string& app_id,
+ ChromeLauncherControllerPerBrowser* controller)
+ : LauncherItemController(TYPE_SHORTCUT, app_id, controller) {
+ // Google Drive should just refocus to it's main app UI.
+ // TODO(davemoore): Generalize this for other applications.
+ if (app_id == "apdfllckaahabafndbhieahigkjlhalf") {
+ const Extension* extension =
+ launcher_controller()->GetExtensionForAppID(app_id);
+ refocus_url_ = GURL(extension->launch_web_url() + "*");
+ }
+ }
+
+ virtual ~AppShortcutLauncherItemController() {}
+
+ // LauncherItemController overrides:
+ virtual string16 GetTitle() OVERRIDE {
+ return GetAppTitle();
+ }
+
+ virtual bool HasWindow(aura::Window* window) const OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsOpen() const OVERRIDE {
+ return false;
+ }
+
+ virtual void Launch(int event_flags) OVERRIDE {
+ launcher_controller()->LaunchApp(app_id(), event_flags);
+ }
+
+ virtual void Activate() OVERRIDE {
+ launcher_controller()->ActivateApp(app_id(), ui::EF_NONE);
+ }
+
+ virtual void Close() OVERRIDE {
+ // TODO: maybe should treat as unpin?
+ }
+
+ virtual void Clicked() OVERRIDE {
+ Activate();
+ }
+
+ virtual void OnRemoved() OVERRIDE {
+ // AppShortcutLauncherItemController is unowned; delete on removal.
+ delete this;
+ }
+
+ virtual void LauncherItemChanged(
+ int model_index,
+ const ash::LauncherItem& old_item) OVERRIDE {
+ }
+
+ // Stores the optional refocus url pattern for this item.
+ const GURL& refocus_url() const { return refocus_url_; }
+ void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; }
+
+ private:
+ GURL refocus_url_;
+ DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController);
+};
+
+// If the value of the pref at |local_path is not empty, it is returned
+// otherwise the value of the pref at |synced_path| is returned.
+std::string GetLocalOrRemotePref(PrefService* pref_service,
+ const char* local_path,
+ const char* synced_path) {
+ const std::string value(pref_service->GetString(local_path));
+ return value.empty() ? pref_service->GetString(synced_path) : value;
+}
+
+// If prefs have synced and the pref value at |local_path| is empty the value
+// from |synced_path| is copied to |local_path|.
+void MaybePropagatePrefToLocal(PrefService* pref_service,
+ const char* local_path,
+ const char* synced_path) {
+ if (pref_service->GetString(local_path).empty() &&
+ pref_service->IsSyncing()) {
+ // First time the user is using this machine, propagate from remote to
+ // local.
+ pref_service->SetString(local_path, pref_service->GetString(synced_path));
+ }
+}
+
+} // namespace
+
+// ChromeLauncherControllerPerBrowser -----------------------------------------
+
+ChromeLauncherControllerPerBrowser::ChromeLauncherControllerPerBrowser(
+ Profile* profile,
+ ash::LauncherModel* model)
+ : model_(model),
+ profile_(profile),
+ app_sync_ui_state_(NULL) {
+ if (!profile_) {
+ // Use the original profile as on chromeos we may get a temporary off the
+ // record profile.
+ profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile();
+
+ app_sync_ui_state_ = AppSyncUIState::Get(profile_);
+ if (app_sync_ui_state_)
+ app_sync_ui_state_->AddObserver(this);
+ }
+
+ model_->AddObserver(this);
+ // TODO(stevenjb): Find a better owner for shell_window_controller_?
+ shell_window_controller_.reset(new ShellWindowLauncherController(this));
+ app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
+ app_icon_loader_.reset(new LauncherAppIconLoader(profile_, this));
+
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_LOADED,
+ content::Source<Profile>(profile_));
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(profile_));
+ pref_change_registrar_.Init(profile_->GetPrefs());
+ pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
+}
+
+ChromeLauncherControllerPerBrowser::~ChromeLauncherControllerPerBrowser() {
+ // Reset the shell window controller here since it has a weak pointer to
+ // this.
+ shell_window_controller_.reset();
+
+ model_->RemoveObserver(this);
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ i->second->OnRemoved();
+ model_->RemoveItemAt(model_->ItemIndexByID(i->first));
+ }
+
+ if (ash::Shell::HasInstance())
+ ash::Shell::GetInstance()->RemoveShellObserver(this);
+
+ if (app_sync_ui_state_)
+ app_sync_ui_state_->RemoveObserver(this);
+
+ profile_->GetPrefs()->RemoveObserver(this);
+}
+
+void ChromeLauncherControllerPerBrowser::Init() {
+ // TODO(xiyuan): Remove migration code and kUseDefaultPinnedApp after M20.
+ // Migration cases:
+ // - Users that unpin all apps:
+ // - have default pinned apps
+ // - kUseDefaultPinnedApps set to false
+ // Migrate them by setting an empty list for kPinnedLauncherApps.
+ //
+ // - Users that have customized pinned apps:
+ // - have non-default non-empty pinned apps list
+ // - kUseDefaultPinnedApps set to false
+ // Nothing needs to be done because customized pref overrides default.
+ //
+ // - Users that have default apps (i.e. new user or never pin/unpin):
+ // - have default pinned apps
+ // - kUseDefaultPinnedApps is still true
+ // Nothing needs to be done because they should get the default.
+ if (profile_->GetPrefs()->FindPreference(
+ prefs::kPinnedLauncherApps)->IsDefaultValue() &&
+ !profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) {
+ ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
+ updater.Get()->Clear();
+ }
+
+ UpdateAppLaunchersFromPref();
+
+ // TODO(sky): update unit test so that this test isn't necessary.
+ if (ash::Shell::HasInstance()) {
+ SetShelfAutoHideBehaviorFromPrefs();
+ SetShelfAlignmentFromPrefs();
+ PrefService* prefs = profile_->GetPrefs();
+ if (prefs->GetString(prefs::kShelfAlignmentLocal).empty() ||
+ prefs->GetString(prefs::kShelfAutoHideBehaviorLocal).empty()) {
+ prefs->AddObserver(this);
+ }
+ ash::Shell::GetInstance()->AddShellObserver(this);
+ }
+}
+
+ash::LauncherID ChromeLauncherControllerPerBrowser::CreateTabbedLauncherItem(
+ LauncherItemController* controller,
+ IncognitoState is_incognito,
+ ash::LauncherItemStatus status) {
+ ash::LauncherID id = model_->next_id();
+ DCHECK(!HasItemController(id));
+ DCHECK(controller);
+ id_to_item_controller_map_[id] = controller;
+ controller->set_launcher_id(id);
+
+ ash::LauncherItem item;
+ item.type = ash::TYPE_TABBED;
+ item.is_incognito = (is_incognito == STATE_INCOGNITO);
+ item.status = status;
+ model_->Add(item);
+ return id;
+}
+
+ash::LauncherID ChromeLauncherControllerPerBrowser::CreateAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status) {
+ DCHECK(controller);
+ return InsertAppLauncherItem(controller, app_id, status,
+ model_->item_count());
+}
+
+void ChromeLauncherControllerPerBrowser::SetItemStatus(
+ ash::LauncherID id,
+ ash::LauncherItemStatus status) {
+ int index = model_->ItemIndexByID(id);
+ DCHECK_GE(index, 0);
+ ash::LauncherItem item = model_->items()[index];
+ item.status = status;
+ model_->Set(index, item);
+}
+
+void ChromeLauncherControllerPerBrowser::SetItemController(
+ ash::LauncherID id,
+ LauncherItemController* controller) {
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ iter->second->OnRemoved();
+ iter->second = controller;
+ controller->set_launcher_id(id);
+}
+
+void ChromeLauncherControllerPerBrowser::CloseLauncherItem(
+ ash::LauncherID id) {
+ if (IsPinned(id)) {
+ // Create a new shortcut controller.
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ SetItemStatus(id, ash::STATUS_CLOSED);
+ std::string app_id = iter->second->app_id();
+ iter->second->OnRemoved();
+ iter->second = new AppShortcutLauncherItemController(app_id, this);
+ iter->second->set_launcher_id(id);
+ } else {
+ LauncherItemClosed(id);
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::Unpin(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ if (controller->type() == LauncherItemController::TYPE_APP) {
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItem item = model_->items()[index];
+ item.type = ash::TYPE_PLATFORM_APP;
+ model_->Set(index, item);
+ } else {
+ LauncherItemClosed(id);
+ }
+ if (CanPin())
+ PersistPinnedState();
+}
+
+void ChromeLauncherControllerPerBrowser::Pin(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItem item = model_->items()[index];
+
+ if (item.type != ash::TYPE_PLATFORM_APP)
+ return;
+
+ item.type = ash::TYPE_APP_SHORTCUT;
+ model_->Set(index, item);
+
+ if (CanPin())
+ PersistPinnedState();
+}
+
+bool ChromeLauncherControllerPerBrowser::IsPinned(ash::LauncherID id) {
+ int index = model_->ItemIndexByID(id);
+ ash::LauncherItemType type = model_->items()[index].type;
+ return type == ash::TYPE_APP_SHORTCUT;
+}
+
+void ChromeLauncherControllerPerBrowser::TogglePinned(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return; // May happen if item closed with menu open.
+
+ if (IsPinned(id))
+ Unpin(id);
+ else
+ Pin(id);
+}
+
+bool ChromeLauncherControllerPerBrowser::IsPinnable(ash::LauncherID id) const {
+ int index = model_->ItemIndexByID(id);
+ if (index == -1)
+ return false;
+
+ ash::LauncherItemType type = model_->items()[index].type;
+ return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) &&
+ CanPin());
+}
+
+void ChromeLauncherControllerPerBrowser::Launch(
+ ash::LauncherID id, int event_flags) {
+ if (!HasItemController(id))
+ return; // In case invoked from menu and item closed while menu up.
+ id_to_item_controller_map_[id]->Launch(event_flags);
+}
+
+void ChromeLauncherControllerPerBrowser::Close(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return; // May happen if menu closed.
+ id_to_item_controller_map_[id]->Close();
+}
+
+bool ChromeLauncherControllerPerBrowser::IsOpen(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return false;
+ return id_to_item_controller_map_[id]->IsOpen();
+}
+
+bool ChromeLauncherControllerPerBrowser::IsPlatformApp(ash::LauncherID id) {
+ if (!HasItemController(id))
+ return false;
+
+ std::string app_id = GetAppIDForLauncherID(id);
+ const Extension* extension = GetExtensionForAppID(app_id);
+ DCHECK(extension);
+ return extension->is_platform_app();
+}
+
+void ChromeLauncherControllerPerBrowser::LaunchApp(const std::string& app_id,
+ int event_flags) {
+ const Extension* extension = GetExtensionForAppID(app_id);
+ extension_utils::OpenExtension(GetProfileForNewWindows(),
+ extension,
+ event_flags);
+}
+
+void ChromeLauncherControllerPerBrowser::ActivateApp(const std::string& app_id,
+ int event_flags) {
+ if (app_id == extension_misc::kChromeAppId) {
+ OnBrowserShortcutClicked(event_flags);
+ return;
+ }
+
+ // If there is an existing non-shortcut controller for this app, open it.
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
+ refocus_pattern.SetMatchAllURLs(true);
+
+ if (id > 0) {
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ if (controller->type() != LauncherItemController::TYPE_SHORTCUT) {
+ controller->Activate();
+ return;
+ }
+
+ AppShortcutLauncherItemController* app_controller =
+ static_cast<AppShortcutLauncherItemController*>(controller);
+ const GURL refocus_url = app_controller->refocus_url();
+
+ if (!refocus_url.is_empty())
+ refocus_pattern.Parse(refocus_url.spec());
+ }
+
+ // Check if there are any open tabs for this app.
+ AppIDToTabContentsListMap::iterator app_i =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (app_i != app_id_to_tab_contents_list_.end()) {
+ for (TabContentsList::iterator tab_i = app_i->second.begin();
+ tab_i != app_i->second.end();
+ ++tab_i) {
+ TabContents* tab = *tab_i;
+ const GURL tab_url = tab->web_contents()->GetURL();
+ if (refocus_pattern.MatchesURL(tab_url)) {
+ Browser* browser = browser::FindBrowserWithWebContents(
+ tab->web_contents());
+ TabStripModel* tab_strip = browser->tab_strip_model();
+ int index = tab_strip->GetIndexOfTabContents(tab);
+ DCHECK_NE(TabStripModel::kNoTab, index);
+ tab_strip->ActivateTabAt(index, false);
+ browser->window()->Show();
+ ash::wm::ActivateWindow(browser->window()->GetNativeWindow());
+ return;
+ }
+ }
+ }
+
+ LaunchApp(app_id, event_flags);
+}
+
+extensions::ExtensionPrefs::LaunchType
+ ChromeLauncherControllerPerBrowser::GetLaunchType(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ const Extension* extension = GetExtensionForAppID(
+ id_to_item_controller_map_[id]->app_id());
+ return profile_->GetExtensionService()->extension_prefs()->GetLaunchType(
+ extension,
+ extensions::ExtensionPrefs::LAUNCH_DEFAULT);
+}
+
+std::string ChromeLauncherControllerPerBrowser::GetAppID(
+ content::WebContents* tab) {
+ return app_tab_helper_->GetAppID(tab);
+}
+
+ash::LauncherID ChromeLauncherControllerPerBrowser::GetLauncherIDForAppID(
+ const std::string& app_id) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->app_id() == app_id)
+ return i->first;
+ }
+ return 0;
+}
+
+std::string ChromeLauncherControllerPerBrowser::GetAppIDForLauncherID(
+ ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+ return id_to_item_controller_map_[id]->app_id();
+}
+
+void ChromeLauncherControllerPerBrowser::SetAppImage(const std::string& id,
+ const gfx::ImageSkia& image) {
+ // TODO: need to get this working for shortcuts.
+
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->app_id() != id)
+ continue;
+
+ // Panel items may share the same app_id as the app that created them,
+ // but they set their icon image in
+ // BrowserLauncherItemController::UpdateLauncher(), so do not set panel
+ // images here.
+ if (i->second->type() == LauncherItemController::TYPE_EXTENSION_PANEL)
+ continue;
+
+ int index = model_->ItemIndexByID(i->first);
+ ash::LauncherItem item = model_->items()[index];
+ item.image = image;
+ model_->Set(index, item);
+ // It's possible we're waiting on more than one item, so don't break.
+ }
+}
+
+bool ChromeLauncherControllerPerBrowser::IsAppPinned(
+ const std::string& app_id) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (IsPinned(i->first) && i->second->app_id() == app_id)
+ return true;
+ }
+ return false;
+}
+
+void ChromeLauncherControllerPerBrowser::PinAppWithID(
+ const std::string& app_id) {
+ if (CanPin())
+ DoPinAppWithID(app_id);
+ else
+ NOTREACHED();
+}
+
+void ChromeLauncherControllerPerBrowser::SetLaunchType(
+ ash::LauncherID id,
+ extensions::ExtensionPrefs::LaunchType launch_type) {
+ if (!HasItemController(id))
+ return;
+
+ return profile_->GetExtensionService()->extension_prefs()->SetLaunchType(
+ id_to_item_controller_map_[id]->app_id(), launch_type);
+}
+
+void ChromeLauncherControllerPerBrowser::UnpinAppsWithID(
+ const std::string& app_id) {
+ if (CanPin())
+ DoUnpinAppsWithID(app_id);
+ else
+ NOTREACHED();
+}
+
+bool ChromeLauncherControllerPerBrowser::IsLoggedInAsGuest() {
+ return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord();
+}
+
+void ChromeLauncherControllerPerBrowser::CreateNewWindow() {
+ chrome::NewEmptyWindow(
+ GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH);
+}
+
+void ChromeLauncherControllerPerBrowser::CreateNewIncognitoWindow() {
+ chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile());
+}
+
+bool ChromeLauncherControllerPerBrowser::CanPin() const {
+ const PrefService::Preference* pref =
+ profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
+ return pref && pref->IsUserModifiable();
+}
+
+void ChromeLauncherControllerPerBrowser::SetAutoHideBehavior(
+ ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) {
+ ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
+ behavior,
+ root_window);
+ // TODO(oshima): Support multiple launcher.
+ if (root_window != ash::Shell::GetPrimaryRootWindow())
+ return;
+
+ const char* value = NULL;
+ switch (behavior) {
+ case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
+ value = ash::kShelfAutoHideBehaviorAlways;
+ break;
+ case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
+ value = ash::kShelfAutoHideBehaviorNever;
+ break;
+ }
+ // See comment in |kShelfAlignment| about why we have two prefs here.
+ profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
+ profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
+}
+
+void ChromeLauncherControllerPerBrowser::RemoveTabFromRunningApp(
+ TabContents* tab,
+ const std::string& app_id) {
+ tab_contents_to_app_id_.erase(tab);
+ AppIDToTabContentsListMap::iterator i_app_id =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (i_app_id != app_id_to_tab_contents_list_.end()) {
+ TabContentsList* tab_list = &i_app_id->second;
+ tab_list->remove(tab);
+ if (tab_list->empty()) {
+ app_id_to_tab_contents_list_.erase(i_app_id);
+ i_app_id = app_id_to_tab_contents_list_.end();
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id > 0)
+ SetItemStatus(id, ash::STATUS_CLOSED);
+ }
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::UpdateAppState(
+ content::WebContents* contents,
+ AppState app_state) {
+ std::string app_id = GetAppID(contents);
+
+ // Check the old |app_id| for a tab. If the contents has changed we need to
+ // remove it from the previous app.
+ TabContents* tab = TabContents::FromWebContents(contents);
+ if (tab_contents_to_app_id_.find(tab) != tab_contents_to_app_id_.end()) {
+ std::string last_app_id = tab_contents_to_app_id_[tab];
+ if (last_app_id != app_id)
+ RemoveTabFromRunningApp(tab, last_app_id);
+ }
+
+ if (app_id.empty())
+ return;
+
+ tab_contents_to_app_id_[tab] = app_id;
+
+ if (app_state == APP_STATE_REMOVED) {
+ // The tab has gone away.
+ RemoveTabFromRunningApp(tab, app_id);
+ } else {
+ TabContentsList& tab_list(app_id_to_tab_contents_list_[app_id]);
+
+ if (app_state == APP_STATE_INACTIVE) {
+ TabContentsList::const_iterator i_tab =
+ std::find(tab_list.begin(), tab_list.end(), tab);
+ if (i_tab == tab_list.end())
+ tab_list.push_back(tab);
+ if (i_tab != tab_list.begin()) {
+ // Going inactive, but wasn't the front tab, indicating that a new
+ // tab has already become active.
+ return;
+ }
+ } else {
+ tab_list.remove(tab);
+ tab_list.push_front(tab);
+ }
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id > 0) {
+ // If the window is active, mark the app as active.
+ SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
+ ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
+ }
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::SetRefocusURLPattern(
+ ash::LauncherID id,
+ const GURL& url) {
+ DCHECK(HasItemController(id));
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+
+ int index = model_->ItemIndexByID(id);
+ if (index == -1) {
+ NOTREACHED() << "Invalid launcher id";
+ return;
+ }
+
+ ash::LauncherItemType type = model_->items()[index].type;
+ if (type == ash::TYPE_APP_SHORTCUT) {
+ AppShortcutLauncherItemController* app_controller =
+ static_cast<AppShortcutLauncherItemController*>(controller);
+ app_controller->set_refocus_url(url);
+ } else {
+ NOTREACHED() << "Invalid launcher type";
+ }
+}
+
+const Extension* ChromeLauncherControllerPerBrowser::GetExtensionForAppID(
+ const std::string& app_id) {
+ return profile_->GetExtensionService()->GetInstalledExtension(app_id);
+}
+
+void ChromeLauncherControllerPerBrowser::OnBrowserShortcutClicked(
+ int event_flags) {
+ if (event_flags & ui::EF_CONTROL_DOWN) {
+ CreateNewWindow();
+ return;
+ }
+
+ Browser* last_browser = browser::FindTabbedBrowser(
+ GetProfileForNewWindows(), true, chrome::HOST_DESKTOP_TYPE_ASH);
+
+ if (!last_browser) {
+ CreateNewWindow();
+ return;
+ }
+
+ aura::Window* window = last_browser->window()->GetNativeWindow();
+ window->Show();
+ ash::wm::ActivateWindow(window);
+}
+
+void ChromeLauncherControllerPerBrowser::ItemClicked(
+ const ash::LauncherItem& item,
+ int event_flags) {
+ DCHECK(HasItemController(item.id));
+ id_to_item_controller_map_[item.id]->Clicked();
+}
+
+int ChromeLauncherControllerPerBrowser::GetBrowserShortcutResourceId() {
+ return IDR_PRODUCT_LOGO_32;
+}
+
+string16 ChromeLauncherControllerPerBrowser::GetTitle(
+ const ash::LauncherItem& item) {
+ DCHECK(HasItemController(item.id));
+ return id_to_item_controller_map_[item.id]->GetTitle();
+}
+
+ui::MenuModel* ChromeLauncherControllerPerBrowser::CreateContextMenu(
+ const ash::LauncherItem& item,
+ aura::RootWindow* root_window) {
+ return new LauncherContextMenu(this, &item, root_window);
+}
+
+ash::LauncherID ChromeLauncherControllerPerBrowser::GetIDByWindow(
+ aura::Window* window) {
+ for (IDToItemControllerMap::const_iterator i =
+ id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ if (i->second->HasWindow(window))
+ return i->first;
+ }
+ return 0;
+}
+
+bool ChromeLauncherControllerPerBrowser::IsDraggable(
+ const ash::LauncherItem& item) {
+ return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true;
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherItemAdded(int index) {
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherItemRemoved(
+ int index,
+ ash::LauncherID id) {
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherItemMoved(
+ int start_index,
+ int target_index) {
+ ash::LauncherID id = model_->items()[target_index].id;
+ if (HasItemController(id) && IsPinned(id))
+ PersistPinnedState();
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherItemChanged(
+ int index,
+ const ash::LauncherItem& old_item) {
+ ash::LauncherID id = model_->items()[index].id;
+ id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item);
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherStatusChanged() {
+}
+
+void ChromeLauncherControllerPerBrowser::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_LOADED: {
+ UpdateAppLaunchersFromPref();
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+ const content::Details<extensions::UnloadedExtensionInfo> unload_info(
+ details);
+ const Extension* extension = unload_info->extension;
+ if (IsAppPinned(extension->id()))
+ DoUnpinAppsWithID(extension->id());
+ app_icon_loader_->ClearImage(extension->id());
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification type=" << type;
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::OnPreferenceChanged(
+ PrefServiceBase* service,
+ const std::string& pref_name) {
+ if (pref_name == prefs::kPinnedLauncherApps) {
+ UpdateAppLaunchersFromPref();
+ } else if (pref_name == prefs::kShelfAlignmentLocal) {
+ SetShelfAlignmentFromPrefs();
+ } else if (pref_name == prefs::kShelfAutoHideBehaviorLocal) {
+ SetShelfAutoHideBehaviorFromPrefs();
+ } else {
+ NOTREACHED() << "Unexpected pref change for " << pref_name;
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::OnShelfAlignmentChanged() {
+ const char* pref_value = NULL;
+ // TODO(oshima): Support multiple displays.
+ switch (ash::Shell::GetInstance()->GetShelfAlignment(
+ ash::Shell::GetPrimaryRootWindow())) {
+ case ash::SHELF_ALIGNMENT_BOTTOM:
+ pref_value = ash::kShelfAlignmentBottom;
+ break;
+ case ash::SHELF_ALIGNMENT_LEFT:
+ pref_value = ash::kShelfAlignmentLeft;
+ break;
+ case ash::SHELF_ALIGNMENT_RIGHT:
+ pref_value = ash::kShelfAlignmentRight;
+ break;
+ }
+ // See comment in |kShelfAlignment| about why we have two prefs here.
+ profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
+ profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
+}
+
+void ChromeLauncherControllerPerBrowser::OnIsSyncingChanged() {
+ MaybePropagatePrefToLocal(profile_->GetPrefs(),
+ prefs::kShelfAlignmentLocal,
+ prefs::kShelfAlignment);
+ MaybePropagatePrefToLocal(profile_->GetPrefs(),
+ prefs::kShelfAutoHideBehaviorLocal,
+ prefs::kShelfAutoHideBehavior);
+}
+
+void ChromeLauncherControllerPerBrowser::OnAppSyncUIStatusChanged() {
+ if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
+ model_->SetStatus(ash::LauncherModel::STATUS_LOADING);
+ else
+ model_->SetStatus(ash::LauncherModel::STATUS_NORMAL);
+}
+
+void ChromeLauncherControllerPerBrowser::PersistPinnedState() {
+ // It is a coding error to call PersistPinnedState() if the pinned apps are
+ // not user-editable. The code should check earlier and not perform any
+ // modification actions that trigger persisting the state.
+ if (!CanPin()) {
+ NOTREACHED() << "Can't pin but pinned state being updated";
+ return;
+ }
+
+ // Set kUseDefaultPinnedApps to false and use pinned apps list from prefs
+ // from now on.
+ profile_->GetPrefs()->SetBoolean(prefs::kUseDefaultPinnedApps, false);
+
+ // Mutating kPinnedLauncherApps is going to notify us and trigger us to
+ // process the change. We don't want that to happen so remove ourselves as a
+ // listener.
+ pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
+ {
+ ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
+ updater->Clear();
+ for (size_t i = 0; i < model_->items().size(); ++i) {
+ if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
+ ash::LauncherID id = model_->items()[i].id;
+ if (HasItemController(id) && IsPinned(id)) {
+ base::DictionaryValue* app_value = ash::CreateAppDict(
+ id_to_item_controller_map_[id]->app_id());
+ if (app_value)
+ updater->Append(app_value);
+ }
+ }
+ }
+ }
+ pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this);
+}
+
+ash::LauncherModel* ChromeLauncherControllerPerBrowser::model() {
+ return model_;
+}
+
+Profile* ChromeLauncherControllerPerBrowser::profile() {
+ return profile_;
+}
+
+Profile* ChromeLauncherControllerPerBrowser::GetProfileForNewWindows() {
+ return ProfileManager::GetDefaultProfileOrOffTheRecord();
+}
+
+void ChromeLauncherControllerPerBrowser::LauncherItemClosed(
+ ash::LauncherID id) {
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ DCHECK(iter != id_to_item_controller_map_.end());
+ app_icon_loader_->ClearImage(iter->second->app_id());
+ iter->second->OnRemoved();
+ id_to_item_controller_map_.erase(iter);
+ model_->RemoveItemAt(model_->ItemIndexByID(id));
+}
+
+void ChromeLauncherControllerPerBrowser::DoPinAppWithID(
+ const std::string& app_id) {
+ // If there is an item, do nothing and return.
+ if (IsAppPinned(app_id))
+ return;
+
+ ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
+ if (launcher_id) {
+ // App item exists, pin it
+ Pin(launcher_id);
+ } else {
+ // Otherwise, create a shortcut item for it.
+ CreateAppShortcutLauncherItem(app_id, model_->item_count());
+ if (CanPin())
+ PersistPinnedState();
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::DoUnpinAppsWithID(
+ const std::string& app_id) {
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ) {
+ IDToItemControllerMap::iterator current(i);
+ ++i;
+ if (current->second->app_id() == app_id && IsPinned(current->first))
+ Unpin(current->first);
+ }
+}
+
+void ChromeLauncherControllerPerBrowser::UpdateAppLaunchersFromPref() {
+ // Construct a vector representation of to-be-pinned apps from the pref.
+ std::vector<std::string> pinned_apps;
+ const base::ListValue* pinned_apps_pref =
+ profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
+ for (base::ListValue::const_iterator it(pinned_apps_pref->begin());
+ it != pinned_apps_pref->end(); ++it) {
+ DictionaryValue* app = NULL;
+ std::string app_id;
+ if ((*it)->GetAsDictionary(&app) &&
+ app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
+ std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
+ pinned_apps.end() &&
+ app_tab_helper_->IsValidID(app_id)) {
+ pinned_apps.push_back(app_id);
+ }
+ }
+
+ // Walk the model and |pinned_apps| from the pref lockstep, adding and
+ // removing items as necessary. NB: This code uses plain old indexing instead
+ // of iterators because of model mutations as part of the loop.
+ std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
+ int index = 0;
+ for (; index < model_->item_count() && pref_app_id != pinned_apps.end();
+ ++index) {
+ // If the next app launcher according to the pref is present in the model,
+ // delete all app launcher entries in between.
+ if (IsAppPinned(*pref_app_id)) {
+ for (; index < model_->item_count(); ++index) {
+ const ash::LauncherItem& item(model_->items()[index]);
+ if (item.type != ash::TYPE_APP_SHORTCUT)
+ continue;
+
+ IDToItemControllerMap::const_iterator entry =
+ id_to_item_controller_map_.find(item.id);
+ if (entry != id_to_item_controller_map_.end() &&
+ entry->second->app_id() == *pref_app_id) {
+ ++pref_app_id;
+ break;
+ } else {
+ LauncherItemClosed(item.id);
+ --index;
+ }
+ }
+ // If the item wasn't found, that means id_to_item_controller_map_
+ // is out of sync.
+ DCHECK(index < model_->item_count());
+ } else {
+ // This app wasn't pinned before, insert a new entry.
+ ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index);
+ index = model_->ItemIndexByID(id);
+ ++pref_app_id;
+ }
+ }
+
+ // Remove any trailing existing items.
+ while (index < model_->item_count()) {
+ const ash::LauncherItem& item(model_->items()[index]);
+ if (item.type == ash::TYPE_APP_SHORTCUT)
+ LauncherItemClosed(item.id);
+ else
+ ++index;
+ }
+
+ // Append unprocessed items from the pref to the end of the model.
+ for (; pref_app_id != pinned_apps.end(); ++pref_app_id)
+ DoPinAppWithID(*pref_app_id);
+}
+
+void ChromeLauncherControllerPerBrowser::SetShelfAutoHideBehaviorFromPrefs() {
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string behavior_value(
+ GetLocalOrRemotePref(profile_->GetPrefs(),
+ prefs::kShelfAutoHideBehaviorLocal,
+ prefs::kShelfAutoHideBehavior));
+
+ // Note: To maintain sync compatibility with old images of chrome/chromeos
+ // the set of values that may be encountered includes the now-extinct
+ // "Default" as well as "Never" and "Always", "Default" should now
+ // be treated as "Never".
+ // (http://code.google.com/p/chromium/issues/detail?id=146773)
+ ash::ShelfAutoHideBehavior behavior =
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
+ if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
+ behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+ // TODO(oshima): Support multiple displays.
+ ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
+ behavior, ash::Shell::GetPrimaryRootWindow());
+}
+
+void ChromeLauncherControllerPerBrowser::SetShelfAlignmentFromPrefs() {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kShowLauncherAlignmentMenu))
+ return;
+
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string alignment_value(
+ GetLocalOrRemotePref(profile_->GetPrefs(),
+ prefs::kShelfAlignmentLocal,
+ prefs::kShelfAlignment));
+ ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
+ if (alignment_value == ash::kShelfAlignmentLeft)
+ alignment = ash::SHELF_ALIGNMENT_LEFT;
+ else if (alignment_value == ash::kShelfAlignmentRight)
+ alignment = ash::SHELF_ALIGNMENT_RIGHT;
+ // TODO(oshima): Support multiple displays.
+ ash::Shell::GetInstance()->SetShelfAlignment(
+ alignment, ash::Shell::GetPrimaryRootWindow());
+}
+
+TabContents* ChromeLauncherControllerPerBrowser::GetLastActiveTabContents(
+ const std::string& app_id) {
+ AppIDToTabContentsListMap::const_iterator i =
+ app_id_to_tab_contents_list_.find(app_id);
+ if (i == app_id_to_tab_contents_list_.end())
+ return NULL;
+ DCHECK_GT(i->second.size(), 0u);
+ return *i->second.begin();
+}
+
+ash::LauncherID ChromeLauncherControllerPerBrowser::InsertAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status,
+ int index) {
+ ash::LauncherID id = model_->next_id();
+ DCHECK(!HasItemController(id));
+ DCHECK(controller);
+ id_to_item_controller_map_[id] = controller;
+ controller->set_launcher_id(id);
+
+ ash::LauncherItem item;
+ item.type = controller->GetLauncherItemType();
+ item.is_incognito = false;
+ item.image = Extension::GetDefaultIcon(true);
+
+ TabContents* active_tab = GetLastActiveTabContents(app_id);
+ if (active_tab) {
+ Browser* browser = browser::FindBrowserWithWebContents(
+ active_tab->web_contents());
+ DCHECK(browser);
+ if (browser->window()->IsActive())
+ status = ash::STATUS_ACTIVE;
+ else
+ status = ash::STATUS_RUNNING;
+ }
+ item.status = status;
+
+ model_->AddAt(index, item);
+
+ if (controller->type() != LauncherItemController::TYPE_EXTENSION_PANEL)
+ app_icon_loader_->FetchImage(app_id);
+
+ return id;
+}
+
+bool ChromeLauncherControllerPerBrowser::HasItemController(
+ ash::LauncherID id) const {
+ return id_to_item_controller_map_.find(id) !=
+ id_to_item_controller_map_.end();
+}
+
+ash::LauncherID
+ChromeLauncherControllerPerBrowser::CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) {
+ AppShortcutLauncherItemController* controller =
+ new AppShortcutLauncherItemController(app_id, this);
+ ash::LauncherID launcher_id = InsertAppLauncherItem(
+ controller, app_id, ash::STATUS_CLOSED, index);
+ return launcher_id;
+}
+
+void ChromeLauncherControllerPerBrowser::SetAppTabHelperForTest(
+ AppTabHelper* helper) {
+ app_tab_helper_.reset(helper);
+}
+
+void ChromeLauncherControllerPerBrowser::SetAppIconLoaderForTest(
+ AppIconLoader* loader) {
+ app_icon_loader_.reset(loader);
+}
+
+const std::string&
+ChromeLauncherControllerPerBrowser::GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) {
+ return id_to_item_controller_map_[id]->app_id();
+}
+
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h
new file mode 100644
index 0000000..49e1883
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h
@@ -0,0 +1,343 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_
+#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model_observer.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/shelf_types.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/public/pref_change_registrar.h"
+#include "base/prefs/public/pref_observer.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/prefs/pref_service_observer.h"
+#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/aura/window_observer.h"
+
+class AppSyncUIState;
+class Browser;
+class BrowserLauncherItemControllerTest;
+class LauncherItemController;
+class Profile;
+class ShellWindowLauncherController;
+class TabContents;
+
+namespace ash {
+class LauncherModel;
+}
+
+namespace aura {
+class Window;
+}
+
+namespace content {
+class WebContents;
+}
+
+// ChromeLauncherControllerPerBrowser manages the launcher items needed for
+// content windows. Launcher items have a type, an optional app id, and a
+// controller. This incarnation manages the items on a per browser base using
+// browser proxies and application icons.
+// * Tabbed browsers and browser app windows have BrowserLauncherItemController,
+// owned by the BrowserView instance.
+// * App shell windows have ShellWindowLauncherItemController, owned by
+// ShellWindowLauncherController.
+// * Shortcuts have no LauncherItemController.
+class ChromeLauncherControllerPerBrowser
+ : public ash::LauncherModelObserver,
+ public ash::ShellObserver,
+ public ChromeLauncherController,
+ public content::NotificationObserver,
+ public PrefObserver,
+ public PrefServiceObserver,
+ public AppSyncUIStateObserver {
+ public:
+ ChromeLauncherControllerPerBrowser(Profile* profile,
+ ash::LauncherModel* model);
+ virtual ~ChromeLauncherControllerPerBrowser();
+
+ // ChromeLauncherController overrides:
+
+ // Initializes this ChromeLauncherControllerPerBrowser.
+ virtual void Init() OVERRIDE;
+
+ // Creates a new tabbed item on the launcher for |controller|.
+ virtual ash::LauncherID CreateTabbedLauncherItem(
+ LauncherItemController* controller,
+ IncognitoState is_incognito,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Creates a new app item on the launcher for |controller|.
+ virtual ash::LauncherID CreateAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Updates the running status of an item.
+ virtual void SetItemStatus(ash::LauncherID id,
+ ash::LauncherItemStatus status) OVERRIDE;
+
+ // Updates the controller associated with id (which should be a shortcut).
+ // |controller| remains owned by caller.
+ virtual void SetItemController(ash::LauncherID id,
+ LauncherItemController* controller) OVERRIDE;
+
+ // Closes or unpins the launcher item.
+ virtual void CloseLauncherItem(ash::LauncherID id) OVERRIDE;
+
+ // Pins the specified id. Currently only supports platform apps.
+ virtual void Pin(ash::LauncherID id) OVERRIDE;
+
+ // Unpins the specified id, closing if not running.
+ virtual void Unpin(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the item identified by |id| is pinned.
+ virtual bool IsPinned(ash::LauncherID id) OVERRIDE;
+
+ // Pins/unpins the specified id.
+ virtual void TogglePinned(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item can be pinned or unpinned. Only apps can
+ // be pinned.
+ virtual bool IsPinnable(ash::LauncherID id) const OVERRIDE;
+
+ // Requests that the launcher item controller specified by |id| open a new
+ // instance of the app. |event_flags| holds the flags of the event which
+ // triggered this command.
+ virtual void Launch(ash::LauncherID id, int event_flags) OVERRIDE;
+
+ // Closes the specified item.
+ virtual void Close(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item is open.
+ virtual bool IsOpen(ash::LauncherID id) OVERRIDE;
+
+ // Returns true if the specified item is for a platform app.
+ virtual bool IsPlatformApp(ash::LauncherID id) OVERRIDE;
+
+ // Opens a new instance of the application identified by |app_id|.
+ // Used by the app-list, and by pinned-app launcher items.
+ virtual void LaunchApp(const std::string& app_id, int event_flags) OVERRIDE;
+
+ // If |app_id| is running, reactivates the app's most recently active window,
+ // otherwise launches and activates the app.
+ // Used by the app-list, and by pinned-app launcher items.
+ virtual void ActivateApp(const std::string& app_id, int event_flags) OVERRIDE;
+
+ // Returns the launch type of app for the specified id.
+ virtual extensions::ExtensionPrefs::LaunchType GetLaunchType(
+ ash::LauncherID id) OVERRIDE;
+
+ // Returns the id of the app for the specified tab.
+ virtual std::string GetAppID(content::WebContents* tab) OVERRIDE;
+
+ virtual ash::LauncherID GetLauncherIDForAppID(
+ const std::string& app_id) OVERRIDE;
+ virtual std::string GetAppIDForLauncherID(ash::LauncherID id) OVERRIDE;
+
+ // Sets the image for an app tab. This is intended to be invoked from the
+ // AppIconLoader.
+ virtual void SetAppImage(const std::string& app_id,
+ const gfx::ImageSkia& image) OVERRIDE;
+
+ // Returns true if a pinned launcher item with given |app_id| could be found.
+ virtual bool IsAppPinned(const std::string& app_id) OVERRIDE;
+
+ // Pins an app with |app_id| to launcher. If there is a running instance in
+ // launcher, the running instance is pinned. If there is no running instance,
+ // a new launcher item is created and pinned.
+ virtual void PinAppWithID(const std::string& app_id) OVERRIDE;
+
+ // Updates the launche type of the app for the specified id to |launch_type|.
+ virtual void SetLaunchType(
+ ash::LauncherID id,
+ extensions::ExtensionPrefs::LaunchType launch_type) OVERRIDE;
+
+ // Unpins any app items whose id is |app_id|.
+ virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE;
+
+ // Returns true if the user is currently logged in as a guest.
+ virtual bool IsLoggedInAsGuest() OVERRIDE;
+
+ // Invoked when user clicks on button in the launcher and there is no last
+ // used window (or CTRL is held with the click).
+ virtual void CreateNewWindow() OVERRIDE;
+
+ // Invoked when the user clicks on button in the launcher to create a new
+ // incognito window.
+ virtual void CreateNewIncognitoWindow() OVERRIDE;
+
+ // Checks whether the user is allowed to pin apps. Pinning may be disallowed
+ // by policy in case there is a pre-defined set of pinned apps.
+ virtual bool CanPin() const OVERRIDE;
+
+ // Updates the pinned pref state. The pinned state consists of a list pref.
+ // Each item of the list is a dictionary. The key |kAppIDPath| gives the
+ // id of the app.
+ virtual void PersistPinnedState() OVERRIDE;
+
+ virtual ash::LauncherModel* model() OVERRIDE;
+
+ virtual Profile* profile() OVERRIDE;
+
+ virtual void SetAutoHideBehavior(ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) OVERRIDE;
+
+ // The tab no longer represents its previously identified application.
+ virtual void RemoveTabFromRunningApp(TabContents* tab,
+ const std::string& app_id) OVERRIDE;
+
+ // Notify the controller that the state of an non platform app's tabs
+ // have changed,
+ virtual void UpdateAppState(content::WebContents* contents,
+ AppState app_state) OVERRIDE;
+
+ // Limits application refocusing to urls that match |url| for |id|.
+ virtual void SetRefocusURLPattern(ash::LauncherID id,
+ const GURL& url) OVERRIDE;
+
+ // Returns the extension identified by |app_id|.
+ virtual const extensions::Extension* GetExtensionForAppID(
+ const std::string& app_id) OVERRIDE;
+
+ // ash::LauncherDelegate overrides:
+ virtual void OnBrowserShortcutClicked(int event_flags) OVERRIDE;
+ virtual void ItemClicked(const ash::LauncherItem& item,
+ int event_flags) OVERRIDE;
+ virtual int GetBrowserShortcutResourceId() OVERRIDE;
+ virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE;
+ virtual ui::MenuModel* CreateContextMenu(
+ const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE;
+ virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE;
+ virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE;
+
+ // ash::LauncherModelObserver overrides:
+ virtual void LauncherItemAdded(int index) OVERRIDE;
+ virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE;
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE;
+ virtual void LauncherItemChanged(int index,
+ const ash::LauncherItem& old_item) OVERRIDE;
+ virtual void LauncherStatusChanged() OVERRIDE;
+
+ // Overridden from content::NotificationObserver:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Overridden from PrefObserver:
+ virtual void OnPreferenceChanged(PrefServiceBase* service,
+ const std::string& pref_name) OVERRIDE;
+
+ // Overridden from ash::ShellObserver:
+ virtual void OnShelfAlignmentChanged() OVERRIDE;
+
+ // Overridden from PrefServiceObserver:
+ virtual void OnIsSyncingChanged() OVERRIDE;
+
+ // Overridden from AppSyncUIStateObserver
+ virtual void OnAppSyncUIStatusChanged() OVERRIDE;
+
+ protected:
+ // ChromeLauncherController overrides:
+
+ // Creates a new app shortcut item and controller on the launcher at |index|.
+ // Use kInsertItemAtEnd to add a shortcut as the last item.
+ virtual ash::LauncherID CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) OVERRIDE;
+
+ // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class.
+ // These are intended for testing.
+ virtual void SetAppTabHelperForTest(AppTabHelper* helper) OVERRIDE;
+ virtual void SetAppIconLoaderForTest(AppIconLoader* loader) OVERRIDE;
+ virtual const std::string& GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) OVERRIDE;
+
+ private:
+ friend class ChromeLauncherControllerPerBrowserTest;
+
+ typedef std::map<ash::LauncherID, LauncherItemController*>
+ IDToItemControllerMap;
+ typedef std::list<TabContents*> TabContentsList;
+ typedef std::map<std::string, TabContentsList> AppIDToTabContentsListMap;
+ typedef std::map<TabContents*, std::string> TabContentsToAppIDMap;
+
+ // Returns the profile used for new windows.
+ Profile* GetProfileForNewWindows();
+
+ // Invoked when the associated browser or app is closed.
+ void LauncherItemClosed(ash::LauncherID id);
+
+ // Internal helpers for pinning and unpinning that handle both
+ // client-triggered and internal pinning operations.
+ void DoPinAppWithID(const std::string& app_id);
+ void DoUnpinAppsWithID(const std::string& app_id);
+
+ // Re-syncs launcher model with prefs::kPinnedLauncherApps.
+ void UpdateAppLaunchersFromPref();
+
+ // Sets the shelf auto-hide behavior from prefs.
+ void SetShelfAutoHideBehaviorFromPrefs();
+
+ // Sets the shelf alignment from prefs.
+ void SetShelfAlignmentFromPrefs();
+
+ // Returns the most recently active tab contents for an app.
+ TabContents* GetLastActiveTabContents(const std::string& app_id);
+
+ // Creates an app launcher to insert at |index|. Note that |index| may be
+ // adjusted by the model to meet ordering constraints.
+ ash::LauncherID InsertAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status,
+ int index);
+
+ bool HasItemController(ash::LauncherID id) const;
+
+ ash::LauncherModel* model_;
+
+ // Profile used for prefs and loading extensions. This is NOT necessarily the
+ // profile new windows are created with.
+ Profile* profile_;
+
+ IDToItemControllerMap id_to_item_controller_map_;
+
+ // Maintains activation order of tab contents for each app.
+ AppIDToTabContentsListMap app_id_to_tab_contents_list_;
+
+ // Direct access to app_id for a tab contents.
+ TabContentsToAppIDMap tab_contents_to_app_id_;
+
+ // Used to track shell windows.
+ scoped_ptr<ShellWindowLauncherController> shell_window_controller_;
+
+ // Used to get app info for tabs.
+ scoped_ptr<AppTabHelper> app_tab_helper_;
+
+ // Used to load the image for an app item.
+ scoped_ptr<AppIconLoader> app_icon_loader_;
+
+ content::NotificationRegistrar notification_registrar_;
+
+ PrefChangeRegistrar pref_change_registrar_;
+
+ AppSyncUIState* app_sync_ui_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerBrowser);
+};
+
+#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc
new file mode 100644
index 0000000..e737b00
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc
@@ -0,0 +1,280 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "ash/launcher/launcher_model.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
+#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_pref_service.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace {
+const int kExpectedAppIndex = 1;
+}
+
+class ChromeLauncherControllerPerBrowserTest : public testing::Test {
+ protected:
+ ChromeLauncherControllerPerBrowserTest()
+ : ui_thread_(content::BrowserThread::UI, &loop_),
+ file_thread_(content::BrowserThread::FILE, &loop_),
+ profile_(new TestingProfile()),
+ extension_service_(NULL) {
+ DictionaryValue manifest;
+ manifest.SetString("name", "launcher controller test extension");
+ manifest.SetString("version", "1");
+ manifest.SetString("description", "for testing pinned apps");
+
+ extensions::TestExtensionSystem* extension_system(
+ static_cast<extensions::TestExtensionSystem*>(
+ extensions::ExtensionSystem::Get(profile_.get())));
+ extension_service_ = extension_system->CreateExtensionService(
+ CommandLine::ForCurrentProcess(), FilePath(), false);
+
+ std::string error;
+ extension1_ = Extension::Create(FilePath(), Extension::LOAD, manifest,
+ Extension::NO_FLAGS,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ &error);
+ extension2_ = Extension::Create(FilePath(), Extension::LOAD, manifest,
+ Extension::NO_FLAGS,
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ &error);
+ // Fake gmail extension.
+ extension3_ = Extension::Create(FilePath(), Extension::LOAD, manifest,
+ Extension::NO_FLAGS,
+ "pjkljhegncpnkpknbcohdijeoejaedia",
+ &error);
+ // Fake search extension.
+ extension4_ = Extension::Create(FilePath(), Extension::LOAD, manifest,
+ Extension::NO_FLAGS,
+ "coobgpohoikkiipiblmjeljniedjpjpf",
+ &error);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ profile_.reset();
+ // Execute any pending deletion tasks.
+ loop_.RunUntilIdle();
+ }
+
+ void InsertPrefValue(base::ListValue* pref_value,
+ int index,
+ const std::string& extension_id) {
+ base::DictionaryValue* entry = new DictionaryValue();
+ entry->SetString(ash::kPinnedAppsPrefAppIDPath, extension_id);
+ pref_value->Insert(index, entry);
+ }
+
+ // Gets the currently configured app launchers from the controller.
+ void GetAppLaunchers(ChromeLauncherControllerPerBrowser* controller,
+ std::vector<std::string>* launchers) {
+ launchers->clear();
+ for (ash::LauncherItems::const_iterator iter(model_.items().begin());
+ iter != model_.items().end(); ++iter) {
+ ChromeLauncherControllerPerBrowser::IDToItemControllerMap::const_iterator
+ entry(controller->id_to_item_controller_map_.find(iter->id));
+ if (iter->type == ash::TYPE_APP_SHORTCUT &&
+ entry != controller->id_to_item_controller_map_.end()) {
+ launchers->push_back(entry->second->app_id());
+ }
+ }
+ }
+
+ // Needed for extension service & friends to work.
+ MessageLoop loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+
+ scoped_refptr<Extension> extension1_;
+ scoped_refptr<Extension> extension2_;
+ scoped_refptr<Extension> extension3_;
+ scoped_refptr<Extension> extension4_;
+ scoped_ptr<TestingProfile> profile_;
+ ash::LauncherModel model_;
+
+ ExtensionService* extension_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerBrowserTest);
+};
+
+TEST_F(ChromeLauncherControllerPerBrowserTest, DefaultApps) {
+ ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(),
+ &model_);
+ launcher_controller.Init();
+
+ // Model should only contain the browser shortcut and app list items.
+ EXPECT_EQ(2, model_.item_count());
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
+
+ // Installing |extension3_| should add it to the launcher.
+ extension_service_->AddExtension(extension3_.get());
+ EXPECT_EQ(3, model_.item_count());
+ EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type);
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id()));
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id()));
+}
+
+TEST_F(ChromeLauncherControllerPerBrowserTest, Policy) {
+ extension_service_->AddExtension(extension1_.get());
+ extension_service_->AddExtension(extension3_.get());
+
+ base::ListValue policy_value;
+ InsertPrefValue(&policy_value, 0, extension1_->id());
+ InsertPrefValue(&policy_value, 1, extension2_->id());
+ profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps,
+ policy_value.DeepCopy());
+
+ // Only |extension1_| should get pinned. |extension2_| is specified but not
+ // installed, and |extension3_| is part of the default set, but that shouldn't
+ // take effect when the policy override is in place.
+ ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(),
+ &model_);
+ launcher_controller.Init();
+ EXPECT_EQ(3, model_.item_count());
+ EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type);
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
+
+ // Installing |extension2_| should add it to the launcher.
+ extension_service_->AddExtension(extension2_.get());
+ EXPECT_EQ(4, model_.item_count());
+ EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type);
+ EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[2].type);
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id()));
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
+
+ // Removing |extension1_| from the policy should be reflected in the launcher.
+ policy_value.Remove(0, NULL);
+ profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps,
+ policy_value.DeepCopy());
+ EXPECT_EQ(3, model_.item_count());
+ EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type);
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id()));
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id()));
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
+}
+
+TEST_F(ChromeLauncherControllerPerBrowserTest, UnpinWithUninstall) {
+ extension_service_->AddExtension(extension3_.get());
+ extension_service_->AddExtension(extension4_.get());
+
+ ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(),
+ &model_);
+ launcher_controller.Init();
+
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id()));
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id()));
+
+ extension_service_->UnloadExtension(extension3_->id(),
+ extension_misc::UNLOAD_REASON_UNINSTALL);
+
+ EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id()));
+ EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id()));
+}
+
+TEST_F(ChromeLauncherControllerPerBrowserTest, PrefUpdates) {
+ extension_service_->AddExtension(extension2_.get());
+ extension_service_->AddExtension(extension3_.get());
+ extension_service_->AddExtension(extension4_.get());
+ ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_);
+
+ std::vector<std::string> expected_launchers;
+ std::vector<std::string> actual_launchers;
+ base::ListValue pref_value;
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+
+ // Unavailable extensions don't create launcher items.
+ InsertPrefValue(&pref_value, 0, extension1_->id());
+ InsertPrefValue(&pref_value, 1, extension2_->id());
+ InsertPrefValue(&pref_value, 2, extension4_->id());
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+ expected_launchers.push_back(extension2_->id());
+ expected_launchers.push_back(extension4_->id());
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+
+ // Redundant pref entries show up only once.
+ InsertPrefValue(&pref_value, 2, extension3_->id());
+ InsertPrefValue(&pref_value, 2, extension3_->id());
+ InsertPrefValue(&pref_value, 5, extension3_->id());
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+ expected_launchers.insert(expected_launchers.begin() + 1, extension3_->id());
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+
+ // Order changes are reflected correctly.
+ pref_value.Clear();
+ InsertPrefValue(&pref_value, 0, extension4_->id());
+ InsertPrefValue(&pref_value, 1, extension3_->id());
+ InsertPrefValue(&pref_value, 2, extension2_->id());
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+ std::reverse(expected_launchers.begin(), expected_launchers.end());
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+
+ // Clearing works.
+ pref_value.Clear();
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+ expected_launchers.clear();
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+}
+
+TEST_F(ChromeLauncherControllerPerBrowserTest, PendingInsertionOrder) {
+ extension_service_->AddExtension(extension1_.get());
+ extension_service_->AddExtension(extension3_.get());
+ ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_);
+
+ base::ListValue pref_value;
+ InsertPrefValue(&pref_value, 0, extension1_->id());
+ InsertPrefValue(&pref_value, 1, extension2_->id());
+ InsertPrefValue(&pref_value, 2, extension3_->id());
+ profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps,
+ pref_value.DeepCopy());
+
+ std::vector<std::string> expected_launchers;
+ expected_launchers.push_back(extension1_->id());
+ expected_launchers.push_back(extension3_->id());
+ std::vector<std::string> actual_launchers;
+
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+
+ // Install |extension2| and verify it shows up between the other two.
+ extension_service_->AddExtension(extension2_.get());
+ expected_launchers.insert(expected_launchers.begin() + 1, extension2_->id());
+ GetAppLaunchers(&controller, &actual_launchers);
+ EXPECT_EQ(expected_launchers, actual_launchers);
+}
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index c24e840..2922371 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -147,6 +147,10 @@
'browser/ui/ash/launcher/browser_launcher_item_controller.h',
'browser/ui/ash/launcher/chrome_launcher_controller.cc',
'browser/ui/ash/launcher/chrome_launcher_controller.h',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_app.h',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h',
'browser/ui/ash/launcher/launcher_app_icon_loader.cc',
'browser/ui/ash/launcher/launcher_app_icon_loader.h',
'browser/ui/ash/launcher/launcher_app_tab_helper.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index b92326a..5af2cb8 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1149,7 +1149,8 @@
'browser/ui/ash/event_rewriter_unittest.cc',
'browser/ui/ash/ime_controller_chromeos_unittest.cc',
'browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc',
- 'browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc',
+ 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc',
'browser/ui/ash/window_positioner_unittest.cc',
'browser/ui/auto_login_prompter_unittest.cc',
'browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc',