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); |
} |