Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / ash / launcher / chrome_launcher_controller.cc
blob93132d9ca9150e5733c6b7c9a7a345b41a8ff394
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
7 #include <vector>
9 #include "ash/ash_switches.h"
10 #include "ash/desktop_background/desktop_background_controller.h"
11 #include "ash/multi_profile_uma.h"
12 #include "ash/root_window_controller.h"
13 #include "ash/shelf/shelf.h"
14 #include "ash/shelf/shelf_item_delegate_manager.h"
15 #include "ash/shelf/shelf_layout_manager.h"
16 #include "ash/shelf/shelf_model.h"
17 #include "ash/shelf/shelf_widget.h"
18 #include "ash/shell.h"
19 #include "ash/wm/window_util.h"
20 #include "base/command_line.h"
21 #include "base/prefs/scoped_user_pref_update.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/values.h"
25 #include "chrome/browser/app_mode/app_mode_utils.h"
26 #include "chrome/browser/chrome_notification_types.h"
27 #include "chrome/browser/defaults.h"
28 #include "chrome/browser/extensions/app_icon_loader_impl.h"
29 #include "chrome/browser/extensions/extension_service.h"
30 #include "chrome/browser/extensions/extension_system.h"
31 #include "chrome/browser/extensions/extension_util.h"
32 #include "chrome/browser/extensions/launch_util.h"
33 #include "chrome/browser/favicon/favicon_tab_helper.h"
34 #include "chrome/browser/prefs/incognito_mode_prefs.h"
35 #include "chrome/browser/prefs/pref_service_syncable.h"
36 #include "chrome/browser/profiles/profile.h"
37 #include "chrome/browser/profiles/profile_manager.h"
38 #include "chrome/browser/ui/ash/app_sync_ui_state.h"
39 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
40 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
41 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
42 #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h"
43 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
44 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h"
45 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
46 #include "chrome/browser/ui/ash/launcher/chrome_launcher_types.h"
47 #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
48 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
49 #include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h"
50 #include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h"
51 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
52 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
53 #include "chrome/browser/ui/browser.h"
54 #include "chrome/browser/ui/browser_commands.h"
55 #include "chrome/browser/ui/browser_finder.h"
56 #include "chrome/browser/ui/browser_list.h"
57 #include "chrome/browser/ui/browser_tabstrip.h"
58 #include "chrome/browser/ui/browser_window.h"
59 #include "chrome/browser/ui/extensions/application_launch.h"
60 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
61 #include "chrome/browser/ui/host_desktop.h"
62 #include "chrome/browser/ui/tabs/tab_strip_model.h"
63 #include "chrome/browser/web_applications/web_app.h"
64 #include "chrome/common/chrome_switches.h"
65 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
66 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
67 #include "chrome/common/pref_names.h"
68 #include "chrome/common/url_constants.h"
69 #include "content/public/browser/navigation_entry.h"
70 #include "content/public/browser/notification_registrar.h"
71 #include "content/public/browser/notification_service.h"
72 #include "content/public/browser/web_contents.h"
73 #include "extensions/common/extension.h"
74 #include "extensions/common/extension_resource.h"
75 #include "extensions/common/url_pattern.h"
76 #include "grit/ash_resources.h"
77 #include "grit/chromium_strings.h"
78 #include "grit/generated_resources.h"
79 #include "grit/theme_resources.h"
80 #include "grit/ui_resources.h"
81 #include "net/base/url_util.h"
82 #include "ui/aura/root_window.h"
83 #include "ui/aura/window.h"
84 #include "ui/base/l10n/l10n_util.h"
85 #include "ui/views/corewm/window_animations.h"
87 #if defined(OS_CHROMEOS)
88 #include "chrome/browser/browser_process.h"
89 #include "chrome/browser/chromeos/login/user_manager.h"
90 #include "chrome/browser/chromeos/login/wallpaper_manager.h"
91 #include "chrome/browser/ui/ash/chrome_shell_delegate.h"
92 #include "chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.h"
93 #include "chrome/browser/ui/ash/launcher/multi_profile_shell_window_launcher_controller.h"
94 #endif
96 using extensions::Extension;
97 using extensions::UnloadedExtensionInfo;
98 using extension_misc::kGmailAppId;
99 using content::WebContents;
101 // static
102 ChromeLauncherController* ChromeLauncherController::instance_ = NULL;
104 namespace {
106 // This will be used as placeholder in the list of the pinned applciatons.
107 // Note that this is NOT a valid extension identifier so that pre M31 versions
108 // will ignore it.
109 const char kAppLauncherIdPlaceholder[] = "AppLauncherIDPlaceholder--------";
111 std::string GetPrefKeyForRootWindow(aura::Window* root_window) {
112 gfx::Display display = gfx::Screen::GetScreenFor(
113 root_window)->GetDisplayNearestWindow(root_window);
114 DCHECK(display.is_valid());
116 return base::Int64ToString(display.id());
119 void UpdatePerDisplayPref(PrefService* pref_service,
120 aura::Window* root_window,
121 const char* pref_key,
122 const std::string& value) {
123 std::string key = GetPrefKeyForRootWindow(root_window);
124 if (key.empty())
125 return;
127 DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences);
128 base::DictionaryValue* shelf_prefs = update.Get();
129 base::DictionaryValue* prefs = NULL;
130 if (!shelf_prefs->GetDictionary(key, &prefs)) {
131 prefs = new base::DictionaryValue();
132 shelf_prefs->Set(key, prefs);
134 prefs->SetStringWithoutPathExpansion(pref_key, value);
137 // Returns a pref value in |pref_service| for the display of |root_window|. The
138 // pref value is stored in |local_path| and |path|, but |pref_service| may have
139 // per-display preferences and the value can be specified by policy. Here is
140 // the priority:
141 // * A value managed by policy. This is a single value that applies to all
142 // displays.
143 // * A user-set value for the specified display.
144 // * A user-set value in |local_path| or |path|, if no per-display settings are
145 // ever specified (see http://crbug.com/173719 for why). |local_path| is
146 // preferred. See comment in |kShelfAlignment| as to why we consider two
147 // prefs and why |local_path| is preferred.
148 // * A value recommended by policy. This is a single value that applies to all
149 // root windows.
150 // * The default value for |local_path| if the value is not recommended by
151 // policy.
152 std::string GetPrefForRootWindow(PrefService* pref_service,
153 aura::Window* root_window,
154 const char* local_path,
155 const char* path) {
156 const PrefService::Preference* local_pref =
157 pref_service->FindPreference(local_path);
158 const std::string value(pref_service->GetString(local_path));
159 if (local_pref->IsManaged())
160 return value;
162 std::string pref_key = GetPrefKeyForRootWindow(root_window);
163 bool has_per_display_prefs = false;
164 if (!pref_key.empty()) {
165 const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary(
166 prefs::kShelfPreferences);
167 const base::DictionaryValue* display_pref = NULL;
168 std::string per_display_value;
169 if (shelf_prefs->GetDictionary(pref_key, &display_pref) &&
170 display_pref->GetString(path, &per_display_value))
171 return per_display_value;
173 // If the pref for the specified display is not found, scan the whole prefs
174 // and check if the prefs for other display is already specified.
175 std::string unused_value;
176 for (base::DictionaryValue::Iterator iter(*shelf_prefs);
177 !iter.IsAtEnd(); iter.Advance()) {
178 const base::DictionaryValue* display_pref = NULL;
179 if (iter.value().GetAsDictionary(&display_pref) &&
180 display_pref->GetString(path, &unused_value)) {
181 has_per_display_prefs = true;
182 break;
187 if (local_pref->IsRecommended() || !has_per_display_prefs)
188 return value;
190 const base::Value* default_value =
191 pref_service->GetDefaultPrefValue(local_path);
192 std::string default_string;
193 default_value->GetAsString(&default_string);
194 return default_string;
197 // If prefs have synced and no user-set value exists at |local_path|, the value
198 // from |synced_path| is copied to |local_path|.
199 void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service,
200 const char* local_path,
201 const char* synced_path) {
202 if (!pref_service->FindPreference(local_path)->HasUserSetting() &&
203 pref_service->IsSyncing()) {
204 // First time the user is using this machine, propagate from remote to
205 // local.
206 pref_service->SetString(local_path, pref_service->GetString(synced_path));
210 std::string GetSourceFromAppListSource(ash::LaunchSource source) {
211 switch (source) {
212 case ash::LAUNCH_FROM_APP_LIST:
213 return std::string(extension_urls::kLaunchSourceAppList);
214 case ash::LAUNCH_FROM_APP_LIST_SEARCH:
215 return std::string(extension_urls::kLaunchSourceAppListSearch);
216 default: return std::string();
220 } // namespace
222 #if defined(OS_CHROMEOS)
223 // A class to get events from ChromeOS when a user gets changed or added.
224 class ChromeLauncherControllerUserSwitchObserverChromeOS
225 : public ChromeLauncherControllerUserSwitchObserver,
226 public chromeos::UserManager::UserSessionStateObserver,
227 content::NotificationObserver {
228 public:
229 ChromeLauncherControllerUserSwitchObserverChromeOS(
230 ChromeLauncherController* controller)
231 : controller_(controller) {
232 DCHECK(chromeos::UserManager::IsInitialized());
233 chromeos::UserManager::Get()->AddSessionStateObserver(this);
234 // A UserAddedToSession notification can be sent before a profile is loaded.
235 // Since our observers require that we have already a profile, we might have
236 // to postpone the notification until the ProfileManager lets us know that
237 // the profile for that newly added user was added to the ProfileManager.
238 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED,
239 content::NotificationService::AllSources());
241 virtual ~ChromeLauncherControllerUserSwitchObserverChromeOS() {
242 chromeos::UserManager::Get()->RemoveSessionStateObserver(this);
245 // chromeos::UserManager::UserSessionStateObserver overrides:
246 virtual void ActiveUserChanged(const chromeos::User* active_user) OVERRIDE;
247 virtual void UserAddedToSession(const chromeos::User* added_user) OVERRIDE;
249 // content::NotificationObserver overrides:
250 virtual void Observe(int type,
251 const content::NotificationSource& source,
252 const content::NotificationDetails& details) OVERRIDE;
254 private:
255 // Add a user to the session.
256 void AddUser(Profile* profile);
258 // The owning ChromeLauncherController.
259 ChromeLauncherController* controller_;
261 // The notification registrar to track the Profile creations after a user got
262 // added to the session (if required).
263 content::NotificationRegistrar registrar_;
265 // Users which were just added to the system, but which profiles were not yet
266 // (fully) loaded.
267 std::set<std::string> added_user_ids_waiting_for_profiles_;
269 DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerUserSwitchObserverChromeOS);
272 void ChromeLauncherControllerUserSwitchObserverChromeOS::ActiveUserChanged(
273 const chromeos::User* active_user) {
274 const std::string& user_email = active_user->email();
275 // Forward the OS specific event to the ChromeLauncherController.
276 controller_->ActiveUserChanged(user_email);
277 // TODO(skuhne): At the moment the login screen does the wallpaper management
278 // and wallpapers are not synchronized across multiple desktops.
279 if (chromeos::WallpaperManager::Get())
280 chromeos::WallpaperManager::Get()->SetUserWallpaper(user_email);
283 void ChromeLauncherControllerUserSwitchObserverChromeOS::UserAddedToSession(
284 const chromeos::User* active_user) {
285 Profile* profile = multi_user_util::GetProfileFromUserID(
286 active_user->email());
287 // If we do not have a profile yet, we postpone forwarding the notification
288 // until it is loaded.
289 if (!profile)
290 added_user_ids_waiting_for_profiles_.insert(active_user->email());
291 else
292 AddUser(profile);
295 void ChromeLauncherControllerUserSwitchObserverChromeOS::Observe(
296 int type,
297 const content::NotificationSource& source,
298 const content::NotificationDetails& details) {
299 if (type == chrome::NOTIFICATION_PROFILE_ADDED &&
300 !added_user_ids_waiting_for_profiles_.empty()) {
301 // Check if the profile is from a user which was on the waiting list.
302 Profile* profile = content::Source<Profile>(source).ptr();
303 std::string user_id = multi_user_util::GetUserIDFromProfile(profile);
304 std::set<std::string>::iterator it = std::find(
305 added_user_ids_waiting_for_profiles_.begin(),
306 added_user_ids_waiting_for_profiles_.end(),
307 user_id);
308 if (it != added_user_ids_waiting_for_profiles_.end()) {
309 added_user_ids_waiting_for_profiles_.erase(it);
310 AddUser(profile->GetOriginalProfile());
315 void ChromeLauncherControllerUserSwitchObserverChromeOS::AddUser(
316 Profile* profile) {
317 if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
318 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED)
319 chrome::MultiUserWindowManager::GetInstance()->AddUser(profile);
320 controller_->AdditionalUserAddedToSession(profile->GetOriginalProfile());
322 #endif
324 ChromeLauncherController::ChromeLauncherController(Profile* profile,
325 ash::ShelfModel* model)
326 : model_(model),
327 item_delegate_manager_(NULL),
328 profile_(profile),
329 app_sync_ui_state_(NULL),
330 ignore_persist_pinned_state_change_(false) {
331 if (!profile_) {
332 // If no profile was passed, we take the currently active profile and use it
333 // as the owner of the current desktop.
334 // Use the original profile as on chromeos we may get a temporary off the
335 // record profile, unless in guest session (where off the record profile is
336 // the right one).
337 Profile* active_profile = ProfileManager::GetActiveUserProfile();
338 profile_ = active_profile->IsGuestSession() ? active_profile :
339 active_profile->GetOriginalProfile();
341 app_sync_ui_state_ = AppSyncUIState::Get(profile_);
342 if (app_sync_ui_state_)
343 app_sync_ui_state_->AddObserver(this);
346 // All profile relevant settings get bound to the current profile.
347 AttachProfile(profile_);
348 model_->AddObserver(this);
350 // In multi profile mode we might have a window manager. We try to create it
351 // here. If the instantiation fails, the manager is not needed.
352 chrome::MultiUserWindowManager::CreateInstance();
354 #if defined(OS_CHROMEOS)
355 // On Chrome OS using multi profile we want to switch the content of the shelf
356 // with a user change. Note that for unit tests the instance can be NULL.
357 if (chrome::MultiUserWindowManager::GetMultiProfileMode() !=
358 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF) {
359 user_switch_observer_.reset(
360 new ChromeLauncherControllerUserSwitchObserverChromeOS(this));
363 // Create our v1/v2 application / browser monitors which will inform the
364 // launcher of status changes.
365 if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
366 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) {
367 // If running in separated destkop mode, we create the multi profile version
368 // of status monitor.
369 browser_status_monitor_.reset(new MultiProfileBrowserStatusMonitor(this));
370 shell_window_controller_.reset(
371 new MultiProfileShellWindowLauncherController(this));
372 } else {
373 // Create our v1/v2 application / browser monitors which will inform the
374 // launcher of status changes.
375 browser_status_monitor_.reset(new BrowserStatusMonitor(this));
376 shell_window_controller_.reset(new ShellWindowLauncherController(this));
378 #else
379 // Create our v1/v2 application / browser monitors which will inform the
380 // launcher of status changes.
381 browser_status_monitor_.reset(new BrowserStatusMonitor(this));
382 shell_window_controller_.reset(new ShellWindowLauncherController(this));
383 #endif
385 // Right now ash::Shell isn't created for tests.
386 // TODO(mukai): Allows it to observe display change and write tests.
387 if (ash::Shell::HasInstance()) {
388 ash::Shell::GetInstance()->display_controller()->AddObserver(this);
389 item_delegate_manager_ =
390 ash::Shell::GetInstance()->shelf_item_delegate_manager();
393 notification_registrar_.Add(this,
394 chrome::NOTIFICATION_EXTENSION_LOADED,
395 content::Source<Profile>(profile_));
396 notification_registrar_.Add(this,
397 chrome::NOTIFICATION_EXTENSION_UNLOADED,
398 content::Source<Profile>(profile_));
401 ChromeLauncherController::~ChromeLauncherController() {
402 // Reset the BrowserStatusMonitor as it has a weak pointer to this.
403 browser_status_monitor_.reset();
405 // Reset the shell window controller here since it has a weak pointer to this.
406 shell_window_controller_.reset();
408 for (std::set<ash::Shelf*>::iterator iter = shelves_.begin();
409 iter != shelves_.end();
410 ++iter)
411 (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this);
413 model_->RemoveObserver(this);
414 if (ash::Shell::HasInstance())
415 ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
416 for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
417 i != id_to_item_controller_map_.end(); ++i) {
418 int index = model_->ItemIndexByID(i->first);
419 // A "browser proxy" is not known to the model and this removal does
420 // therefore not need to be propagated to the model.
421 if (index != -1 &&
422 model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT)
423 model_->RemoveItemAt(index);
426 if (ash::Shell::HasInstance())
427 ash::Shell::GetInstance()->RemoveShellObserver(this);
429 // Release all profile dependent resources.
430 ReleaseProfile();
431 if (instance_ == this)
432 instance_ = NULL;
434 // Get rid of the multi user window manager instance.
435 chrome::MultiUserWindowManager::DeleteInstance();
438 // static
439 ChromeLauncherController* ChromeLauncherController::CreateInstance(
440 Profile* profile,
441 ash::ShelfModel* model) {
442 // We do not check here for re-creation of the ChromeLauncherController since
443 // it appears that it might be intentional that the ChromeLauncherController
444 // can be re-created.
445 instance_ = new ChromeLauncherController(profile, model);
446 return instance_;
449 void ChromeLauncherController::Init() {
450 CreateBrowserShortcutLauncherItem();
451 UpdateAppLaunchersFromPref();
453 // TODO(sky): update unit test so that this test isn't necessary.
454 if (ash::Shell::HasInstance()) {
455 SetShelfAutoHideBehaviorFromPrefs();
456 SetShelfAlignmentFromPrefs();
457 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
458 if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() ||
459 !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)->
460 HasUserSetting()) {
461 // This causes OnIsSyncingChanged to be called when the value of
462 // PrefService::IsSyncing() changes.
463 prefs->AddObserver(this);
465 ash::Shell::GetInstance()->AddShellObserver(this);
469 ash::LauncherID ChromeLauncherController::CreateAppLauncherItem(
470 LauncherItemController* controller,
471 const std::string& app_id,
472 ash::LauncherItemStatus status) {
473 CHECK(controller);
474 int index = 0;
475 // Panels are inserted on the left so as not to push all existing panels over.
476 if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL)
477 index = model_->item_count();
478 return InsertAppLauncherItem(controller,
479 app_id,
480 status,
481 index,
482 controller->GetLauncherItemType());
485 void ChromeLauncherController::SetItemStatus(
486 ash::LauncherID id,
487 ash::LauncherItemStatus status) {
488 int index = model_->ItemIndexByID(id);
489 ash::LauncherItemStatus old_status = model_->items()[index].status;
490 // Since ordinary browser windows are not registered, we might get a negative
491 // index here.
492 if (index >= 0 && old_status != status) {
493 ash::LauncherItem item = model_->items()[index];
494 item.status = status;
495 model_->Set(index, item);
499 void ChromeLauncherController::SetItemController(
500 ash::LauncherID id,
501 LauncherItemController* controller) {
502 CHECK(controller);
503 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
504 CHECK(iter != id_to_item_controller_map_.end());
505 controller->set_launcher_id(id);
506 iter->second = controller;
507 // Existing controller is destroyed and replaced by registering again.
508 SetShelfItemDelegate(id, controller);
511 void ChromeLauncherController::CloseLauncherItem(ash::LauncherID id) {
512 CHECK(id);
513 if (IsPinned(id)) {
514 // Create a new shortcut controller.
515 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
516 CHECK(iter != id_to_item_controller_map_.end());
517 SetItemStatus(id, ash::STATUS_CLOSED);
518 std::string app_id = iter->second->app_id();
519 iter->second = new AppShortcutLauncherItemController(app_id, this);
520 iter->second->set_launcher_id(id);
521 // Existing controller is destroyed and replaced by registering again.
522 SetShelfItemDelegate(id, iter->second);
523 } else {
524 LauncherItemClosed(id);
528 void ChromeLauncherController::Pin(ash::LauncherID id) {
529 DCHECK(HasItemController(id));
531 int index = model_->ItemIndexByID(id);
532 DCHECK_GE(index, 0);
534 ash::LauncherItem item = model_->items()[index];
536 if (item.type == ash::TYPE_PLATFORM_APP ||
537 item.type == ash::TYPE_WINDOWED_APP) {
538 item.type = ash::TYPE_APP_SHORTCUT;
539 model_->Set(index, item);
540 } else if (item.type != ash::TYPE_APP_SHORTCUT) {
541 return;
544 if (CanPin())
545 PersistPinnedState();
548 void ChromeLauncherController::Unpin(ash::LauncherID id) {
549 DCHECK(HasItemController(id));
551 LauncherItemController* controller = id_to_item_controller_map_[id];
552 if (controller->type() == LauncherItemController::TYPE_APP ||
553 controller->locked()) {
554 UnpinRunningAppInternal(model_->ItemIndexByID(id));
555 } else {
556 LauncherItemClosed(id);
558 if (CanPin())
559 PersistPinnedState();
562 bool ChromeLauncherController::IsPinned(ash::LauncherID id) {
563 int index = model_->ItemIndexByID(id);
564 if (index < 0)
565 return false;
566 ash::LauncherItemType type = model_->items()[index].type;
567 return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT);
570 void ChromeLauncherController::TogglePinned(ash::LauncherID id) {
571 if (!HasItemController(id))
572 return; // May happen if item closed with menu open.
574 if (IsPinned(id))
575 Unpin(id);
576 else
577 Pin(id);
580 bool ChromeLauncherController::IsPinnable(ash::LauncherID id) const {
581 int index = model_->ItemIndexByID(id);
582 if (index == -1)
583 return false;
585 ash::LauncherItemType type = model_->items()[index].type;
586 return ((type == ash::TYPE_APP_SHORTCUT ||
587 type == ash::TYPE_PLATFORM_APP ||
588 type == ash::TYPE_WINDOWED_APP) &&
589 CanPin());
592 void ChromeLauncherController::LockV1AppWithID(
593 const std::string& app_id) {
594 ash::LauncherID id = GetLauncherIDForAppID(app_id);
595 if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) {
596 CreateAppShortcutLauncherItemWithType(app_id,
597 model_->item_count(),
598 ash::TYPE_WINDOWED_APP);
599 id = GetLauncherIDForAppID(app_id);
601 CHECK(id);
602 id_to_item_controller_map_[id]->lock();
605 void ChromeLauncherController::UnlockV1AppWithID(
606 const std::string& app_id) {
607 ash::LauncherID id = GetLauncherIDForAppID(app_id);
608 CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id));
609 CHECK(id);
610 LauncherItemController* controller = id_to_item_controller_map_[id];
611 controller->unlock();
612 if (!controller->locked() && !IsPinned(id))
613 CloseLauncherItem(id);
616 void ChromeLauncherController::Launch(ash::LauncherID id,
617 int event_flags) {
618 if (!HasItemController(id))
619 return; // In case invoked from menu and item closed while menu up.
620 id_to_item_controller_map_[id]->Launch(ash::LAUNCH_FROM_UNKNOWN, event_flags);
623 void ChromeLauncherController::Close(ash::LauncherID id) {
624 if (!HasItemController(id))
625 return; // May happen if menu closed.
626 id_to_item_controller_map_[id]->Close();
629 bool ChromeLauncherController::IsOpen(ash::LauncherID id) {
630 if (!HasItemController(id))
631 return false;
632 return id_to_item_controller_map_[id]->IsOpen();
635 bool ChromeLauncherController::IsPlatformApp(ash::LauncherID id) {
636 if (!HasItemController(id))
637 return false;
639 std::string app_id = GetAppIDForLauncherID(id);
640 const Extension* extension = GetExtensionForAppID(app_id);
641 // An extension can be synced / updated at any time and therefore not be
642 // available.
643 return extension ? extension->is_platform_app() : false;
646 void ChromeLauncherController::LaunchApp(const std::string& app_id,
647 ash::LaunchSource source,
648 int event_flags) {
649 // |extension| could be NULL when it is being unloaded for updating.
650 const Extension* extension = GetExtensionForAppID(app_id);
651 if (!extension)
652 return;
654 const ExtensionService* service =
655 extensions::ExtensionSystem::Get(profile_)->extension_service();
656 if (!extension_util::IsAppLaunchableWithoutEnabling(app_id, service)) {
657 // Do nothing if there is already a running enable flow.
658 if (extension_enable_flow_)
659 return;
661 extension_enable_flow_.reset(
662 new ExtensionEnableFlow(profile_, app_id, this));
663 extension_enable_flow_->StartForNativeWindow(NULL);
664 return;
667 if (LaunchedInNativeDesktop(app_id))
668 return;
670 // The app will be created for the currently active profile.
671 AppLaunchParams params(profile_,
672 extension,
673 event_flags,
674 chrome::HOST_DESKTOP_TYPE_ASH);
675 if (source != ash::LAUNCH_FROM_UNKNOWN &&
676 app_id == extension_misc::kWebStoreAppId) {
677 // Get the corresponding source string.
678 std::string source_value = GetSourceFromAppListSource(source);
680 // Set an override URL to include the source.
681 GURL extension_url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
682 params.override_url = net::AppendQueryParameter(
683 extension_url, extension_urls::kWebstoreSourceField, source_value);
686 OpenApplication(params);
689 void ChromeLauncherController::ActivateApp(const std::string& app_id,
690 ash::LaunchSource source,
691 int event_flags) {
692 // If there is an existing non-shortcut controller for this app, open it.
693 ash::LauncherID id = GetLauncherIDForAppID(app_id);
694 if (id) {
695 LauncherItemController* controller = id_to_item_controller_map_[id];
696 controller->Activate(source);
697 return;
700 // Create a temporary application launcher item and use it to see if there are
701 // running instances.
702 scoped_ptr<AppShortcutLauncherItemController> app_controller(
703 new AppShortcutLauncherItemController(app_id, this));
704 if (!app_controller->GetRunningApplications().empty())
705 app_controller->Activate(source);
706 else
707 LaunchApp(app_id, source, event_flags);
710 extensions::LaunchType ChromeLauncherController::GetLaunchType(
711 ash::LauncherID id) {
712 DCHECK(HasItemController(id));
714 const Extension* extension = GetExtensionForAppID(
715 id_to_item_controller_map_[id]->app_id());
717 // An extension can be unloaded/updated/unavailable at any time.
718 if (!extension)
719 return extensions::LAUNCH_TYPE_DEFAULT;
721 return extensions::GetLaunchType(
722 profile_->GetExtensionService()->extension_prefs(),
723 extension);
726 ash::LauncherID ChromeLauncherController::GetLauncherIDForAppID(
727 const std::string& app_id) {
728 for (IDToItemControllerMap::const_iterator i =
729 id_to_item_controller_map_.begin();
730 i != id_to_item_controller_map_.end(); ++i) {
731 if (i->second->type() == LauncherItemController::TYPE_APP_PANEL)
732 continue; // Don't include panels
733 if (i->second->app_id() == app_id)
734 return i->first;
736 return 0;
739 const std::string& ChromeLauncherController::GetAppIDForLauncherID(
740 ash::LauncherID id) {
741 CHECK(HasItemController(id));
742 return id_to_item_controller_map_[id]->app_id();
745 void ChromeLauncherController::SetAppImage(const std::string& id,
746 const gfx::ImageSkia& image) {
747 // TODO: need to get this working for shortcuts.
748 for (IDToItemControllerMap::const_iterator i =
749 id_to_item_controller_map_.begin();
750 i != id_to_item_controller_map_.end(); ++i) {
751 LauncherItemController* controller = i->second;
752 if (controller->app_id() != id)
753 continue;
754 if (controller->image_set_by_controller())
755 continue;
756 int index = model_->ItemIndexByID(i->first);
757 if (index == -1)
758 continue;
759 ash::LauncherItem item = model_->items()[index];
760 item.image = image;
761 model_->Set(index, item);
762 // It's possible we're waiting on more than one item, so don't break.
766 void ChromeLauncherController::OnAutoHideBehaviorChanged(
767 aura::Window* root_window,
768 ash::ShelfAutoHideBehavior new_behavior) {
769 SetShelfAutoHideBehaviorPrefs(new_behavior, root_window);
772 void ChromeLauncherController::SetLauncherItemImage(
773 ash::LauncherID launcher_id,
774 const gfx::ImageSkia& image) {
775 int index = model_->ItemIndexByID(launcher_id);
776 if (index == -1)
777 return;
778 ash::LauncherItem item = model_->items()[index];
779 item.image = image;
780 model_->Set(index, item);
783 bool ChromeLauncherController::CanPin() const {
784 const PrefService::Preference* pref =
785 profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
786 return pref && pref->IsUserModifiable();
789 bool ChromeLauncherController::IsAppPinned(const std::string& app_id) {
790 for (IDToItemControllerMap::const_iterator i =
791 id_to_item_controller_map_.begin();
792 i != id_to_item_controller_map_.end(); ++i) {
793 if (IsPinned(i->first) && i->second->app_id() == app_id)
794 return true;
796 return false;
799 bool ChromeLauncherController::IsWindowedAppInLauncher(
800 const std::string& app_id) {
801 int index = model_->ItemIndexByID(GetLauncherIDForAppID(app_id));
802 if (index < 0)
803 return false;
805 ash::LauncherItemType type = model_->items()[index].type;
806 return type == ash::TYPE_WINDOWED_APP;
809 void ChromeLauncherController::PinAppWithID(const std::string& app_id) {
810 if (CanPin())
811 DoPinAppWithID(app_id);
812 else
813 NOTREACHED();
816 void ChromeLauncherController::SetLaunchType(
817 ash::LauncherID id,
818 extensions::LaunchType launch_type) {
819 if (!HasItemController(id))
820 return;
822 extensions::SetLaunchType(profile_->GetExtensionService(),
823 id_to_item_controller_map_[id]->app_id(),
824 launch_type);
827 void ChromeLauncherController::UnpinAppWithID(const std::string& app_id) {
828 if (CanPin())
829 DoUnpinAppWithID(app_id);
830 else
831 NOTREACHED();
834 bool ChromeLauncherController::IsLoggedInAsGuest() {
835 return profile_->IsGuestSession();
838 void ChromeLauncherController::CreateNewWindow() {
839 // Use the currently active user.
840 chrome::NewEmptyWindow(profile_, chrome::HOST_DESKTOP_TYPE_ASH);
843 void ChromeLauncherController::CreateNewIncognitoWindow() {
844 // Use the currently active user.
845 chrome::NewEmptyWindow(profile_->GetOffTheRecordProfile(),
846 chrome::HOST_DESKTOP_TYPE_ASH);
849 void ChromeLauncherController::PersistPinnedState() {
850 if (ignore_persist_pinned_state_change_)
851 return;
852 // It is a coding error to call PersistPinnedState() if the pinned apps are
853 // not user-editable. The code should check earlier and not perform any
854 // modification actions that trigger persisting the state.
855 if (!CanPin()) {
856 NOTREACHED() << "Can't pin but pinned state being updated";
857 return;
859 // Mutating kPinnedLauncherApps is going to notify us and trigger us to
860 // process the change. We don't want that to happen so remove ourselves as a
861 // listener.
862 pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
864 ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
865 updater->Clear();
866 for (size_t i = 0; i < model_->items().size(); ++i) {
867 if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
868 ash::LauncherID id = model_->items()[i].id;
869 if (HasItemController(id) && IsPinned(id)) {
870 base::DictionaryValue* app_value = ash::CreateAppDict(
871 id_to_item_controller_map_[id]->app_id());
872 if (app_value)
873 updater->Append(app_value);
875 } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) {
876 PersistChromeItemIndex(i);
877 } else if (model_->items()[i].type == ash::TYPE_APP_LIST) {
878 base::DictionaryValue* app_value = ash::CreateAppDict(
879 kAppLauncherIdPlaceholder);
880 if (app_value)
881 updater->Append(app_value);
885 pref_change_registrar_.Add(
886 prefs::kPinnedLauncherApps,
887 base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
888 base::Unretained(this)));
891 ash::ShelfModel* ChromeLauncherController::model() {
892 return model_;
895 Profile* ChromeLauncherController::profile() {
896 return profile_;
899 ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior(
900 aura::Window* root_window) const {
901 // Don't show the shelf in app mode.
902 if (chrome::IsRunningInAppMode())
903 return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
905 #if defined(OS_WIN)
906 // Autohide functionality temporarily disabled for windows due to
907 // issue crbug.com/292864.
908 // TODO(shrikant): Remove this once the issue gets resolved.
909 return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
910 #endif
912 // See comment in |kShelfAlignment| as to why we consider two prefs.
913 const std::string behavior_value(
914 GetPrefForRootWindow(profile_->GetPrefs(),
915 root_window,
916 prefs::kShelfAutoHideBehaviorLocal,
917 prefs::kShelfAutoHideBehavior));
919 // Note: To maintain sync compatibility with old images of chrome/chromeos
920 // the set of values that may be encountered includes the now-extinct
921 // "Default" as well as "Never" and "Always", "Default" should now
922 // be treated as "Never" (http://crbug.com/146773).
923 if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
924 return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
925 return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
928 bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior(
929 aura::Window* root_window) const {
930 #if defined(OS_WIN)
931 // Autohide functionality temporarily disabled for windows due to
932 // issue crbug.com/292864.
933 // TODO(shrikant): Remove this once the issue gets resolved.
934 return false;
935 #endif
936 return profile_->GetPrefs()->
937 FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable();
940 void ChromeLauncherController::ToggleShelfAutoHideBehavior(
941 aura::Window* root_window) {
942 ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) ==
943 ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
944 ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER :
945 ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
946 SetShelfAutoHideBehaviorPrefs(behavior, root_window);
947 return;
950 void ChromeLauncherController::RemoveTabFromRunningApp(
951 WebContents* tab,
952 const std::string& app_id) {
953 web_contents_to_app_id_.erase(tab);
954 // BrowserShortcutLauncherItemController::UpdateBrowserItemState() will update
955 // the state when no application is associated with the tab.
956 if (app_id.empty())
957 return;
959 AppIDToWebContentsListMap::iterator i_app_id =
960 app_id_to_web_contents_list_.find(app_id);
961 if (i_app_id != app_id_to_web_contents_list_.end()) {
962 WebContentsList* tab_list = &i_app_id->second;
963 tab_list->remove(tab);
964 ash::LauncherItemStatus status = ash::STATUS_RUNNING;
965 if (tab_list->empty()) {
966 app_id_to_web_contents_list_.erase(i_app_id);
967 status = ash::STATUS_CLOSED;
969 ash::LauncherID id = GetLauncherIDForAppID(app_id);
970 if (id)
971 SetItemStatus(id, status);
975 void ChromeLauncherController::UpdateAppState(content::WebContents* contents,
976 AppState app_state) {
977 std::string app_id = app_tab_helper_->GetAppID(contents);
979 // Check if the gMail app is loaded and it matches the given content.
980 // This special treatment is needed to address crbug.com/234268.
981 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents))
982 app_id = kGmailAppId;
984 // Check the old |app_id| for a tab. If the contents has changed we need to
985 // remove it from the previous app.
986 if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) {
987 std::string last_app_id = web_contents_to_app_id_[contents];
988 if (last_app_id != app_id)
989 RemoveTabFromRunningApp(contents, last_app_id);
992 web_contents_to_app_id_[contents] = app_id;
994 if (app_state == APP_STATE_REMOVED) {
995 // The tab has gone away.
996 RemoveTabFromRunningApp(contents, app_id);
997 } else if (!app_id.empty()) {
998 WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]);
999 WebContentsList::const_iterator i_tab =
1000 std::find(tab_list.begin(), tab_list.end(), contents);
1002 if (i_tab == tab_list.end())
1003 tab_list.push_back(contents);
1005 if (app_state == APP_STATE_INACTIVE || app_state == APP_STATE_ACTIVE) {
1006 if (i_tab != tab_list.begin()) {
1007 // Going to running state, but wasn't the front tab, indicating that a
1008 // new tab has already become active.
1009 return;
1013 if (app_state == APP_STATE_ACTIVE || app_state == APP_STATE_WINDOW_ACTIVE) {
1014 tab_list.remove(contents);
1015 tab_list.push_front(contents);
1018 ash::LauncherID id = GetLauncherIDForAppID(app_id);
1019 if (id) {
1020 // If the window is active, mark the app as active.
1021 SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
1022 ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
1027 ash::LauncherID ChromeLauncherController::GetLauncherIDForWebContents(
1028 content::WebContents* contents) {
1029 DCHECK(contents);
1031 std::string app_id = app_tab_helper_->GetAppID(contents);
1033 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents))
1034 app_id = kGmailAppId;
1036 ash::LauncherID id = GetLauncherIDForAppID(app_id);
1038 if (app_id.empty() || !id) {
1039 int browser_index = model_->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
1040 return model_->items()[browser_index].id;
1043 return id;
1046 void ChromeLauncherController::SetRefocusURLPatternForTest(ash::LauncherID id,
1047 const GURL& url) {
1048 DCHECK(HasItemController(id));
1049 LauncherItemController* controller = id_to_item_controller_map_[id];
1051 int index = model_->ItemIndexByID(id);
1052 if (index == -1) {
1053 NOTREACHED() << "Invalid launcher id";
1054 return;
1057 ash::LauncherItemType type = model_->items()[index].type;
1058 if (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_WINDOWED_APP) {
1059 AppShortcutLauncherItemController* app_controller =
1060 static_cast<AppShortcutLauncherItemController*>(controller);
1061 app_controller->set_refocus_url(url);
1062 } else {
1063 NOTREACHED() << "Invalid launcher type";
1067 const Extension* ChromeLauncherController::GetExtensionForAppID(
1068 const std::string& app_id) const {
1069 // Some unit tests do not have a real extension.
1070 return (profile_->GetExtensionService()) ?
1071 profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL;
1074 void ChromeLauncherController::ActivateWindowOrMinimizeIfActive(
1075 ui::BaseWindow* window,
1076 bool allow_minimize) {
1077 // In separated desktop mode we might have to teleport a window back to the
1078 // current user.
1079 if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
1080 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) {
1081 aura::Window* native_window = window->GetNativeWindow();
1082 const std::string& current_user =
1083 multi_user_util::GetUserIDFromProfile(profile());
1084 chrome::MultiUserWindowManager* manager =
1085 chrome::MultiUserWindowManager::GetInstance();
1086 if (!manager->IsWindowOnDesktopOfUser(native_window, current_user)) {
1087 ash::MultiProfileUMA::RecordTeleportAction(
1088 ash::MultiProfileUMA::TELEPORT_WINDOW_RETURN_BY_LAUNCHER);
1089 manager->ShowWindowForUser(native_window, current_user);
1090 window->Activate();
1091 return;
1095 if (window->IsActive() && allow_minimize) {
1096 if (CommandLine::ForCurrentProcess()->HasSwitch(
1097 switches::kDisableMinimizeOnSecondLauncherItemClick)) {
1098 AnimateWindow(window->GetNativeWindow(),
1099 views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
1100 } else {
1101 window->Minimize();
1103 } else {
1104 window->Show();
1105 window->Activate();
1109 void ChromeLauncherController::OnShelfCreated(ash::Shelf* shelf) {
1110 shelves_.insert(shelf);
1111 shelf->shelf_widget()->shelf_layout_manager()->AddObserver(this);
1114 void ChromeLauncherController::OnShelfDestroyed(ash::Shelf* shelf) {
1115 shelves_.erase(shelf);
1116 // RemoveObserver is not called here, since by the time this method is called
1117 // Shelf is already in its destructor.
1120 void ChromeLauncherController::ShelfItemAdded(int index) {
1121 // The app list launcher can get added to the shelf after we applied the
1122 // preferences. In that case the item might be at the wrong spot. As such we
1123 // call the function again.
1124 if (model_->items()[index].type == ash::TYPE_APP_LIST &&
1125 ash::switches::UseAlternateShelfLayout())
1126 UpdateAppLaunchersFromPref();
1129 void ChromeLauncherController::ShelfItemRemoved(int index, ash::LauncherID id) {
1132 void ChromeLauncherController::ShelfItemMoved(int start_index,
1133 int target_index) {
1134 const ash::LauncherItem& item = model_->items()[target_index];
1135 // We remember the moved item position if it is either pinnable or
1136 // it is the app list with the alternate shelf layout.
1137 if ((HasItemController(item.id) && IsPinned(item.id)) ||
1138 (ash::switches::UseAlternateShelfLayout() &&
1139 item.type == ash::TYPE_APP_LIST))
1140 PersistPinnedState();
1143 void ChromeLauncherController::ShelfItemChanged(
1144 int index,
1145 const ash::LauncherItem& old_item) {
1148 void ChromeLauncherController::ShelfStatusChanged() {
1151 void ChromeLauncherController::ActiveUserChanged(
1152 const std::string& user_email) {
1153 // Store the order of running applications for the user which gets inactive.
1154 RememberUnpinnedRunningApplicationOrder();
1155 // Coming here the default profile is already switched. All profile specific
1156 // resources get released and the new profile gets attached instead.
1157 ReleaseProfile();
1158 // When coming here, the active user has already be changed so that we can
1159 // set it as active.
1160 AttachProfile(ProfileManager::GetActiveUserProfile());
1161 // Update the V1 applications.
1162 browser_status_monitor_->ActiveUserChanged(user_email);
1163 // Switch the running applications to the new user.
1164 shell_window_controller_->ActiveUserChanged(user_email);
1165 // Update the user specific shell properties from the new user profile.
1166 UpdateAppLaunchersFromPref();
1167 SetShelfAlignmentFromPrefs();
1168 SetShelfAutoHideBehaviorFromPrefs();
1169 SetShelfBehaviorsFromPrefs();
1170 // Restore the order of running, but unpinned applications for the activated
1171 // user.
1172 RestoreUnpinnedRunningApplicationOrder(user_email);
1175 void ChromeLauncherController::AdditionalUserAddedToSession(Profile* profile) {
1176 // Switch the running applications to the new user.
1177 shell_window_controller_->AdditionalUserAddedToSession(profile);
1180 void ChromeLauncherController::Observe(
1181 int type,
1182 const content::NotificationSource& source,
1183 const content::NotificationDetails& details) {
1184 switch (type) {
1185 case chrome::NOTIFICATION_EXTENSION_LOADED: {
1186 const Extension* extension =
1187 content::Details<const Extension>(details).ptr();
1188 if (IsAppPinned(extension->id())) {
1189 // Clear and re-fetch to ensure icon is up-to-date.
1190 app_icon_loader_->ClearImage(extension->id());
1191 app_icon_loader_->FetchImage(extension->id());
1194 UpdateAppLaunchersFromPref();
1195 break;
1197 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
1198 const content::Details<UnloadedExtensionInfo>& unload_info(details);
1199 const Extension* extension = unload_info->extension;
1200 const std::string& id = extension->id();
1201 // Since we might have windowed apps of this type which might have
1202 // outstanding locks which needs to be removed.
1203 if (GetLauncherIDForAppID(id) &&
1204 unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) {
1205 CloseWindowedAppsFromRemovedExtension(id);
1208 if (IsAppPinned(id)) {
1209 if (unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) {
1210 DoUnpinAppWithID(id);
1211 app_icon_loader_->ClearImage(id);
1212 } else {
1213 app_icon_loader_->UpdateImage(id);
1216 break;
1218 default:
1219 NOTREACHED() << "Unexpected notification type=" << type;
1223 void ChromeLauncherController::OnShelfAlignmentChanged(
1224 aura::Window* root_window) {
1225 const char* pref_value = NULL;
1226 switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) {
1227 case ash::SHELF_ALIGNMENT_BOTTOM:
1228 pref_value = ash::kShelfAlignmentBottom;
1229 break;
1230 case ash::SHELF_ALIGNMENT_LEFT:
1231 pref_value = ash::kShelfAlignmentLeft;
1232 break;
1233 case ash::SHELF_ALIGNMENT_RIGHT:
1234 pref_value = ash::kShelfAlignmentRight;
1235 break;
1236 case ash::SHELF_ALIGNMENT_TOP:
1237 pref_value = ash::kShelfAlignmentTop;
1240 UpdatePerDisplayPref(
1241 profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value);
1243 if (root_window == ash::Shell::GetPrimaryRootWindow()) {
1244 // See comment in |kShelfAlignment| about why we have two prefs here.
1245 profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
1246 profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
1250 void ChromeLauncherController::OnDisplayConfigurationChanging() {
1253 void ChromeLauncherController::OnDisplayConfigurationChanged() {
1254 SetShelfBehaviorsFromPrefs();
1257 void ChromeLauncherController::OnIsSyncingChanged() {
1258 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
1259 MaybePropagatePrefToLocal(prefs,
1260 prefs::kShelfAlignmentLocal,
1261 prefs::kShelfAlignment);
1262 MaybePropagatePrefToLocal(prefs,
1263 prefs::kShelfAutoHideBehaviorLocal,
1264 prefs::kShelfAutoHideBehavior);
1267 void ChromeLauncherController::OnAppSyncUIStatusChanged() {
1268 if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
1269 model_->SetStatus(ash::ShelfModel::STATUS_LOADING);
1270 else
1271 model_->SetStatus(ash::ShelfModel::STATUS_NORMAL);
1274 void ChromeLauncherController::ExtensionEnableFlowFinished() {
1275 LaunchApp(extension_enable_flow_->extension_id(),
1276 ash::LAUNCH_FROM_UNKNOWN,
1277 ui::EF_NONE);
1278 extension_enable_flow_.reset();
1281 void ChromeLauncherController::ExtensionEnableFlowAborted(bool user_initiated) {
1282 extension_enable_flow_.reset();
1285 ChromeLauncherAppMenuItems ChromeLauncherController::GetApplicationList(
1286 const ash::LauncherItem& item,
1287 int event_flags) {
1288 // Make sure that there is a controller associated with the id and that the
1289 // extension itself is a valid application and not a panel.
1290 if (!HasItemController(item.id) ||
1291 !GetLauncherIDForAppID(id_to_item_controller_map_[item.id]->app_id()))
1292 return ChromeLauncherAppMenuItems().Pass();
1294 return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags);
1297 std::vector<content::WebContents*>
1298 ChromeLauncherController::GetV1ApplicationsFromAppId(std::string app_id) {
1299 ash::LauncherID id = GetLauncherIDForAppID(app_id);
1301 // If there is no such an item pinned to the launcher, no menu gets created.
1302 if (id) {
1303 LauncherItemController* controller = id_to_item_controller_map_[id];
1304 DCHECK(controller);
1305 if (controller->type() == LauncherItemController::TYPE_SHORTCUT)
1306 return GetV1ApplicationsFromController(controller);
1308 return std::vector<content::WebContents*>();
1311 void ChromeLauncherController::ActivateShellApp(const std::string& app_id,
1312 int index) {
1313 ash::LauncherID id = GetLauncherIDForAppID(app_id);
1314 if (id) {
1315 LauncherItemController* controller = id_to_item_controller_map_[id];
1316 if (controller->type() == LauncherItemController::TYPE_APP) {
1317 ShellWindowLauncherItemController* shell_window_controller =
1318 static_cast<ShellWindowLauncherItemController*>(controller);
1319 shell_window_controller->ActivateIndexedApp(index);
1324 bool ChromeLauncherController::IsWebContentHandledByApplication(
1325 content::WebContents* web_contents,
1326 const std::string& app_id) {
1327 if ((web_contents_to_app_id_.find(web_contents) !=
1328 web_contents_to_app_id_.end()) &&
1329 (web_contents_to_app_id_[web_contents] == app_id))
1330 return true;
1331 return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents));
1334 bool ChromeLauncherController::ContentCanBeHandledByGmailApp(
1335 content::WebContents* web_contents) {
1336 ash::LauncherID id = GetLauncherIDForAppID(kGmailAppId);
1337 if (id) {
1338 const GURL url = web_contents->GetURL();
1339 // We need to extend the application matching for the gMail app beyond the
1340 // manifest file's specification. This is required because of the namespace
1341 // overlap with the offline app ("/mail/mu/").
1342 if (!MatchPattern(url.path(), "/mail/mu/*") &&
1343 MatchPattern(url.path(), "/mail/*") &&
1344 GetExtensionForAppID(kGmailAppId) &&
1345 GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url))
1346 return true;
1348 return false;
1351 gfx::Image ChromeLauncherController::GetAppListIcon(
1352 content::WebContents* web_contents) const {
1353 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
1354 if (IsIncognito(web_contents))
1355 return rb.GetImageNamed(IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER);
1356 FaviconTabHelper* favicon_tab_helper =
1357 FaviconTabHelper::FromWebContents(web_contents);
1358 gfx::Image result = favicon_tab_helper->GetFavicon();
1359 if (result.IsEmpty())
1360 return rb.GetImageNamed(IDR_DEFAULT_FAVICON);
1361 return result;
1364 base::string16 ChromeLauncherController::GetAppListTitle(
1365 content::WebContents* web_contents) const {
1366 base::string16 title = web_contents->GetTitle();
1367 if (!title.empty())
1368 return title;
1369 WebContentsToAppIDMap::const_iterator iter =
1370 web_contents_to_app_id_.find(web_contents);
1371 if (iter != web_contents_to_app_id_.end()) {
1372 std::string app_id = iter->second;
1373 const extensions::Extension* extension = GetExtensionForAppID(app_id);
1374 if (extension)
1375 return base::UTF8ToUTF16(extension->name());
1377 return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
1380 ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItem(
1381 const std::string& app_id,
1382 int index) {
1383 return CreateAppShortcutLauncherItemWithType(app_id,
1384 index,
1385 ash::TYPE_APP_SHORTCUT);
1388 void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) {
1389 app_tab_helper_.reset(helper);
1392 void ChromeLauncherController::SetAppIconLoaderForTest(
1393 extensions::AppIconLoader* loader) {
1394 app_icon_loader_.reset(loader);
1397 const std::string& ChromeLauncherController::GetAppIdFromLauncherIdForTest(
1398 ash::LauncherID id) {
1399 return id_to_item_controller_map_[id]->app_id();
1402 void ChromeLauncherController::SetShelfItemDelegateManagerForTest(
1403 ash::ShelfItemDelegateManager* manager) {
1404 item_delegate_manager_ = manager;
1407 void ChromeLauncherController::RememberUnpinnedRunningApplicationOrder() {
1408 RunningAppListIds list;
1409 for (int i = 0; i < model_->item_count(); i++) {
1410 ash::LauncherItemType type = model_->items()[i].type;
1411 if (type == ash::TYPE_WINDOWED_APP || type == ash::TYPE_PLATFORM_APP)
1412 list.push_back(GetAppIDForLauncherID(model_->items()[i].id));
1414 last_used_running_application_order_[
1415 multi_user_util::GetUserIDFromProfile(profile_)] = list;
1418 void ChromeLauncherController::RestoreUnpinnedRunningApplicationOrder(
1419 const std::string& user_id) {
1420 const RunningAppListIdMap::iterator app_id_list =
1421 last_used_running_application_order_.find(user_id);
1422 if (app_id_list == last_used_running_application_order_.end())
1423 return;
1425 // Find the first insertion point for running applications.
1426 int running_index = model_->FirstRunningAppIndex();
1427 for (RunningAppListIds::iterator app_id = app_id_list->second.begin();
1428 app_id != app_id_list->second.end(); ++app_id) {
1429 ash::LauncherID launcher_id = GetLauncherIDForAppID(*app_id);
1430 if (launcher_id) {
1431 int app_index = model_->ItemIndexByID(launcher_id);
1432 DCHECK_GE(app_index, 0);
1433 ash::LauncherItemType type = model_->items()[app_index].type;
1434 if (type == ash::TYPE_WINDOWED_APP || type == ash::TYPE_PLATFORM_APP) {
1435 if (running_index != app_index)
1436 model_->Move(running_index, app_index);
1437 running_index++;
1443 ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItemWithType(
1444 const std::string& app_id,
1445 int index,
1446 ash::LauncherItemType launcher_item_type) {
1447 AppShortcutLauncherItemController* controller =
1448 new AppShortcutLauncherItemController(app_id, this);
1449 ash::LauncherID launcher_id = InsertAppLauncherItem(
1450 controller, app_id, ash::STATUS_CLOSED, index, launcher_item_type);
1451 return launcher_id;
1454 LauncherItemController* ChromeLauncherController::GetLauncherItemController(
1455 const ash::LauncherID id) {
1456 if (!HasItemController(id))
1457 return NULL;
1458 return id_to_item_controller_map_[id];
1461 bool ChromeLauncherController::IsBrowserFromActiveUser(Browser* browser) {
1462 // If running multi user mode with separate desktops, we have to check if the
1463 // browser is from the active user.
1464 if (chrome::MultiUserWindowManager::GetMultiProfileMode() !=
1465 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED)
1466 return true;
1467 return multi_user_util::IsProfileFromActiveUser(browser->profile());
1470 void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) {
1471 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
1472 CHECK(iter != id_to_item_controller_map_.end());
1473 CHECK(iter->second);
1474 app_icon_loader_->ClearImage(iter->second->app_id());
1475 id_to_item_controller_map_.erase(iter);
1476 int index = model_->ItemIndexByID(id);
1477 // A "browser proxy" is not known to the model and this removal does
1478 // therefore not need to be propagated to the model.
1479 if (index != -1)
1480 model_->RemoveItemAt(index);
1483 void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) {
1484 // If there is an item, do nothing and return.
1485 if (IsAppPinned(app_id))
1486 return;
1488 ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
1489 if (launcher_id) {
1490 // App item exists, pin it
1491 Pin(launcher_id);
1492 } else {
1493 // Otherwise, create a shortcut item for it.
1494 CreateAppShortcutLauncherItem(app_id, model_->item_count());
1495 if (CanPin())
1496 PersistPinnedState();
1500 void ChromeLauncherController::DoUnpinAppWithID(const std::string& app_id) {
1501 ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
1502 if (launcher_id && IsPinned(launcher_id))
1503 Unpin(launcher_id);
1506 int ChromeLauncherController::PinRunningAppInternal(
1507 int index,
1508 ash::LauncherID launcher_id) {
1509 int running_index = model_->ItemIndexByID(launcher_id);
1510 ash::LauncherItem item = model_->items()[running_index];
1511 DCHECK(item.type == ash::TYPE_WINDOWED_APP ||
1512 item.type == ash::TYPE_PLATFORM_APP);
1513 item.type = ash::TYPE_APP_SHORTCUT;
1514 model_->Set(running_index, item);
1515 // The |ShelfModel|'s weight system might reposition the item to a
1516 // new index, so we get the index again.
1517 running_index = model_->ItemIndexByID(launcher_id);
1518 if (running_index < index)
1519 --index;
1520 if (running_index != index)
1521 model_->Move(running_index, index);
1522 return index;
1525 void ChromeLauncherController::UnpinRunningAppInternal(int index) {
1526 DCHECK_GE(index, 0);
1527 ash::LauncherItem item = model_->items()[index];
1528 DCHECK_EQ(item.type, ash::TYPE_APP_SHORTCUT);
1529 item.type = ash::TYPE_WINDOWED_APP;
1530 // A platform app and a windowed app are sharing TYPE_APP_SHORTCUT. As such
1531 // we have to check here what this was before it got a shortcut.
1532 if (HasItemController(item.id) &&
1533 id_to_item_controller_map_[item.id]->type() ==
1534 LauncherItemController::TYPE_APP)
1535 item.type = ash::TYPE_PLATFORM_APP;
1536 model_->Set(index, item);
1539 void ChromeLauncherController::UpdateAppLaunchersFromPref() {
1540 // There are various functions which will trigger a |PersistPinnedState| call
1541 // like a direct call to |DoPinAppWithID|, or an indirect call to the menu
1542 // model which will use weights to re-arrange the icons to new positions.
1543 // Since this function is meant to synchronize the "is state" with the
1544 // "sync state", it makes no sense to store any changes by this function back
1545 // into the pref state. Therefore we tell |persistPinnedState| to ignore any
1546 // invocations while we are running.
1547 base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true);
1548 std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser();
1550 int index = 0;
1551 int max_index = model_->item_count();
1553 // When one of the two special items cannot be moved (and we do not know where
1554 // yet), we remember the current location in one of these variables.
1555 int chrome_index = -1;
1556 int app_list_index = -1;
1558 // Walk the model and |pinned_apps| from the pref lockstep, adding and
1559 // removing items as necessary. NB: This code uses plain old indexing instead
1560 // of iterators because of model mutations as part of the loop.
1561 std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
1562 for (; index < max_index && pref_app_id != pinned_apps.end(); ++index) {
1563 // Check if we have an item which we need to handle.
1564 if (*pref_app_id == extension_misc::kChromeAppId ||
1565 *pref_app_id == kAppLauncherIdPlaceholder ||
1566 IsAppPinned(*pref_app_id)) {
1567 for (; index < max_index; ++index) {
1568 const ash::LauncherItem& item(model_->items()[index]);
1569 bool is_app_list = item.type == ash::TYPE_APP_LIST;
1570 bool is_chrome = item.type == ash::TYPE_BROWSER_SHORTCUT;
1571 if (item.type != ash::TYPE_APP_SHORTCUT && !is_app_list && !is_chrome)
1572 continue;
1573 IDToItemControllerMap::const_iterator entry =
1574 id_to_item_controller_map_.find(item.id);
1575 if ((kAppLauncherIdPlaceholder == *pref_app_id && is_app_list) ||
1576 (extension_misc::kChromeAppId == *pref_app_id && is_chrome) ||
1577 (entry != id_to_item_controller_map_.end() &&
1578 entry->second->app_id() == *pref_app_id)) {
1579 // Check if an item needs to be moved here.
1580 MoveChromeOrApplistToFinalPosition(
1581 is_chrome, is_app_list, index, &chrome_index, &app_list_index);
1582 ++pref_app_id;
1583 break;
1584 } else {
1585 if (is_chrome || is_app_list) {
1586 // We cannot delete any of these shortcuts. As such we remember
1587 // their positions and move them later where they belong.
1588 if (is_chrome)
1589 chrome_index = index;
1590 else
1591 app_list_index = index;
1592 // And skip the item - or exit the loop if end is reached (note that
1593 // in that case we will reduce the index again by one and this only
1594 // compensates for it).
1595 if (index >= max_index - 1)
1596 break;
1597 ++index;
1598 } else {
1599 // Check if this is a platform or a windowed app.
1600 if (item.type == ash::TYPE_APP_SHORTCUT &&
1601 (id_to_item_controller_map_[item.id]->locked() ||
1602 id_to_item_controller_map_[item.id]->type() ==
1603 LauncherItemController::TYPE_APP)) {
1604 // Note: This will not change the amount of items (|max_index|).
1605 // Even changes to the actual |index| due to item weighting
1606 // changes should be fine.
1607 UnpinRunningAppInternal(index);
1608 } else {
1609 LauncherItemClosed(item.id);
1610 --max_index;
1613 --index;
1616 // If the item wasn't found, that means id_to_item_controller_map_
1617 // is out of sync.
1618 DCHECK(index <= max_index);
1619 } else {
1620 // Check if the item was already running but not yet pinned.
1621 ash::LauncherID launcher_id = GetLauncherIDForAppID(*pref_app_id);
1622 if (launcher_id) {
1623 // This app is running but not yet pinned. So pin and move it.
1624 index = PinRunningAppInternal(index, launcher_id);
1625 } else {
1626 // This app wasn't pinned before, insert a new entry.
1627 launcher_id = CreateAppShortcutLauncherItem(*pref_app_id, index);
1628 ++max_index;
1629 index = model_->ItemIndexByID(launcher_id);
1631 ++pref_app_id;
1635 // Remove any trailing existing items.
1636 while (index < model_->item_count()) {
1637 const ash::LauncherItem& item(model_->items()[index]);
1638 if (item.type == ash::TYPE_APP_SHORTCUT) {
1639 if (id_to_item_controller_map_[item.id]->locked() ||
1640 id_to_item_controller_map_[item.id]->type() ==
1641 LauncherItemController::TYPE_APP)
1642 UnpinRunningAppInternal(index);
1643 else
1644 LauncherItemClosed(item.id);
1645 } else {
1646 if (item.type == ash::TYPE_BROWSER_SHORTCUT)
1647 chrome_index = index;
1648 else if (item.type == ash::TYPE_APP_LIST)
1649 app_list_index = index;
1650 ++index;
1654 // Append unprocessed items from the pref to the end of the model.
1655 for (; pref_app_id != pinned_apps.end(); ++pref_app_id) {
1656 // All items but the chrome and / or app list shortcut needs to be added.
1657 bool is_chrome = *pref_app_id == extension_misc::kChromeAppId;
1658 bool is_app_list = *pref_app_id == kAppLauncherIdPlaceholder;
1659 // Coming here we know the next item which can be finalized, either the
1660 // chrome item or the app launcher. The final position is the end of the
1661 // list. The menu model will make sure that the item is grouped according
1662 // to its weight (which we do not know here).
1663 if (!is_chrome && !is_app_list) {
1664 DoPinAppWithID(*pref_app_id);
1665 int target_index = FindInsertionPoint(false);
1666 ash::LauncherID id = GetLauncherIDForAppID(*pref_app_id);
1667 int source_index = model_->ItemIndexByID(id);
1668 if (source_index != target_index)
1669 model_->Move(source_index, target_index);
1671 // Needed for the old layout - the weight might force it to be lower in
1672 // rank.
1673 if (app_list_index != -1 && target_index <= app_list_index)
1674 ++app_list_index;
1675 } else {
1676 int target_index = FindInsertionPoint(is_app_list);
1677 MoveChromeOrApplistToFinalPosition(
1678 is_chrome, is_app_list, target_index, &chrome_index, &app_list_index);
1683 void ChromeLauncherController::SetShelfAutoHideBehaviorPrefs(
1684 ash::ShelfAutoHideBehavior behavior,
1685 aura::Window* root_window) {
1686 const char* value = NULL;
1687 switch (behavior) {
1688 case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
1689 value = ash::kShelfAutoHideBehaviorAlways;
1690 break;
1691 case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
1692 value = ash::kShelfAutoHideBehaviorNever;
1693 break;
1694 case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
1695 // This one should not be a valid preference option for now. We only want
1696 // to completely hide it when we run app mode.
1697 NOTREACHED();
1698 return;
1701 UpdatePerDisplayPref(
1702 profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value);
1704 if (root_window == ash::Shell::GetPrimaryRootWindow()) {
1705 // See comment in |kShelfAlignment| about why we have two prefs here.
1706 profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
1707 profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
1711 void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() {
1712 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1714 for (aura::Window::Windows::const_iterator iter = root_windows.begin();
1715 iter != root_windows.end(); ++iter) {
1716 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
1717 GetShelfAutoHideBehavior(*iter), *iter);
1721 void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
1722 if (!ash::ShelfWidget::ShelfAlignmentAllowed())
1723 return;
1725 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1727 for (aura::Window::Windows::const_iterator iter = root_windows.begin();
1728 iter != root_windows.end(); ++iter) {
1729 // See comment in |kShelfAlignment| as to why we consider two prefs.
1730 const std::string alignment_value(
1731 GetPrefForRootWindow(profile_->GetPrefs(),
1732 *iter,
1733 prefs::kShelfAlignmentLocal,
1734 prefs::kShelfAlignment));
1735 ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
1736 if (alignment_value == ash::kShelfAlignmentLeft)
1737 alignment = ash::SHELF_ALIGNMENT_LEFT;
1738 else if (alignment_value == ash::kShelfAlignmentRight)
1739 alignment = ash::SHELF_ALIGNMENT_RIGHT;
1740 else if (alignment_value == ash::kShelfAlignmentTop)
1741 alignment = ash::SHELF_ALIGNMENT_TOP;
1742 ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter);
1746 void ChromeLauncherController::SetShelfBehaviorsFromPrefs() {
1747 SetShelfAutoHideBehaviorFromPrefs();
1748 SetShelfAlignmentFromPrefs();
1751 WebContents* ChromeLauncherController::GetLastActiveWebContents(
1752 const std::string& app_id) {
1753 AppIDToWebContentsListMap::const_iterator i =
1754 app_id_to_web_contents_list_.find(app_id);
1755 if (i == app_id_to_web_contents_list_.end())
1756 return NULL;
1757 DCHECK_GT(i->second.size(), 0u);
1758 return *i->second.begin();
1761 ash::LauncherID ChromeLauncherController::InsertAppLauncherItem(
1762 LauncherItemController* controller,
1763 const std::string& app_id,
1764 ash::LauncherItemStatus status,
1765 int index,
1766 ash::LauncherItemType launcher_item_type) {
1767 ash::LauncherID id = model_->next_id();
1768 CHECK(!HasItemController(id));
1769 CHECK(controller);
1770 id_to_item_controller_map_[id] = controller;
1771 controller->set_launcher_id(id);
1773 ash::LauncherItem item;
1774 item.type = launcher_item_type;
1775 item.image = extensions::IconsInfo::GetDefaultAppIcon();
1777 WebContents* active_tab = GetLastActiveWebContents(app_id);
1778 if (active_tab) {
1779 Browser* browser = chrome::FindBrowserWithWebContents(active_tab);
1780 DCHECK(browser);
1781 if (browser->window()->IsActive())
1782 status = ash::STATUS_ACTIVE;
1783 else
1784 status = ash::STATUS_RUNNING;
1786 item.status = status;
1788 model_->AddAt(index, item);
1790 app_icon_loader_->FetchImage(app_id);
1792 SetShelfItemDelegate(id, controller);
1794 return id;
1797 bool ChromeLauncherController::HasItemController(ash::LauncherID id) const {
1798 return id_to_item_controller_map_.find(id) !=
1799 id_to_item_controller_map_.end();
1802 std::vector<content::WebContents*>
1803 ChromeLauncherController::GetV1ApplicationsFromController(
1804 LauncherItemController* controller) {
1805 DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT);
1806 AppShortcutLauncherItemController* app_controller =
1807 static_cast<AppShortcutLauncherItemController*>(controller);
1808 return app_controller->GetRunningApplications();
1811 BrowserShortcutLauncherItemController*
1812 ChromeLauncherController::GetBrowserShortcutLauncherItemController() {
1813 for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
1814 i != id_to_item_controller_map_.end(); ++i) {
1815 int index = model_->ItemIndexByID(i->first);
1816 const ash::LauncherItem& item = model_->items()[index];
1817 if (item.type == ash::TYPE_BROWSER_SHORTCUT)
1818 return static_cast<BrowserShortcutLauncherItemController*>(i->second);
1820 // Create a LauncherItemController for the Browser shortcut if it does not
1821 // exist yet.
1822 ash::LauncherID id = CreateBrowserShortcutLauncherItem();
1823 DCHECK(id_to_item_controller_map_[id]);
1824 return static_cast<BrowserShortcutLauncherItemController*>(
1825 id_to_item_controller_map_[id]);
1828 ash::LauncherID ChromeLauncherController::CreateBrowserShortcutLauncherItem() {
1829 ash::LauncherItem browser_shortcut;
1830 browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT;
1831 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
1832 browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32);
1833 ash::LauncherID id = model_->next_id();
1834 size_t index = GetChromeIconIndexForCreation();
1835 model_->AddAt(index, browser_shortcut);
1836 id_to_item_controller_map_[id] =
1837 new BrowserShortcutLauncherItemController(this);
1838 id_to_item_controller_map_[id]->set_launcher_id(id);
1839 // ShelfItemDelegateManager owns BrowserShortcutLauncherItemController.
1840 SetShelfItemDelegate(id, id_to_item_controller_map_[id]);
1841 return id;
1844 void ChromeLauncherController::PersistChromeItemIndex(int index) {
1845 profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index);
1848 int ChromeLauncherController::GetChromeIconIndexFromPref() const {
1849 size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex);
1850 const base::ListValue* pinned_apps_pref =
1851 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
1852 return std::max(static_cast<size_t>(0),
1853 std::min(pinned_apps_pref->GetSize(), index));
1856 void ChromeLauncherController::MoveChromeOrApplistToFinalPosition(
1857 bool is_chrome,
1858 bool is_app_list,
1859 int target_index,
1860 int* chrome_index,
1861 int* app_list_index) {
1862 if (is_chrome && *chrome_index != -1) {
1863 model_->Move(*chrome_index, target_index);
1864 if (*app_list_index != -1 &&
1865 *chrome_index < *app_list_index &&
1866 target_index > *app_list_index)
1867 --(*app_list_index);
1868 *chrome_index = -1;
1869 } else if (is_app_list && *app_list_index != -1) {
1870 model_->Move(*app_list_index, target_index);
1871 if (*chrome_index != -1 &&
1872 *app_list_index < *chrome_index &&
1873 target_index > *chrome_index)
1874 --(*chrome_index);
1875 *app_list_index = -1;
1879 int ChromeLauncherController::FindInsertionPoint(bool is_app_list) {
1880 bool alternate = ash::switches::UseAlternateShelfLayout();
1881 // Keeping this change small to backport to M33&32 (see crbug.com/329597).
1882 // TODO(skuhne): With the removal of the legacy shelf layout we should remove
1883 // the ability to move the app list item since this was never used. We should
1884 // instead ask the ShelfModel::ValidateInsertionIndex or similir for an index.
1885 if (is_app_list && alternate)
1886 return 0;
1888 for (int i = model_->item_count() - 1; i > 0; --i) {
1889 ash::LauncherItemType type = model_->items()[i].type;
1890 if (type == ash::TYPE_APP_SHORTCUT ||
1891 ((is_app_list || alternate) && type == ash::TYPE_APP_LIST) ||
1892 type == ash::TYPE_BROWSER_SHORTCUT ||
1893 type == ash::TYPE_WINDOWED_APP)
1894 return i;
1896 return 0;
1899 int ChromeLauncherController::GetChromeIconIndexForCreation() {
1900 // We get the list of pinned apps as they currently would get pinned.
1901 // Within this list the chrome icon will be the correct location.
1902 std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser();
1904 std::vector<std::string>::iterator it =
1905 std::find(pinned_apps.begin(),
1906 pinned_apps.end(),
1907 std::string(extension_misc::kChromeAppId));
1908 DCHECK(it != pinned_apps.end());
1909 int index = it - pinned_apps.begin();
1911 // We should do here a comparison between the is state and the "want to be"
1912 // state since some apps might be able to pin but are not yet. Instead - for
1913 // the time being we clamp against the amount of known items and wait for the
1914 // next |UpdateAppLaunchersFromPref()| call to correct it - it will come since
1915 // the pinning will be done then.
1916 return std::min(model_->item_count(), index);
1919 std::vector<std::string>
1920 ChromeLauncherController::GetListOfPinnedAppsAndBrowser() {
1921 // Adding the app list item to the list of items requires that the ID is not
1922 // a valid and known ID for the extension system. The ID was constructed that
1923 // way - but just to make sure...
1924 DCHECK(!app_tab_helper_->IsValidIDForCurrentUser(kAppLauncherIdPlaceholder));
1926 std::vector<std::string> pinned_apps;
1928 // Get the new incarnation of the list.
1929 const base::ListValue* pinned_apps_pref =
1930 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
1932 // Keep track of the addition of the chrome and the app list icon.
1933 bool chrome_icon_added = false;
1934 bool app_list_icon_added = false;
1935 size_t chrome_icon_index = GetChromeIconIndexFromPref();
1937 // See if the chrome string is already in the pinned list and remove it if
1938 // needed.
1939 base::Value* chrome_app = ash::CreateAppDict(extension_misc::kChromeAppId);
1940 if (chrome_app) {
1941 chrome_icon_added = pinned_apps_pref->Find(*chrome_app) !=
1942 pinned_apps_pref->end();
1943 delete chrome_app;
1946 for (size_t index = 0; index < pinned_apps_pref->GetSize(); ++index) {
1947 // We need to position the chrome icon relative to it's place in the pinned
1948 // preference list - even if an item of that list isn't shown yet.
1949 if (index == chrome_icon_index && !chrome_icon_added) {
1950 pinned_apps.push_back(extension_misc::kChromeAppId);
1951 chrome_icon_added = true;
1953 const base::DictionaryValue* app = NULL;
1954 std::string app_id;
1955 if (pinned_apps_pref->GetDictionary(index, &app) &&
1956 app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
1957 (std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
1958 pinned_apps.end())) {
1959 if (app_id == extension_misc::kChromeAppId) {
1960 chrome_icon_added = true;
1961 pinned_apps.push_back(extension_misc::kChromeAppId);
1962 } else if (app_id == kAppLauncherIdPlaceholder) {
1963 app_list_icon_added = true;
1964 pinned_apps.push_back(kAppLauncherIdPlaceholder);
1965 } else if (app_tab_helper_->IsValidIDForCurrentUser(app_id)) {
1966 // Note: In multi profile scenarios we only want to show pinnable apps
1967 // here which is correct. Running applications from the other users will
1968 // continue to run. So no need for multi profile modifications.
1969 pinned_apps.push_back(app_id);
1974 // If not added yet, the chrome item will be the last item in the list.
1975 if (!chrome_icon_added)
1976 pinned_apps.push_back(extension_misc::kChromeAppId);
1978 // If not added yet, add the app list item either at the end or at the
1979 // beginning - depending on the shelf layout.
1980 if (!app_list_icon_added) {
1981 if (ash::switches::UseAlternateShelfLayout())
1982 pinned_apps.insert(pinned_apps.begin(), kAppLauncherIdPlaceholder);
1983 else
1984 pinned_apps.push_back(kAppLauncherIdPlaceholder);
1986 return pinned_apps;
1989 bool ChromeLauncherController::IsIncognito(
1990 const content::WebContents* web_contents) const {
1991 const Profile* profile =
1992 Profile::FromBrowserContext(web_contents->GetBrowserContext());
1993 return profile->IsOffTheRecord() && !profile->IsGuestSession();
1996 void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension(
1997 const std::string& app_id) {
1998 // This function cannot rely on the controller's enumeration functionality
1999 // since the extension has already be unloaded.
2000 const BrowserList* ash_browser_list =
2001 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
2002 std::vector<Browser*> browser_to_close;
2003 for (BrowserList::const_reverse_iterator
2004 it = ash_browser_list->begin_last_active();
2005 it != ash_browser_list->end_last_active(); ++it) {
2006 Browser* browser = *it;
2007 if (!browser->is_type_tabbed() &&
2008 browser->is_type_popup() &&
2009 browser->is_app() &&
2010 app_id == web_app::GetExtensionIdFromApplicationName(
2011 browser->app_name())) {
2012 browser_to_close.push_back(browser);
2015 while (!browser_to_close.empty()) {
2016 TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model();
2017 tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE);
2018 browser_to_close.pop_back();
2022 void ChromeLauncherController::SetShelfItemDelegate(
2023 ash::LauncherID id,
2024 ash::ShelfItemDelegate* item_delegate) {
2025 DCHECK_GT(id, 0);
2026 DCHECK(item_delegate);
2027 DCHECK(item_delegate_manager_);
2028 item_delegate_manager_->SetShelfItemDelegate(
2029 id, scoped_ptr<ash::ShelfItemDelegate>(item_delegate).Pass());
2032 void ChromeLauncherController::AttachProfile(Profile* profile) {
2033 profile_ = profile;
2034 // Either add the profile to the list of known profiles and make it the active
2035 // one for some functions of AppTabHelper or create a new one.
2036 if (!app_tab_helper_.get())
2037 app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
2038 else
2039 app_tab_helper_->SetCurrentUser(profile_);
2040 // TODO(skuhne): The AppIconLoaderImpl has the same problem. Each loaded
2041 // image is associated with a profile (it's loader requires the profile).
2042 // Since icon size changes are possible, the icon could be requested to be
2043 // reloaded. However - having it not multi profile aware would cause problems
2044 // if the icon cache gets deleted upon user switch.
2045 app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
2046 profile_, extension_misc::EXTENSION_ICON_SMALL, this));
2048 pref_change_registrar_.Init(profile_->GetPrefs());
2049 pref_change_registrar_.Add(
2050 prefs::kPinnedLauncherApps,
2051 base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
2052 base::Unretained(this)));
2053 pref_change_registrar_.Add(
2054 prefs::kShelfAlignmentLocal,
2055 base::Bind(&ChromeLauncherController::SetShelfAlignmentFromPrefs,
2056 base::Unretained(this)));
2057 pref_change_registrar_.Add(
2058 prefs::kShelfAutoHideBehaviorLocal,
2059 base::Bind(&ChromeLauncherController::
2060 SetShelfAutoHideBehaviorFromPrefs,
2061 base::Unretained(this)));
2062 pref_change_registrar_.Add(
2063 prefs::kShelfPreferences,
2064 base::Bind(&ChromeLauncherController::SetShelfBehaviorsFromPrefs,
2065 base::Unretained(this)));
2068 void ChromeLauncherController::ReleaseProfile() {
2069 if (app_sync_ui_state_)
2070 app_sync_ui_state_->RemoveObserver(this);
2072 PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
2074 pref_change_registrar_.RemoveAll();