Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2610)

Unified Diff: chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc

Issue 22887015: Remove PerBrowser launcher (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix nit Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 442f2e83c1abc8272e9877722f593d6fd6a78de7..973ded6f0b4a767ca7225c6b96b79833b965b9c7 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -4,14 +4,286 @@
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include <vector>
+
#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_util.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
#include "base/command_line.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/app_mode/app_mode_utils.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/extensions/app_icon_loader_impl.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/favicon/favicon_tab_helper.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/prefs/pref_service_syncable.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/launcher/app_shortcut_launcher_item_controller.h"
+#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
+#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
+#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.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/ash/launcher/shell_window_launcher_item_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_list.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/extensions/extension_enable_flow.h"
+#include "chrome/browser/ui/host_desktop.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/manifest_handlers/icons_handler.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_registrar.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/url_pattern.h"
+#include "grit/ash_resources.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "grit/ui_resources.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/corewm/window_animations.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h"
+#endif
+
+using extensions::Extension;
+using extension_misc::kGmailAppId;
+using content::WebContents;
-// statics
+// static
ChromeLauncherController* ChromeLauncherController::instance_ = NULL;
+namespace {
+
+std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) {
+ gfx::Display display = gfx::Screen::GetScreenFor(
+ root_window)->GetDisplayNearestWindow(root_window);
+ DCHECK(display.is_valid());
+
+ return base::Int64ToString(display.id());
+}
+
+void UpdatePerDisplayPref(PrefService* pref_service,
+ aura::RootWindow* root_window,
+ const char* pref_key,
+ const std::string& value) {
+ std::string key = GetPrefKeyForRootWindow(root_window);
+ if (key.empty())
+ return;
+
+ DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences);
+ base::DictionaryValue* shelf_prefs = update.Get();
+ base::DictionaryValue* prefs = NULL;
+ if (!shelf_prefs->GetDictionary(key, &prefs)) {
+ prefs = new base::DictionaryValue();
+ shelf_prefs->Set(key, prefs);
+ }
+ prefs->SetStringWithoutPathExpansion(pref_key, value);
+}
+
+// Returns a pref value in |pref_service| for the display of |root_window|. The
+// pref value is stored in |local_path| and |path|, but |pref_service| may have
+// per-display preferences and the value can be specified by policy. Here is
+// the priority:
+// * A value managed by policy. This is a single value that applies to all
+// displays.
+// * A user-set value for the specified display.
+// * A user-set value in |local_path| or |path|, if no per-display settings are
+// ever specified (see http://crbug.com/173719 for why). |local_path| is
+// preferred. See comment in |kShelfAlignment| as to why we consider two
+// prefs and why |local_path| is preferred.
+// * A value recommended by policy. This is a single value that applies to all
+// root windows.
+// * The default value for |local_path| if the value is not recommended by
+// policy.
+std::string GetPrefForRootWindow(PrefService* pref_service,
+ aura::RootWindow* root_window,
+ const char* local_path,
+ const char* path) {
+ const PrefService::Preference* local_pref =
+ pref_service->FindPreference(local_path);
+ const std::string value(pref_service->GetString(local_path));
+ if (local_pref->IsManaged())
+ return value;
+
+ std::string pref_key = GetPrefKeyForRootWindow(root_window);
+ bool has_per_display_prefs = false;
+ if (!pref_key.empty()) {
+ const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary(
+ prefs::kShelfPreferences);
+ const base::DictionaryValue* display_pref = NULL;
+ std::string per_display_value;
+ if (shelf_prefs->GetDictionary(pref_key, &display_pref) &&
+ display_pref->GetString(path, &per_display_value))
+ return per_display_value;
+
+ // If the pref for the specified display is not found, scan the whole prefs
+ // and check if the prefs for other display is already specified.
+ std::string unused_value;
+ for (base::DictionaryValue::Iterator iter(*shelf_prefs);
+ !iter.IsAtEnd(); iter.Advance()) {
+ const base::DictionaryValue* display_pref = NULL;
+ if (iter.value().GetAsDictionary(&display_pref) &&
+ display_pref->GetString(path, &unused_value)) {
+ has_per_display_prefs = true;
+ break;
+ }
+ }
+ }
+
+ if (local_pref->IsRecommended() || !has_per_display_prefs)
+ return value;
+
+ const base::Value* default_value =
+ pref_service->GetDefaultPrefValue(local_path);
+ std::string default_string;
+ default_value->GetAsString(&default_string);
+ return default_string;
+}
+
+// If prefs have synced and no user-set value exists at |local_path|, the value
+// from |synced_path| is copied to |local_path|.
+void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service,
+ const char* local_path,
+ const char* synced_path) {
+ if (!pref_service->FindPreference(local_path)->HasUserSetting() &&
+ 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::ChromeLauncherController(
+ Profile* profile,
+ ash::LauncherModel* model)
+ : model_(model),
+ profile_(profile),
+ app_sync_ui_state_(NULL),
+ ignore_persist_pinned_state_change_(false) {
+ 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);
+ BrowserList::AddObserver(this);
+ // Right now ash::Shell isn't created for tests.
+ // TODO(mukai): Allows it to observe display change and write tests.
+ if (ash::Shell::HasInstance())
+ ash::Shell::GetInstance()->display_controller()->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 extensions::AppIconLoaderImpl(
+ profile_, extension_misc::EXTENSION_ICON_SMALL, 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,
+ base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ prefs::kShelfAlignmentLocal,
+ base::Bind(&ChromeLauncherController::SetShelfAlignmentFromPrefs,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ prefs::kShelfAutoHideBehaviorLocal,
+ base::Bind(&ChromeLauncherController::
+ SetShelfAutoHideBehaviorFromPrefs,
+ base::Unretained(this)));
+ pref_change_registrar_.Add(
+ prefs::kShelfPreferences,
+ base::Bind(&ChromeLauncherController::SetShelfBehaviorsFromPrefs,
+ base::Unretained(this)));
+}
+
+ChromeLauncherController::~ChromeLauncherController() {
+ // Reset the shell window controller here since it has a weak pointer to this.
+ shell_window_controller_.reset();
+
+ for (std::set<ash::Launcher*>::iterator iter = launchers_.begin();
+ iter != launchers_.end();
+ ++iter)
+ (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this);
+
+ model_->RemoveObserver(this);
+ BrowserList::RemoveObserver(this);
+ if (ash::Shell::HasInstance())
+ ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ i->second->OnRemoved();
+ // TODO(skuhne): After getting rid of the old launcher, get also rid of the
+ // BrowserLauncherItemController (since it is only used for activation
+ // tracking at that point.
+ int index = model_->ItemIndexByID(i->first);
+ // A "browser proxy" is not known to the model and this removal does
+ // therefore not need to be propagated to the model.
+ if (index != -1 &&
+ model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT)
+ model_->RemoveItemAt(index);
+ }
+
+ if (ash::Shell::HasInstance())
+ ash::Shell::GetInstance()->RemoveShellObserver(this);
+
+ if (app_sync_ui_state_)
+ app_sync_ui_state_->RemoveObserver(this);
+
+ PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
+
+ if (instance_ == this)
+ instance_ = NULL;
+}
+
// static
ChromeLauncherController* ChromeLauncherController::CreateInstance(
Profile* profile,
@@ -19,21 +291,1368 @@ ChromeLauncherController* ChromeLauncherController::CreateInstance(
// 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::kAshDisablePerAppLauncher))
- instance_ = new ChromeLauncherControllerPerApp(profile, model);
- else
- instance_ = new ChromeLauncherControllerPerBrowser(profile, model);
+ instance_ = new ChromeLauncherController(profile, model);
return instance_;
}
-ChromeLauncherController::~ChromeLauncherController() {
- if (instance_ == this)
- instance_ = NULL;
+void ChromeLauncherController::Init() {
+ UpdateAppLaunchersFromPref();
+ CreateBrowserShortcutLauncherItem();
+
+ // TODO(sky): update unit test so that this test isn't necessary.
+ if (ash::Shell::HasInstance()) {
+ SetShelfAutoHideBehaviorFromPrefs();
+ SetShelfAlignmentFromPrefs();
+ PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
+ if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() ||
+ !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)->
+ HasUserSetting()) {
+ // This causes OnIsSyncingChanged to be called when the value of
+ // PrefService::IsSyncing() changes.
+ prefs->AddObserver(this);
+ }
+ ash::Shell::GetInstance()->AddShellObserver(this);
+ }
+}
+
+ash::LauncherID ChromeLauncherController::CreateAppLauncherItem(
+ LauncherItemController* controller,
+ const std::string& app_id,
+ ash::LauncherItemStatus status) {
+ CHECK(controller);
+ int index = 0;
+ // Panels are inserted on the left so as not to push all existing panels over.
+ if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) {
+ index = model_->item_count();
+ // For the alternate shelf layout increment the index (after the app icon)
+ if (ash::switches::UseAlternateShelfLayout())
+ ++index;
+ }
+ return InsertAppLauncherItem(controller,
+ app_id,
+ status,
+ index,
+ controller->GetLauncherItemType());
+}
+
+void ChromeLauncherController::SetItemStatus(
+ ash::LauncherID id,
+ ash::LauncherItemStatus status) {
+ int index = model_->ItemIndexByID(id);
+ // Since ordinary browser windows are not registered, we might get a negative
+ // index here.
+ if (index >= 0) {
+ ash::LauncherItem item = model_->items()[index];
+ item.status = status;
+ model_->Set(index, item);
+
+ if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT)
+ return;
+ }
+ UpdateBrowserItemStatus();
+}
+
+void ChromeLauncherController::SetItemController(
+ ash::LauncherID id,
+ LauncherItemController* controller) {
+ CHECK(controller);
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ CHECK(iter != id_to_item_controller_map_.end());
+ iter->second->OnRemoved();
+ iter->second = controller;
+ controller->set_launcher_id(id);
+}
+
+void ChromeLauncherController::CloseLauncherItem(ash::LauncherID id) {
+ CHECK(id);
+ if (IsPinned(id)) {
+ // Create a new shortcut controller.
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ CHECK(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::Pin(ash::LauncherID id) {
+ DCHECK(HasItemController(id));
+
+ int index = model_->ItemIndexByID(id);
+ DCHECK_GE(index, 0);
+
+ ash::LauncherItem item = model_->items()[index];
+
+ if (item.type == ash::TYPE_PLATFORM_APP ||
+ item.type == ash::TYPE_WINDOWED_APP) {
+ item.type = ash::TYPE_APP_SHORTCUT;
+ model_->Set(index, item);
+ } else if (item.type != ash::TYPE_APP_SHORTCUT) {
+ return;
+ }
+
+ if (CanPin())
+ PersistPinnedState();
+}
+
+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);
+ DCHECK_GE(index, 0);
+ ash::LauncherItem item = model_->items()[index];
+ item.type = ash::TYPE_PLATFORM_APP;
+ model_->Set(index, item);
+ } else {
+ // Prevent the removal of items upon unpin if it is locked by a running
+ // windowed V1 app.
+ if (!controller->locked()) {
+ LauncherItemClosed(id);
+ } else {
+ int index = model_->ItemIndexByID(id);
+ DCHECK_GE(index, 0);
+ ash::LauncherItem item = model_->items()[index];
+ item.type = ash::TYPE_WINDOWED_APP;
+ model_->Set(index, item);
+ }
+ }
+ if (CanPin())
+ PersistPinnedState();
+}
+
+bool ChromeLauncherController::IsPinned(ash::LauncherID id) {
+ int index = model_->ItemIndexByID(id);
+ if (index < 0)
+ return false;
+ ash::LauncherItemType type = model_->items()[index].type;
+ return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_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 ||
+ type == ash::TYPE_WINDOWED_APP) &&
+ CanPin());
+}
+
+void ChromeLauncherController::LockV1AppWithID(
+ const std::string& app_id) {
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) {
+ CreateAppShortcutLauncherItemWithType(app_id,
+ model_->item_count(),
+ ash::TYPE_WINDOWED_APP);
+ id = GetLauncherIDForAppID(app_id);
+ }
+ CHECK(id);
+ id_to_item_controller_map_[id]->lock();
+}
+
+void ChromeLauncherController::UnlockV1AppWithID(
+ const std::string& app_id) {
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id));
+ CHECK(id);
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ controller->unlock();
+ if (!controller->locked() && !IsPinned(id))
+ CloseLauncherItem(id);
+}
+
+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::IsPerAppLauncher() {
- if (!instance_)
+bool ChromeLauncherController::IsOpen(ash::LauncherID id) {
+ if (!HasItemController(id))
return false;
- return instance_->GetPerAppInterface() != NULL;
+ 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);
+ // An extension can be synced / updated at any time and therefore not be
+ // available.
+ return extension ? extension->is_platform_app() : false;
+}
+
+void ChromeLauncherController::LaunchApp(const std::string& app_id,
+ int event_flags) {
+ // |extension| could be NULL when it is being unloaded for updating.
+ const Extension* extension = GetExtensionForAppID(app_id);
+ if (!extension)
+ return;
+
+ const ExtensionService* service =
+ extensions::ExtensionSystem::Get(profile_)->extension_service();
+ if (!service->IsExtensionEnabledForLauncher(app_id)) {
+ // Do nothing if there is already a running enable flow.
+ if (extension_enable_flow_)
+ return;
+
+ extension_enable_flow_.reset(
+ new ExtensionEnableFlow(profile_, app_id, this));
+ extension_enable_flow_->StartForNativeWindow(NULL);
+ return;
+ }
+
+ chrome::OpenApplication(chrome::AppLaunchParams(GetProfileForNewWindows(),
+ extension,
+ event_flags));
+}
+
+void ChromeLauncherController::ActivateApp(const std::string& app_id,
+ int event_flags) {
+ // If there is an existing non-shortcut controller for this app, open it.
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id) {
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ controller->Activate();
+ return;
+ }
+
+ // Create a temporary application launcher item and use it to see if there are
+ // running instances.
+ scoped_ptr<AppShortcutLauncherItemController> app_controller(
+ new AppShortcutLauncherItemController(app_id, this));
+ if (!app_controller->GetRunningApplications().empty())
+ app_controller->Activate();
+ else
+ 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());
+
+ // An extension can be unloaded/updated/unavailable at any time.
+ if (!extension)
+ return extensions::ExtensionPrefs::LAUNCH_DEFAULT;
+
+ 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->type() == LauncherItemController::TYPE_APP_PANEL)
+ continue; // Don't include panels
+ if (i->second->app_id() == app_id)
+ return i->first;
+ }
+ return 0;
+}
+
+std::string ChromeLauncherController::GetAppIDForLauncherID(
+ ash::LauncherID id) {
+ CHECK(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) {
+ LauncherItemController* controller = i->second;
+ if (controller->app_id() != id)
+ continue;
+ if (controller->image_set_by_controller())
+ continue;
+ int index = model_->ItemIndexByID(i->first);
+ if (index == -1)
+ continue;
+ 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.
+ }
+}
+
+void ChromeLauncherController::OnAutoHideBehaviorChanged(
+ aura::RootWindow* root_window,
+ ash::ShelfAutoHideBehavior new_behavior) {
+ SetShelfAutoHideBehaviorPrefs(new_behavior, root_window);
+}
+
+void ChromeLauncherController::SetLauncherItemImage(
+ ash::LauncherID launcher_id,
+ const gfx::ImageSkia& image) {
+ int index = model_->ItemIndexByID(launcher_id);
+ if (index == -1)
+ return;
+ ash::LauncherItem item = model_->items()[index];
+ item.image = image;
+ model_->Set(index, item);
+}
+
+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;
+}
+
+bool ChromeLauncherController::IsWindowedAppInLauncher(
+ const std::string& app_id) {
+ int index = model_->ItemIndexByID(GetLauncherIDForAppID(app_id));
+ if (index < 0)
+ return false;
+
+ ash::LauncherItemType type = model_->items()[index].type;
+ return type == ash::TYPE_WINDOWED_APP;
+}
+
+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;
+
+ 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(),
+ chrome::HOST_DESKTOP_TYPE_ASH);
+}
+
+bool ChromeLauncherController::CanPin() const {
+ const PrefService::Preference* pref =
+ profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
+ return pref && pref->IsUserModifiable();
+}
+
+void ChromeLauncherController::PersistPinnedState() {
+ if (ignore_persist_pinned_state_change_)
+ return;
+ // 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;
+ }
+
+ // 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);
+ }
+ } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) {
+ PersistChromeItemIndex(i);
+ }
+ }
+ }
+ pref_change_registrar_.Add(
+ prefs::kPinnedLauncherApps,
+ base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
+ base::Unretained(this)));
+}
+
+ash::LauncherModel* ChromeLauncherController::model() {
+ return model_;
+}
+
+Profile* ChromeLauncherController::profile() {
+ return profile_;
+}
+
+ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior(
+ aura::RootWindow* root_window) const {
+ // Don't show the shelf in app mode.
+ if (chrome::IsRunningInAppMode())
+ return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
+
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string behavior_value(
+ GetPrefForRootWindow(profile_->GetPrefs(),
+ root_window,
+ 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://crbug.com/146773).
+ if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
+ return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+ return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
+}
+
+bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior(
+ aura::RootWindow* root_window) const {
+ return profile_->GetPrefs()->
+ FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable();
+}
+
+void ChromeLauncherController::ToggleShelfAutoHideBehavior(
+ aura::RootWindow* root_window) {
+ ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) ==
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER :
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+ SetShelfAutoHideBehaviorPrefs(behavior, root_window);
+ return;
+}
+
+void ChromeLauncherController::RemoveTabFromRunningApp(
+ WebContents* tab,
+ const std::string& app_id) {
+ web_contents_to_app_id_.erase(tab);
+ AppIDToWebContentsListMap::iterator i_app_id =
+ app_id_to_web_contents_list_.find(app_id);
+ if (i_app_id != app_id_to_web_contents_list_.end()) {
+ WebContentsList* tab_list = &i_app_id->second;
+ tab_list->remove(tab);
+ if (tab_list->empty()) {
+ app_id_to_web_contents_list_.erase(i_app_id);
+ i_app_id = app_id_to_web_contents_list_.end();
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id)
+ SetItemStatus(id, ash::STATUS_CLOSED);
+ }
+ }
+}
+
+void ChromeLauncherController::UpdateAppState(content::WebContents* contents,
+ AppState app_state) {
+ std::string app_id = GetAppID(contents);
+
+ // Check if the gMail app is loaded and it matches the given content.
+ // This special treatment is needed to address crbug.com/234268.
+ if (app_id.empty() && ContentCanBeHandledByGmailApp(contents))
+ app_id = kGmailAppId;
+
+ // Check the old |app_id| for a tab. If the contents has changed we need to
+ // remove it from the previous app.
+ if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) {
+ std::string last_app_id = web_contents_to_app_id_[contents];
+ if (last_app_id != app_id)
+ RemoveTabFromRunningApp(contents, last_app_id);
+ }
+
+ if (app_id.empty()) {
+ // Even if there is no application running, we should update the activation
+ // state of the associated browser.
+ UpdateBrowserItemStatus();
+ return;
+ }
+
+ web_contents_to_app_id_[contents] = app_id;
+
+ if (app_state == APP_STATE_REMOVED) {
+ // The tab has gone away.
+ RemoveTabFromRunningApp(contents, app_id);
+ } else {
+ WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]);
+
+ if (app_state == APP_STATE_INACTIVE) {
+ WebContentsList::const_iterator i_tab =
+ std::find(tab_list.begin(), tab_list.end(), contents);
+ if (i_tab == tab_list.end())
+ tab_list.push_back(contents);
+ 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(contents);
+ tab_list.push_front(contents);
+ }
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id) {
+ // If the window is active, mark the app as active.
+ SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
+ ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
+ }
+ }
+ UpdateBrowserItemStatus();
+}
+
+void ChromeLauncherController::SetRefocusURLPatternForTest(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 || type == ash::TYPE_WINDOWED_APP) {
+ 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) const {
+ // Some unit tests do not have a real extension.
+ return (profile_->GetExtensionService()) ?
+ profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL;
+}
+
+void ChromeLauncherController::ActivateWindowOrMinimizeIfActive(
+ ui::BaseWindow* window,
+ bool allow_minimize) {
+ if (window->IsActive() && allow_minimize) {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableMinimizeOnSecondLauncherItemClick)) {
+ AnimateWindow(window->GetNativeWindow(),
+ views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
+ } else {
+ window->Minimize();
+ }
+ } else {
+ window->Show();
+ window->Activate();
+ }
+}
+
+void ChromeLauncherController::ItemSelected(const ash::LauncherItem& item,
+ const ui::Event& event) {
+ DCHECK(HasItemController(item.id));
+ LauncherItemController* item_controller = id_to_item_controller_map_[item.id];
+#if defined(OS_CHROMEOS)
+ if (!item_controller->app_id().empty()) {
+ chromeos::default_pinned_apps_field_trial::RecordShelfAppClick(
+ item_controller->app_id());
+ }
+#endif
+ item_controller->Clicked(event);
+}
+
+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::LauncherMenuModel* ChromeLauncherController::CreateApplicationMenu(
+ const ash::LauncherItem& item,
+ int event_flags) {
+ return new LauncherApplicationMenuItemModel(GetApplicationList(item,
+ event_flags));
+}
+
+ash::LauncherID ChromeLauncherController::GetIDByWindow(aura::Window* window) {
+ int browser_index = ash::launcher::GetBrowserItemIndex(*model_);
+ DCHECK_GE(browser_index, 0);
+ ash::LauncherID browser_id = model_->items()[browser_index].id;
+
+ IDToItemControllerMap::const_iterator i = id_to_item_controller_map_.begin();
+ for (; i != id_to_item_controller_map_.end(); ++i) {
+ // Since a |window| can be used by multiple applications, an explicit
+ // application always gets chosen over the generic browser.
+ if (i->first != browser_id && i->second->IsCurrentlyShownInWindow(window))
+ return i->first;
+ }
+
+ if (i == id_to_item_controller_map_.end() &&
+ GetBrowserShortcutLauncherItemController()->
+ IsCurrentlyShownInWindow(window))
+ return browser_id;
+
+ return 0;
+}
+
+bool ChromeLauncherController::IsDraggable(const ash::LauncherItem& item) {
+ return (item.type == ash::TYPE_APP_SHORTCUT ||
+ item.type == ash::TYPE_WINDOWED_APP) ? CanPin() : true;
+}
+
+bool ChromeLauncherController::ShouldShowTooltip(
+ const ash::LauncherItem& item) {
+ if (item.type == ash::TYPE_APP_PANEL &&
+ id_to_item_controller_map_[item.id]->IsVisible())
+ return false;
+ return true;
+}
+
+void ChromeLauncherController::OnLauncherCreated(ash::Launcher* launcher) {
+ launchers_.insert(launcher);
+ launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this);
+}
+
+void ChromeLauncherController::OnLauncherDestroyed(ash::Launcher* launcher) {
+ launchers_.erase(launcher);
+ // RemoveObserver is not called here, since by the time this method is called
+ // Launcher is already in its destructor.
+}
+
+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;
+ DCHECK(HasItemController(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: {
+ const Extension* extension =
+ content::Details<const Extension>(details).ptr();
+ if (IsAppPinned(extension->id())) {
+ // Clear and re-fetch to ensure icon is up-to-date.
+ app_icon_loader_->ClearImage(extension->id());
+ app_icon_loader_->FetchImage(extension->id());
+ }
+
+ UpdateAppLaunchersFromPref();
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+ const content::Details<extensions::UnloadedExtensionInfo>& unload_info(
+ details);
+ const Extension* extension = unload_info->extension;
+ const std::string& id = extension->id();
+ // Since we might have windowed apps of this type which might have
+ // outstanding locks which needs to be removed.
+ if (GetLauncherIDForAppID(id) &&
+ unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) {
+ CloseWindowedAppsFromRemovedExtension(id);
+ }
+
+ if (IsAppPinned(id)) {
+ if (unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) {
+ DoUnpinAppsWithID(id);
+ app_icon_loader_->ClearImage(id);
+ } else {
+ app_icon_loader_->UpdateImage(id);
+ }
+ }
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification type=" << type;
+ }
+}
+
+void ChromeLauncherController::OnShelfAlignmentChanged(
+ aura::RootWindow* root_window) {
+ const char* pref_value = NULL;
+ switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) {
+ 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;
+ case ash::SHELF_ALIGNMENT_TOP:
+ pref_value = ash::kShelfAlignmentTop;
+ }
+
+ UpdatePerDisplayPref(
+ profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value);
+
+ if (root_window == ash::Shell::GetPrimaryRootWindow()) {
+ // 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::OnDisplayConfigurationChanging() {
+}
+
+void ChromeLauncherController::OnDisplayConfigurationChanged() {
+ SetShelfBehaviorsFromPrefs();
+}
+
+void ChromeLauncherController::OnIsSyncingChanged() {
+ PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
+ MaybePropagatePrefToLocal(prefs,
+ prefs::kShelfAlignmentLocal,
+ prefs::kShelfAlignment);
+ MaybePropagatePrefToLocal(prefs,
+ 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::ExtensionEnableFlowFinished() {
+ LaunchApp(extension_enable_flow_->extension_id(), ui::EF_NONE);
+ extension_enable_flow_.reset();
+}
+
+void ChromeLauncherController::ExtensionEnableFlowAborted(bool user_initiated) {
+ extension_enable_flow_.reset();
+}
+
+ChromeLauncherAppMenuItems ChromeLauncherController::GetApplicationList(
+ const ash::LauncherItem& item,
+ int event_flags) {
+ // Make sure that there is a controller associated with the id and that the
+ // extension itself is a valid application and not a panel.
+ if (!HasItemController(item.id) ||
+ !GetLauncherIDForAppID(id_to_item_controller_map_[item.id]->app_id()))
+ return ChromeLauncherAppMenuItems().Pass();
+
+ return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags);
+}
+
+std::vector<content::WebContents*>
+ChromeLauncherController::GetV1ApplicationsFromAppId(std::string app_id) {
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+
+ // If there is no such an item pinned to the launcher, no menu gets created.
+ if (id) {
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ DCHECK(controller);
+ if (controller->type() == LauncherItemController::TYPE_SHORTCUT)
+ return GetV1ApplicationsFromController(controller);
+ }
+ return std::vector<content::WebContents*>();
+}
+
+void ChromeLauncherController::ActivateShellApp(const std::string& app_id,
+ int index) {
+ ash::LauncherID id = GetLauncherIDForAppID(app_id);
+ if (id) {
+ LauncherItemController* controller = id_to_item_controller_map_[id];
+ if (controller->type() == LauncherItemController::TYPE_APP) {
+ ShellWindowLauncherItemController* shell_window_controller =
+ static_cast<ShellWindowLauncherItemController*>(controller);
+ shell_window_controller->ActivateIndexedApp(index);
+ }
+ }
+}
+
+bool ChromeLauncherController::IsWebContentHandledByApplication(
+ content::WebContents* web_contents,
+ const std::string& app_id) {
+ if ((web_contents_to_app_id_.find(web_contents) !=
+ web_contents_to_app_id_.end()) &&
+ (web_contents_to_app_id_[web_contents] == app_id))
+ return true;
+ return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents));
+}
+
+bool ChromeLauncherController::ContentCanBeHandledByGmailApp(
+ content::WebContents* web_contents) {
+ ash::LauncherID id = GetLauncherIDForAppID(kGmailAppId);
+ if (id) {
+ const GURL url = web_contents->GetURL();
+ // We need to extend the application matching for the gMail app beyond the
+ // manifest file's specification. This is required because of the namespace
+ // overlap with the offline app ("/mail/mu/").
+ if (!MatchPattern(url.path(), "/mail/mu/*") &&
+ MatchPattern(url.path(), "/mail/*") &&
+ GetExtensionForAppID(kGmailAppId) &&
+ GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url))
+ return true;
+ }
+ return false;
+}
+
+gfx::Image ChromeLauncherController::GetAppListIcon(
+ content::WebContents* web_contents) const {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ if (IsIncognito(web_contents))
+ return rb.GetImageNamed(IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER);
+ FaviconTabHelper* favicon_tab_helper =
+ FaviconTabHelper::FromWebContents(web_contents);
+ gfx::Image result = favicon_tab_helper->GetFavicon();
+ if (result.IsEmpty())
+ return rb.GetImageNamed(IDR_DEFAULT_FAVICON);
+ return result;
+}
+
+string16 ChromeLauncherController::GetAppListTitle(
+ content::WebContents* web_contents) const {
+ string16 title = web_contents->GetTitle();
+ if (!title.empty())
+ return title;
+ WebContentsToAppIDMap::const_iterator iter =
+ web_contents_to_app_id_.find(web_contents);
+ if (iter != web_contents_to_app_id_.end()) {
+ std::string app_id = iter->second;
+ const extensions::Extension* extension = GetExtensionForAppID(app_id);
+ if (extension)
+ return UTF8ToUTF16(extension->name());
+ }
+ return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
+}
+
+void ChromeLauncherController::OnBrowserRemoved(Browser* browser) {
+ // When called by a unit test it is possible that there is no shell.
+ // In that case, the following function should not get called.
+ if (ash::Shell::HasInstance())
+ UpdateBrowserItemStatus();
+}
+
+ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItem(
+ const std::string& app_id,
+ int index) {
+ return CreateAppShortcutLauncherItemWithType(app_id,
+ index,
+ ash::TYPE_APP_SHORTCUT);
+}
+
+void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) {
+ app_tab_helper_.reset(helper);
+}
+
+void ChromeLauncherController::SetAppIconLoaderForTest(
+ extensions::AppIconLoader* loader) {
+ app_icon_loader_.reset(loader);
+}
+
+const std::string& ChromeLauncherController::GetAppIdFromLauncherIdForTest(
+ ash::LauncherID id) {
+ return id_to_item_controller_map_[id]->app_id();
+}
+
+ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItemWithType(
+ const std::string& app_id,
+ int index,
+ ash::LauncherItemType launcher_item_type) {
+ AppShortcutLauncherItemController* controller =
+ new AppShortcutLauncherItemController(app_id, this);
+ ash::LauncherID launcher_id = InsertAppLauncherItem(
+ controller, app_id, ash::STATUS_CLOSED, index, launcher_item_type);
+ return launcher_id;
+}
+
+void ChromeLauncherController::UpdateBrowserItemStatus() {
+ // Determine the new browser's active state and change if necessary.
+ size_t browser_index = ash::launcher::GetBrowserItemIndex(*model_);
+ DCHECK_GE(browser_index, 0u);
+ ash::LauncherItem browser_item = model_->items()[browser_index];
+ ash::LauncherItemStatus browser_status = ash::STATUS_CLOSED;
+
+ aura::Window* window = ash::wm::GetActiveWindow();
+ if (window) {
+ // Check if the active browser / tab is a browser which is not an app,
+ // a windowed app, a popup or any other item which is not a browser of
+ // interest.
+ Browser* browser = chrome::FindBrowserWithWindow(window);
+ if (IsBrowserRepresentedInBrowserList(browser)) {
+ browser_status = ash::STATUS_ACTIVE;
+ const ash::LauncherItems& items = model_->items();
+ // If another launcher item has claimed to be active, we don't.
+ for (size_t i = 0;
+ i < items.size() && browser_status == ash::STATUS_ACTIVE; ++i) {
+ if (i != browser_index && items[i].status == ash::STATUS_ACTIVE)
+ browser_status = ash::STATUS_RUNNING;
+ }
+ }
+ }
+
+ if (browser_status == ash::STATUS_CLOSED) {
+ const BrowserList* ash_browser_list =
+ BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
+ for (BrowserList::const_reverse_iterator it =
+ ash_browser_list->begin_last_active();
+ it != ash_browser_list->end_last_active() &&
+ browser_status == ash::STATUS_CLOSED; ++it) {
+ if (IsBrowserRepresentedInBrowserList(*it))
+ browser_status = ash::STATUS_RUNNING;
+ }
+ }
+
+ if (browser_status != browser_item.status) {
+ browser_item.status = browser_status;
+ model_->Set(browser_index, browser_item);
+ }
+}
+
+Profile* ChromeLauncherController::GetProfileForNewWindows() {
+ return ProfileManager::GetDefaultProfileOrOffTheRecord();
+}
+
+void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) {
+ IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
+ CHECK(iter != id_to_item_controller_map_.end());
+ CHECK(iter->second);
+ app_icon_loader_->ClearImage(iter->second->app_id());
+ iter->second->OnRemoved();
+ id_to_item_controller_map_.erase(iter);
+ int index = model_->ItemIndexByID(id);
+ // A "browser proxy" is not known to the model and this removal does
+ // therefore not need to be propagated to the model.
+ if (index != -1)
+ model_->RemoveItemAt(index);
+}
+
+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;
+ int chrome_icon_index = GetChromeIconIndexFromPref();
+ int index = 0;
+ int max_index = model_->item_count();
+ // Using the alternate shelf layout the App Icon should be the first item in
+ // the list thus start adding items at slot 1 (instead of slot 0).
+ if (ash::switches::UseAlternateShelfLayout()) {
+ ++index;
+ ++max_index;
+ // The alternate shelf layout's icon position will always include the
+ // AppLauncher which needs to be subtracted here.
+ if (chrome_icon_index > 0)
+ --chrome_icon_index;
+ }
+ 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) {
+ // To preserve the Chrome icon position, we insert a dummy slot for it - if
+ // the model has a Chrome item. While initializing we can come here with no
+ // item in which case the count would be 1 or below.
+ if (it - pinned_apps_pref->begin() == chrome_icon_index &&
+ model_->item_count() > 1) {
+ pinned_apps.push_back(extension_misc::kChromeAppId);
+ }
+
+ 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());
+ for (; index < max_index && 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 (*pref_app_id == extension_misc::kChromeAppId ||
+ IsAppPinned(*pref_app_id)) {
+ for (; index < max_index; ++index) {
+ const ash::LauncherItem& item(model_->items()[index]);
+ if (item.type != ash::TYPE_APP_SHORTCUT &&
+ item.type != ash::TYPE_BROWSER_SHORTCUT)
+ continue;
+
+ IDToItemControllerMap::const_iterator entry =
+ id_to_item_controller_map_.find(item.id);
+ if ((extension_misc::kChromeAppId == *pref_app_id &&
+ item.type == ash::TYPE_BROWSER_SHORTCUT) ||
+ (entry != id_to_item_controller_map_.end() &&
+ entry->second->app_id() == *pref_app_id)) {
+ ++pref_app_id;
+ break;
+ } else {
+ if (item.type == ash::TYPE_BROWSER_SHORTCUT) {
+ // We cannot delete the browser shortcut. As such we move it up by
+ // one. To avoid any side effects from our pinned state observer, we
+ // do not call the model directly.
+ MoveItemWithoutPinnedStateChangeNotification(index, index + 1);
+ } else {
+ LauncherItemClosed(item.id);
+ --max_index;
+ }
+ --index;
+ }
+ }
+ // If the item wasn't found, that means id_to_item_controller_map_
+ // is out of sync.
+ DCHECK(index < max_index);
+ } 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) {
+ // Ignore the chrome icon.
+ if (*pref_app_id != extension_misc::kChromeAppId)
+ DoPinAppWithID(*pref_app_id);
+ }
+
+}
+
+void ChromeLauncherController::SetShelfAutoHideBehaviorPrefs(
+ ash::ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) {
+ 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;
+ case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
+ // This one should not be a valid preference option for now. We only want
+ // to completely hide it when we run app mode.
+ NOTREACHED();
+ return;
+ }
+
+ UpdatePerDisplayPref(
+ profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value);
+
+ if (root_window == ash::Shell::GetPrimaryRootWindow()) {
+ // 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::SetShelfAutoHideBehaviorFromPrefs() {
+ ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
+
+ for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
+ GetShelfAutoHideBehavior(*iter), *iter);
+ }
+}
+
+void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
+ if (!ash::ShelfWidget::ShelfAlignmentAllowed())
+ return;
+
+ ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
+
+ for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ // See comment in |kShelfAlignment| as to why we consider two prefs.
+ const std::string alignment_value(
+ GetPrefForRootWindow(profile_->GetPrefs(),
+ *iter,
+ 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;
+ else if (alignment_value == ash::kShelfAlignmentTop)
+ alignment = ash::SHELF_ALIGNMENT_TOP;
+ ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter);
+ }
+}
+
+void ChromeLauncherController::SetShelfBehaviorsFromPrefs() {
+ SetShelfAutoHideBehaviorFromPrefs();
+ SetShelfAlignmentFromPrefs();
+}
+
+WebContents* ChromeLauncherController::GetLastActiveWebContents(
+ const std::string& app_id) {
+ AppIDToWebContentsListMap::const_iterator i =
+ app_id_to_web_contents_list_.find(app_id);
+ if (i == app_id_to_web_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::LauncherItemType launcher_item_type) {
+ ash::LauncherID id = model_->next_id();
+ CHECK(!HasItemController(id));
+ CHECK(controller);
+ id_to_item_controller_map_[id] = controller;
+ controller->set_launcher_id(id);
+
+ ash::LauncherItem item;
+ item.type = launcher_item_type;
+ item.is_incognito = false;
+ item.image = extensions::IconsInfo::GetDefaultAppIcon();
+
+ WebContents* active_tab = GetLastActiveWebContents(app_id);
+ if (active_tab) {
+ Browser* browser = chrome::FindBrowserWithWebContents(active_tab);
+ DCHECK(browser);
+ if (browser->window()->IsActive())
+ status = ash::STATUS_ACTIVE;
+ else
+ status = ash::STATUS_RUNNING;
+ }
+ item.status = status;
+
+ model_->AddAt(index, item);
+
+ app_icon_loader_->FetchImage(app_id);
+
+ return id;
+}
+
+bool ChromeLauncherController::HasItemController(ash::LauncherID id) const {
+ return id_to_item_controller_map_.find(id) !=
+ id_to_item_controller_map_.end();
+}
+
+std::vector<content::WebContents*>
+ChromeLauncherController::GetV1ApplicationsFromController(
+ LauncherItemController* controller) {
+ DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT);
+ AppShortcutLauncherItemController* app_controller =
+ static_cast<AppShortcutLauncherItemController*>(controller);
+ return app_controller->GetRunningApplications();
+}
+
+bool ChromeLauncherController::IsBrowserRepresentedInBrowserList(
+ Browser* browser) {
+ return (browser &&
+ (browser->is_type_tabbed() ||
+ !browser->is_app() ||
+ !browser->is_type_popup() ||
+ GetLauncherIDForAppID(web_app::GetExtensionIdFromApplicationName(
+ browser->app_name())) <= 0));
+}
+
+LauncherItemController*
+ChromeLauncherController::GetBrowserShortcutLauncherItemController() {
+ for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
+ i != id_to_item_controller_map_.end(); ++i) {
+ int index = model_->ItemIndexByID(i->first);
+ const ash::LauncherItem& item = model_->items()[index];
+ if (item.type == ash::TYPE_BROWSER_SHORTCUT)
+ return i->second;
+ }
+ // LauncerItemController For Browser Shortcut must be existed. If it does not
+ // existe create it.
+ ash::LauncherID id = CreateBrowserShortcutLauncherItem();
+ DCHECK(id_to_item_controller_map_[id]);
+ return id_to_item_controller_map_[id];
+}
+
+ash::LauncherID ChromeLauncherController::CreateBrowserShortcutLauncherItem() {
+ ash::LauncherItem browser_shortcut;
+ browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT;
+ browser_shortcut.is_incognito = false;
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32);
+ ash::LauncherID id = model_->next_id();
+ size_t index = GetChromeIconIndexFromPref();
+ model_->AddAt(index, browser_shortcut);
+ browser_item_controller_.reset(
+ new BrowserShortcutLauncherItemController(this, profile_));
+ id_to_item_controller_map_[id] = browser_item_controller_.get();
+ id_to_item_controller_map_[id]->set_launcher_id(id);
+ return id;
+}
+
+void ChromeLauncherController::PersistChromeItemIndex(int index) {
+ profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index);
+}
+
+int ChromeLauncherController::GetChromeIconIndexFromPref() const {
+ size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex);
+ const base::ListValue* pinned_apps_pref =
+ profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
+ if (ash::switches::UseAlternateShelfLayout())
+ return std::max(static_cast<size_t>(1),
+ std::min(pinned_apps_pref->GetSize() + 1, index));
+ return std::max(static_cast<size_t>(0),
+ std::min(pinned_apps_pref->GetSize(), index));
+}
+
+bool ChromeLauncherController::IsIncognito(
+ content::WebContents* web_contents) const {
+ const Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ return profile->IsOffTheRecord() && !profile->IsGuestSession();
+}
+
+void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension(
+ const std::string& app_id) {
+ // This function cannot rely on the controller's enumeration functionality
+ // since the extension has already be unloaded.
+ const BrowserList* ash_browser_list =
+ BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
+ std::vector<Browser*> browser_to_close;
+ for (BrowserList::const_reverse_iterator
+ it = ash_browser_list->begin_last_active();
+ it != ash_browser_list->end_last_active(); ++it) {
+ Browser* browser = *it;
+ if (!browser->is_type_tabbed() &&
+ browser->is_type_popup() &&
+ browser->is_app() &&
+ app_id == web_app::GetExtensionIdFromApplicationName(
+ browser->app_name())) {
+ browser_to_close.push_back(browser);
+ }
+ }
+ while (!browser_to_close.empty()) {
+ TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model();
+ tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE);
+ browser_to_close.pop_back();
+ }
+}
+
+void
+ChromeLauncherController::MoveItemWithoutPinnedStateChangeNotification(
+ int source_index, int target_index) {
+ base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true);
+ model_->Move(source_index, target_index);
}

Powered by Google App Engine
This is Rietveld 408576698