diff options
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', |