blob: 11a9368472060f0b6289b474ae37046148904286 [file] [log] [blame]
// 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.h"
#include <set>
#include <vector>
#include "ash/launcher/launcher_model.h"
#include "ash/launcher/launcher_types.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/extensions/pending_extension_manager.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/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.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 "grit/theme_resources.h"
#include "ui/aura/window.h"
using extensions::Extension;
namespace {
// Max loading animation time in milliseconds.
const int kMaxLoadingTimeMs = 60 * 1000;
// 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) {
}
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 Open(int event_flags) OVERRIDE {
// This will open the app for app_id(), replacing this with an appropriate
// LauncherItemController.
launcher_controller()->OpenAppID(app_id(), event_flags);
}
virtual void Close() OVERRIDE {
// TODO: maybe should treat as unpin?
}
virtual void Clicked() OVERRIDE {
Open(ui::EF_NONE);
}
virtual void OnRemoved() OVERRIDE {
// AppShortcutLauncherItemController is unowned; delete on removal.
delete this;
}
private:
DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController);
};
} // namespace
// ChromeLauncherController ----------------------------------------------------
// statics
ChromeLauncherController* ChromeLauncherController::instance_ = NULL;
ChromeLauncherController::ChromeLauncherController(Profile* profile,
ash::LauncherModel* model)
: model_(model),
profile_(profile),
observed_sync_service_(NULL) {
if (!profile_) {
// Use the original profile as on chromeos we may get a temporary off the
// record profile.
profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile();
// Monitor app sync on chromeos.
PrefService* prefs = profile_->GetPrefs();
if (!IsLoggedInAsGuest() &&
prefs->GetBoolean(prefs::kLauncherShouldRunSyncAnimation)) {
prefs->SetBoolean(prefs::kLauncherShouldRunSyncAnimation, false);
observed_sync_service_ =
ProfileSyncServiceFactory::GetForProfile(profile_);
if (observed_sync_service_)
observed_sync_service_->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);
pref_change_registrar_.Add(prefs::kShelfAlignment, this);
pref_change_registrar_.Add(prefs::kShelfAutoHideBehavior, this);
}
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 (observed_sync_service_)
observed_sync_service_->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();
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::Open(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]->Open(event_flags);
}
void ChromeLauncherController::OpenAppID(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 > 0) {
LauncherItemController* controller = id_to_item_controller_map_[id];
if (controller->type() != LauncherItemController::TYPE_SHORTCUT) {
controller->Open(event_flags);
return;
}
}
// Check if there are any open tabs for this app.
AppIDToTabContentsListMap::iterator i =
app_id_to_tab_contents_list_.find(app_id);
if (i != app_id_to_tab_contents_list_.end()) {
DCHECK(!i->second.empty());
TabContents* tab = i->second.front();
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());
} else {
const Extension* extension =
profile_->GetExtensionService()->GetInstalledExtension(app_id);
extension_utils::OpenExtension(GetProfileForNewWindows(),
extension,
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();
}
extensions::ExtensionPrefs::LaunchType ChromeLauncherController::GetLaunchType(
ash::LauncherID id) {
DCHECK(HasItemController(id));
return profile_->GetExtensionService()->extension_prefs()->GetLaunchType(
id_to_item_controller_map_[id]->app_id(),
extensions::ExtensionPrefs::LAUNCH_DEFAULT);
}
std::string ChromeLauncherController::GetAppID(TabContents* 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;
}
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::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) {
ash::Shell::GetInstance()->SetShelfAutoHideBehavior(behavior);
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;
}
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(TabContents* tab,
AppState app_state) {
std::string app_id = GetAppID(tab);
// Check the old |app_id| for a tab. If the contents has changed we need to
// remove it from the previous app.
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);
} 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::CreateNewTab() {
Browser* last_browser = browser::FindTabbedBrowser(
GetProfileForNewWindows(), true);
if (!last_browser) {
CreateNewWindow();
return;
}
if (!IsActiveBrowserShowingNTP(last_browser))
chrome::NewTab(last_browser);
aura::Window* window = last_browser->window()->GetNativeWindow();
window->Show();
ash::wm::ActivateWindow(window);
}
void ChromeLauncherController::CreateNewWindow() {
chrome::NewEmptyWindow(GetProfileForNewWindows());
}
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) {
return new LauncherContextMenu(this, &item);
}
ui::MenuModel* ChromeLauncherController::CreateContextMenuForLauncher() {
return new LauncherContextMenu(this, NULL);
}
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) {
if (model_->items()[index].status == ash::STATUS_ACTIVE &&
old_item.status == ash::STATUS_RUNNING) {
ash::LauncherID id = model_->items()[index].id;
id_to_item_controller_map_[id]->Open(ui::EF_NONE);
}
}
void ChromeLauncherController::LauncherStatusChanged() {
}
void ChromeLauncherController::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_EXTENSION_LOADED: {
UpdateAppLaunchersFromPref();
CheckAppSync();
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;
}
case chrome::NOTIFICATION_PREF_CHANGED: {
const std::string& pref_name(
*content::Details<std::string>(details).ptr());
if (pref_name == prefs::kPinnedLauncherApps)
UpdateAppLaunchersFromPref();
else if (pref_name == prefs::kShelfAlignment)
SetShelfAlignmentFromPrefs();
else if (pref_name == prefs::kShelfAutoHideBehavior)
SetShelfAutoHideBehaviorFromPrefs();
else
NOTREACHED() << "Unexpected pref change for " << pref_name;
break;
}
default:
NOTREACHED() << "Unexpected notification type=" << type;
}
}
void ChromeLauncherController::OnShelfAlignmentChanged() {
const char* pref_value = NULL;
switch (ash::Shell::GetInstance()->GetShelfAlignment()) {
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;
}
profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
}
void ChromeLauncherController::OnStateChanged() {
DCHECK(observed_sync_service_);
CheckAppSync();
}
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, this);
{
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());
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() {
// 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)
const std::string behavior_value(
profile_->GetPrefs()->GetString(prefs::kShelfAutoHideBehavior));
ash::ShelfAutoHideBehavior behavior =
ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
ash::Shell::GetInstance()->SetShelfAutoHideBehavior(behavior);
}
void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kShowLauncherAlignmentMenu))
return;
const std::string alignment_value(
profile_->GetPrefs()->GetString(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;
ash::Shell::GetInstance()->SetShelfAlignment(alignment);
}
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();
}
void ChromeLauncherController::CheckAppSync() {
if (!observed_sync_service_ ||
!observed_sync_service_->HasSyncSetupCompleted()) {
return;
}
const bool synced = observed_sync_service_->ShouldPushChanges();
const bool has_pending_extension = profile_->GetExtensionService()->
pending_extension_manager()->HasPendingExtensionFromSync();
if (synced && !has_pending_extension)
StopLoadingAnimation();
else
StartLoadingAnimation();
}
void ChromeLauncherController::StartLoadingAnimation() {
DCHECK(observed_sync_service_);
if (model_->status() == ash::LauncherModel::STATUS_LOADING)
return;
loading_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kMaxLoadingTimeMs),
this, &ChromeLauncherController::StopLoadingAnimation);
model_->SetStatus(ash::LauncherModel::STATUS_LOADING);
}
void ChromeLauncherController::StopLoadingAnimation() {
DCHECK(observed_sync_service_);
model_->SetStatus(ash::LauncherModel::STATUS_NORMAL);
loading_timer_.Stop();
observed_sync_service_->RemoveObserver(this);
observed_sync_service_ = NULL;
}
bool ChromeLauncherController::IsActiveBrowserShowingNTP(Browser* browser) {
content::WebContents* current_tab = chrome::GetActiveWebContents(browser);
if (current_tab) {
content::NavigationEntry* active_entry =
current_tab->GetController().GetActiveEntry();
if (active_entry &&
active_entry->GetURL() == GURL(chrome::kChromeUINewTabURL))
return true;
}
return false;
}