Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / ash / launcher / chrome_launcher_controller.cc
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.
4
5 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
6
7 #include <vector>
8
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/system/tray/system_tray_delegate.h"
20 #include "ash/wm/window_util.h"
21 #include "base/command_line.h"
22 #include "base/prefs/scoped_user_pref_update.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/values.h"
26 #include "chrome/browser/app_mode/app_mode_utils.h"
27 #include "chrome/browser/chrome_notification_types.h"
28 #include "chrome/browser/defaults.h"
29 #include "chrome/browser/extensions/app_icon_loader_impl.h"
30 #include "chrome/browser/extensions/extension_service.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/app_window_launcher_controller.h"
42 #include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
43 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
44 #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h"
45 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
46 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h"
47 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
48 #include "chrome/browser/ui/ash/launcher/chrome_launcher_types.h"
49 #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
50 #include "chrome/browser/ui/ash/launcher/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/pref_names.h"
67 #include "chrome/common/url_constants.h"
68 #include "content/public/browser/navigation_entry.h"
69 #include "content/public/browser/notification_registrar.h"
70 #include "content/public/browser/notification_service.h"
71 #include "content/public/browser/web_contents.h"
72 #include "extensions/browser/extension_prefs.h"
73 #include "extensions/browser/extension_system.h"
74 #include "extensions/common/extension.h"
75 #include "extensions/common/extension_resource.h"
76 #include "extensions/common/manifest_handlers/icons_handler.h"
77 #include "extensions/common/url_pattern.h"
78 #include "grit/ash_resources.h"
79 #include "grit/chromium_strings.h"
80 #include "grit/generated_resources.h"
81 #include "grit/theme_resources.h"
82 #include "grit/ui_resources.h"
83 #include "net/base/url_util.h"
84 #include "ui/aura/window.h"
85 #include "ui/aura/window_event_dispatcher.h"
86 #include "ui/base/l10n/l10n_util.h"
87 #include "ui/keyboard/keyboard_util.h"
88 #include "ui/wm/core/window_animations.h"
89
90 #if defined(OS_CHROMEOS)
91 #include "chrome/browser/browser_process.h"
92 #include "chrome/browser/chromeos/login/user_manager.h"
93 #include "chrome/browser/ui/ash/chrome_shell_delegate.h"
94 #include "chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h"
95 #include "chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.h"
96 #endif
97
98 using extensions::Extension;
99 using extensions::UnloadedExtensionInfo;
100 using extension_misc::kGmailAppId;
101 using content::WebContents;
102
103 // static
104 ChromeLauncherController* ChromeLauncherController::instance_ = NULL;
105
106 namespace {
107
108 // This will be used as placeholder in the list of the pinned applciatons.
109 // Note that this is NOT a valid extension identifier so that pre M31 versions
110 // will ignore it.
111 const char kAppShelfIdPlaceholder[] = "AppShelfIDPlaceholder--------";
112
113 std::string GetPrefKeyForRootWindow(aura::Window* root_window) {
114   gfx::Display display = gfx::Screen::GetScreenFor(
115       root_window)->GetDisplayNearestWindow(root_window);
116   DCHECK(display.is_valid());
117
118   return base::Int64ToString(display.id());
119 }
120
121 void UpdatePerDisplayPref(PrefService* pref_service,
122                           aura::Window* root_window,
123                           const char* pref_key,
124                           const std::string& value) {
125   std::string key = GetPrefKeyForRootWindow(root_window);
126   if (key.empty())
127     return;
128
129   DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences);
130   base::DictionaryValue* shelf_prefs = update.Get();
131   base::DictionaryValue* prefs = NULL;
132   if (!shelf_prefs->GetDictionary(key, &prefs)) {
133     prefs = new base::DictionaryValue();
134     shelf_prefs->Set(key, prefs);
135   }
136   prefs->SetStringWithoutPathExpansion(pref_key, value);
137 }
138
139 // Returns a pref value in |pref_service| for the display of |root_window|. The
140 // pref value is stored in |local_path| and |path|, but |pref_service| may have
141 // per-display preferences and the value can be specified by policy. Here is
142 // the priority:
143 //  * A value managed by policy. This is a single value that applies to all
144 //    displays.
145 //  * A user-set value for the specified display.
146 //  * A user-set value in |local_path| or |path|, if no per-display settings are
147 //    ever specified (see http://crbug.com/173719 for why). |local_path| is
148 //    preferred. See comment in |kShelfAlignment| as to why we consider two
149 //    prefs and why |local_path| is preferred.
150 //  * A value recommended by policy. This is a single value that applies to all
151 //    root windows.
152 //  * The default value for |local_path| if the value is not recommended by
153 //    policy.
154 std::string GetPrefForRootWindow(PrefService* pref_service,
155                                  aura::Window* root_window,
156                                  const char* local_path,
157                                  const char* path) {
158   const PrefService::Preference* local_pref =
159       pref_service->FindPreference(local_path);
160   const std::string value(pref_service->GetString(local_path));
161   if (local_pref->IsManaged())
162     return value;
163
164   std::string pref_key = GetPrefKeyForRootWindow(root_window);
165   bool has_per_display_prefs = false;
166   if (!pref_key.empty()) {
167     const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary(
168         prefs::kShelfPreferences);
169     const base::DictionaryValue* display_pref = NULL;
170     std::string per_display_value;
171     if (shelf_prefs->GetDictionary(pref_key, &display_pref) &&
172         display_pref->GetString(path, &per_display_value))
173       return per_display_value;
174
175     // If the pref for the specified display is not found, scan the whole prefs
176     // and check if the prefs for other display is already specified.
177     std::string unused_value;
178     for (base::DictionaryValue::Iterator iter(*shelf_prefs);
179          !iter.IsAtEnd(); iter.Advance()) {
180       const base::DictionaryValue* display_pref = NULL;
181       if (iter.value().GetAsDictionary(&display_pref) &&
182           display_pref->GetString(path, &unused_value)) {
183         has_per_display_prefs = true;
184         break;
185       }
186     }
187   }
188
189   if (local_pref->IsRecommended() || !has_per_display_prefs)
190     return value;
191
192   const base::Value* default_value =
193       pref_service->GetDefaultPrefValue(local_path);
194   std::string default_string;
195   default_value->GetAsString(&default_string);
196   return default_string;
197 }
198
199 // If prefs have synced and no user-set value exists at |local_path|, the value
200 // from |synced_path| is copied to |local_path|.
201 void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service,
202                                const char* local_path,
203                                const char* synced_path) {
204   if (!pref_service->FindPreference(local_path)->HasUserSetting() &&
205       pref_service->IsSyncing()) {
206     // First time the user is using this machine, propagate from remote to
207     // local.
208     pref_service->SetString(local_path, pref_service->GetString(synced_path));
209   }
210 }
211
212 std::string GetSourceFromAppListSource(ash::LaunchSource source) {
213   switch (source) {
214     case ash::LAUNCH_FROM_APP_LIST:
215       return std::string(extension_urls::kLaunchSourceAppList);
216     case ash::LAUNCH_FROM_APP_LIST_SEARCH:
217       return std::string(extension_urls::kLaunchSourceAppListSearch);
218     default: return std::string();
219   }
220 }
221
222 }  // namespace
223
224 #if defined(OS_CHROMEOS)
225 // A class to get events from ChromeOS when a user gets changed or added.
226 class ChromeLauncherControllerUserSwitchObserverChromeOS
227     : public ChromeLauncherControllerUserSwitchObserver,
228       public chromeos::UserManager::UserSessionStateObserver,
229       content::NotificationObserver {
230  public:
231   ChromeLauncherControllerUserSwitchObserverChromeOS(
232       ChromeLauncherController* controller)
233       : controller_(controller) {
234     DCHECK(chromeos::UserManager::IsInitialized());
235     chromeos::UserManager::Get()->AddSessionStateObserver(this);
236     // A UserAddedToSession notification can be sent before a profile is loaded.
237     // Since our observers require that we have already a profile, we might have
238     // to postpone the notification until the ProfileManager lets us know that
239     // the profile for that newly added user was added to the ProfileManager.
240     registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED,
241                    content::NotificationService::AllSources());
242   }
243   virtual ~ChromeLauncherControllerUserSwitchObserverChromeOS() {
244     chromeos::UserManager::Get()->RemoveSessionStateObserver(this);
245   }
246
247   // chromeos::UserManager::UserSessionStateObserver overrides:
248   virtual void UserAddedToSession(const chromeos::User* added_user) OVERRIDE;
249
250   // content::NotificationObserver overrides:
251   virtual void Observe(int type,
252                const content::NotificationSource& source,
253                const content::NotificationDetails& details) OVERRIDE;
254
255  private:
256   // Add a user to the session.
257   void AddUser(Profile* profile);
258
259   // The owning ChromeLauncherController.
260   ChromeLauncherController* controller_;
261
262   // The notification registrar to track the Profile creations after a user got
263   // added to the session (if required).
264   content::NotificationRegistrar registrar_;
265
266   // Users which were just added to the system, but which profiles were not yet
267   // (fully) loaded.
268   std::set<std::string> added_user_ids_waiting_for_profiles_;
269
270   DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerUserSwitchObserverChromeOS);
271 };
272
273 void ChromeLauncherControllerUserSwitchObserverChromeOS::UserAddedToSession(
274     const chromeos::User* active_user) {
275   Profile* profile = multi_user_util::GetProfileFromUserID(
276       active_user->email());
277   // If we do not have a profile yet, we postpone forwarding the notification
278   // until it is loaded.
279   if (!profile)
280     added_user_ids_waiting_for_profiles_.insert(active_user->email());
281   else
282     AddUser(profile);
283 }
284
285 void ChromeLauncherControllerUserSwitchObserverChromeOS::Observe(
286     int type,
287     const content::NotificationSource& source,
288     const content::NotificationDetails& details) {
289   if (type == chrome::NOTIFICATION_PROFILE_ADDED &&
290       !added_user_ids_waiting_for_profiles_.empty()) {
291     // Check if the profile is from a user which was on the waiting list.
292     Profile* profile = content::Source<Profile>(source).ptr();
293     std::string user_id = multi_user_util::GetUserIDFromProfile(profile);
294     std::set<std::string>::iterator it = std::find(
295         added_user_ids_waiting_for_profiles_.begin(),
296         added_user_ids_waiting_for_profiles_.end(),
297         user_id);
298     if (it != added_user_ids_waiting_for_profiles_.end()) {
299       added_user_ids_waiting_for_profiles_.erase(it);
300       AddUser(profile->GetOriginalProfile());
301     }
302   }
303 }
304
305 void ChromeLauncherControllerUserSwitchObserverChromeOS::AddUser(
306     Profile* profile) {
307   if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
308           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED)
309     chrome::MultiUserWindowManager::GetInstance()->AddUser(profile);
310   controller_->AdditionalUserAddedToSession(profile->GetOriginalProfile());
311 }
312 #endif
313
314 ChromeLauncherController::ChromeLauncherController(Profile* profile,
315                                                    ash::ShelfModel* model)
316     : model_(model),
317       item_delegate_manager_(NULL),
318       profile_(profile),
319       app_sync_ui_state_(NULL),
320       ignore_persist_pinned_state_change_(false) {
321   if (!profile_) {
322     // If no profile was passed, we take the currently active profile and use it
323     // as the owner of the current desktop.
324     // Use the original profile as on chromeos we may get a temporary off the
325     // record profile, unless in guest session (where off the record profile is
326     // the right one).
327     Profile* active_profile = ProfileManager::GetActiveUserProfile();
328     profile_ = active_profile->IsGuestSession() ? active_profile :
329         active_profile->GetOriginalProfile();
330
331     app_sync_ui_state_ = AppSyncUIState::Get(profile_);
332     if (app_sync_ui_state_)
333       app_sync_ui_state_->AddObserver(this);
334   }
335
336   // All profile relevant settings get bound to the current profile.
337   AttachProfile(profile_);
338   model_->AddObserver(this);
339
340   // In multi profile mode we might have a window manager. We try to create it
341   // here. If the instantiation fails, the manager is not needed.
342   chrome::MultiUserWindowManager::CreateInstance();
343
344 #if defined(OS_CHROMEOS)
345   // On Chrome OS using multi profile we want to switch the content of the shelf
346   // with a user change. Note that for unit tests the instance can be NULL.
347   if (chrome::MultiUserWindowManager::GetMultiProfileMode() !=
348           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF) {
349     user_switch_observer_.reset(
350         new ChromeLauncherControllerUserSwitchObserverChromeOS(this));
351   }
352
353   // Create our v1/v2 application / browser monitors which will inform the
354   // launcher of status changes.
355   if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
356           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) {
357     // If running in separated destkop mode, we create the multi profile version
358     // of status monitor.
359     browser_status_monitor_.reset(new MultiProfileBrowserStatusMonitor(this));
360     app_window_controller_.reset(
361         new MultiProfileAppWindowLauncherController(this));
362   } else {
363     // Create our v1/v2 application / browser monitors which will inform the
364     // launcher of status changes.
365     browser_status_monitor_.reset(new BrowserStatusMonitor(this));
366     app_window_controller_.reset(new AppWindowLauncherController(this));
367   }
368 #else
369   // Create our v1/v2 application / browser monitors which will inform the
370   // launcher of status changes.
371   browser_status_monitor_.reset(new BrowserStatusMonitor(this));
372   app_window_controller_.reset(new AppWindowLauncherController(this));
373 #endif
374
375   // Right now ash::Shell isn't created for tests.
376   // TODO(mukai): Allows it to observe display change and write tests.
377   if (ash::Shell::HasInstance()) {
378     ash::Shell::GetInstance()->display_controller()->AddObserver(this);
379     item_delegate_manager_ =
380         ash::Shell::GetInstance()->shelf_item_delegate_manager();
381   }
382
383   notification_registrar_.Add(this,
384                               chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
385                               content::Source<Profile>(profile_));
386   notification_registrar_.Add(
387       this,
388       chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
389       content::Source<Profile>(profile_));
390 }
391
392 ChromeLauncherController::~ChromeLauncherController() {
393   // Reset the BrowserStatusMonitor as it has a weak pointer to this.
394   browser_status_monitor_.reset();
395
396   // Reset the app window controller here since it has a weak pointer to this.
397   app_window_controller_.reset();
398
399   for (std::set<ash::Shelf*>::iterator iter = shelves_.begin();
400        iter != shelves_.end();
401        ++iter)
402     (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this);
403
404   model_->RemoveObserver(this);
405   if (ash::Shell::HasInstance())
406     ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
407   for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
408        i != id_to_item_controller_map_.end(); ++i) {
409     int index = model_->ItemIndexByID(i->first);
410     // A "browser proxy" is not known to the model and this removal does
411     // therefore not need to be propagated to the model.
412     if (index != -1 &&
413         model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT)
414       model_->RemoveItemAt(index);
415   }
416
417   if (ash::Shell::HasInstance())
418     ash::Shell::GetInstance()->RemoveShellObserver(this);
419
420   // Release all profile dependent resources.
421   ReleaseProfile();
422   if (instance_ == this)
423     instance_ = NULL;
424
425   // Get rid of the multi user window manager instance.
426   chrome::MultiUserWindowManager::DeleteInstance();
427 }
428
429 // static
430 ChromeLauncherController* ChromeLauncherController::CreateInstance(
431     Profile* profile,
432     ash::ShelfModel* model) {
433   // We do not check here for re-creation of the ChromeLauncherController since
434   // it appears that it might be intentional that the ChromeLauncherController
435   // can be re-created.
436   instance_ = new ChromeLauncherController(profile, model);
437   return instance_;
438 }
439
440 void ChromeLauncherController::Init() {
441   CreateBrowserShortcutLauncherItem();
442   UpdateAppLaunchersFromPref();
443
444   // TODO(sky): update unit test so that this test isn't necessary.
445   if (ash::Shell::HasInstance()) {
446     SetShelfAutoHideBehaviorFromPrefs();
447     SetShelfAlignmentFromPrefs();
448     PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
449     if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() ||
450         !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)->
451             HasUserSetting()) {
452       // This causes OnIsSyncingChanged to be called when the value of
453       // PrefService::IsSyncing() changes.
454       prefs->AddObserver(this);
455     }
456     ash::Shell::GetInstance()->AddShellObserver(this);
457   }
458 }
459
460 ash::ShelfID ChromeLauncherController::CreateAppLauncherItem(
461     LauncherItemController* controller,
462     const std::string& app_id,
463     ash::ShelfItemStatus status) {
464   CHECK(controller);
465   int index = 0;
466   // Panels are inserted on the left so as not to push all existing panels over.
467   if (controller->GetShelfItemType() != ash::TYPE_APP_PANEL)
468     index = model_->item_count();
469   return InsertAppLauncherItem(controller,
470                                app_id,
471                                status,
472                                index,
473                                controller->GetShelfItemType());
474 }
475
476 void ChromeLauncherController::SetItemStatus(ash::ShelfID id,
477                                              ash::ShelfItemStatus status) {
478   int index = model_->ItemIndexByID(id);
479   ash::ShelfItemStatus old_status = model_->items()[index].status;
480   // Since ordinary browser windows are not registered, we might get a negative
481   // index here.
482   if (index >= 0 && old_status != status) {
483     ash::ShelfItem item = model_->items()[index];
484     item.status = status;
485     model_->Set(index, item);
486   }
487 }
488
489 void ChromeLauncherController::SetItemController(
490     ash::ShelfID id,
491     LauncherItemController* controller) {
492   CHECK(controller);
493   IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
494   CHECK(iter != id_to_item_controller_map_.end());
495   controller->set_shelf_id(id);
496   iter->second = controller;
497   // Existing controller is destroyed and replaced by registering again.
498   SetShelfItemDelegate(id, controller);
499 }
500
501 void ChromeLauncherController::CloseLauncherItem(ash::ShelfID id) {
502   CHECK(id);
503   if (IsPinned(id)) {
504     // Create a new shortcut controller.
505     IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
506     CHECK(iter != id_to_item_controller_map_.end());
507     SetItemStatus(id, ash::STATUS_CLOSED);
508     std::string app_id = iter->second->app_id();
509     iter->second = new AppShortcutLauncherItemController(app_id, this);
510     iter->second->set_shelf_id(id);
511     // Existing controller is destroyed and replaced by registering again.
512     SetShelfItemDelegate(id, iter->second);
513   } else {
514     LauncherItemClosed(id);
515   }
516 }
517
518 void ChromeLauncherController::Pin(ash::ShelfID id) {
519   DCHECK(HasItemController(id));
520
521   int index = model_->ItemIndexByID(id);
522   DCHECK_GE(index, 0);
523
524   ash::ShelfItem item = model_->items()[index];
525
526   if (item.type == ash::TYPE_PLATFORM_APP ||
527       item.type == ash::TYPE_WINDOWED_APP) {
528     item.type = ash::TYPE_APP_SHORTCUT;
529     model_->Set(index, item);
530   } else if (item.type != ash::TYPE_APP_SHORTCUT) {
531     return;
532   }
533
534   if (CanPin())
535     PersistPinnedState();
536 }
537
538 void ChromeLauncherController::Unpin(ash::ShelfID id) {
539   DCHECK(HasItemController(id));
540
541   LauncherItemController* controller = id_to_item_controller_map_[id];
542   if (controller->type() == LauncherItemController::TYPE_APP ||
543       controller->locked()) {
544     UnpinRunningAppInternal(model_->ItemIndexByID(id));
545   } else {
546     LauncherItemClosed(id);
547   }
548   if (CanPin())
549     PersistPinnedState();
550 }
551
552 bool ChromeLauncherController::IsPinned(ash::ShelfID id) {
553   int index = model_->ItemIndexByID(id);
554   if (index < 0)
555     return false;
556   ash::ShelfItemType type = model_->items()[index].type;
557   return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT);
558 }
559
560 void ChromeLauncherController::TogglePinned(ash::ShelfID id) {
561   if (!HasItemController(id))
562     return;  // May happen if item closed with menu open.
563
564   if (IsPinned(id))
565     Unpin(id);
566   else
567     Pin(id);
568 }
569
570 bool ChromeLauncherController::IsPinnable(ash::ShelfID id) const {
571   int index = model_->ItemIndexByID(id);
572   if (index == -1)
573     return false;
574
575   ash::ShelfItemType type = model_->items()[index].type;
576   return ((type == ash::TYPE_APP_SHORTCUT ||
577            type == ash::TYPE_PLATFORM_APP ||
578            type == ash::TYPE_WINDOWED_APP) &&
579           CanPin());
580 }
581
582 void ChromeLauncherController::LockV1AppWithID(
583     const std::string& app_id) {
584   ash::ShelfID id = GetShelfIDForAppID(app_id);
585   if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) {
586     CreateAppShortcutLauncherItemWithType(app_id,
587                                           model_->item_count(),
588                                           ash::TYPE_WINDOWED_APP);
589     id = GetShelfIDForAppID(app_id);
590   }
591   CHECK(id);
592   id_to_item_controller_map_[id]->lock();
593 }
594
595 void ChromeLauncherController::UnlockV1AppWithID(const std::string& app_id) {
596   ash::ShelfID id = GetShelfIDForAppID(app_id);
597   CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id));
598   CHECK(id);
599   LauncherItemController* controller = id_to_item_controller_map_[id];
600   controller->unlock();
601   if (!controller->locked() && !IsPinned(id))
602     CloseLauncherItem(id);
603 }
604
605 void ChromeLauncherController::Launch(ash::ShelfID id, int event_flags) {
606   if (!HasItemController(id))
607     return;  // In case invoked from menu and item closed while menu up.
608   id_to_item_controller_map_[id]->Launch(ash::LAUNCH_FROM_UNKNOWN, event_flags);
609 }
610
611 void ChromeLauncherController::Close(ash::ShelfID id) {
612   if (!HasItemController(id))
613     return;  // May happen if menu closed.
614   id_to_item_controller_map_[id]->Close();
615 }
616
617 bool ChromeLauncherController::IsOpen(ash::ShelfID id) {
618   if (!HasItemController(id))
619     return false;
620   return id_to_item_controller_map_[id]->IsOpen();
621 }
622
623 bool ChromeLauncherController::IsPlatformApp(ash::ShelfID id) {
624   if (!HasItemController(id))
625     return false;
626
627   std::string app_id = GetAppIDForShelfID(id);
628   const Extension* extension = GetExtensionForAppID(app_id);
629   // An extension can be synced / updated at any time and therefore not be
630   // available.
631   return extension ? extension->is_platform_app() : false;
632 }
633
634 void ChromeLauncherController::LaunchApp(const std::string& app_id,
635                                          ash::LaunchSource source,
636                                          int event_flags) {
637   // |extension| could be NULL when it is being unloaded for updating.
638   const Extension* extension = GetExtensionForAppID(app_id);
639   if (!extension)
640     return;
641
642   if (!extensions::util::IsAppLaunchableWithoutEnabling(app_id, profile_)) {
643     // Do nothing if there is already a running enable flow.
644     if (extension_enable_flow_)
645       return;
646
647     extension_enable_flow_.reset(
648         new ExtensionEnableFlow(profile_, app_id, this));
649     extension_enable_flow_->StartForNativeWindow(NULL);
650     return;
651   }
652
653   if (LaunchedInNativeDesktop(app_id))
654     return;
655
656   // The app will be created for the currently active profile.
657   AppLaunchParams params(profile_,
658                          extension,
659                          event_flags,
660                          chrome::HOST_DESKTOP_TYPE_ASH);
661   if (source != ash::LAUNCH_FROM_UNKNOWN &&
662       app_id == extension_misc::kWebStoreAppId) {
663     // Get the corresponding source string.
664     std::string source_value = GetSourceFromAppListSource(source);
665
666     // Set an override URL to include the source.
667     GURL extension_url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
668     params.override_url = net::AppendQueryParameter(
669         extension_url, extension_urls::kWebstoreSourceField, source_value);
670   }
671
672   OpenApplication(params);
673 }
674
675 void ChromeLauncherController::ActivateApp(const std::string& app_id,
676                                            ash::LaunchSource source,
677                                            int event_flags) {
678   // If there is an existing non-shortcut controller for this app, open it.
679   ash::ShelfID id = GetShelfIDForAppID(app_id);
680   if (id) {
681     LauncherItemController* controller = id_to_item_controller_map_[id];
682     controller->Activate(source);
683     return;
684   }
685
686   // Create a temporary application launcher item and use it to see if there are
687   // running instances.
688   scoped_ptr<AppShortcutLauncherItemController> app_controller(
689       new AppShortcutLauncherItemController(app_id, this));
690   if (!app_controller->GetRunningApplications().empty())
691     app_controller->Activate(source);
692   else
693     LaunchApp(app_id, source, event_flags);
694 }
695
696 extensions::LaunchType ChromeLauncherController::GetLaunchType(
697     ash::ShelfID id) {
698   DCHECK(HasItemController(id));
699
700   const Extension* extension = GetExtensionForAppID(
701       id_to_item_controller_map_[id]->app_id());
702
703   // An extension can be unloaded/updated/unavailable at any time.
704   if (!extension)
705     return extensions::LAUNCH_TYPE_DEFAULT;
706
707   return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_),
708                                    extension);
709 }
710
711 ash::ShelfID ChromeLauncherController::GetShelfIDForAppID(
712     const std::string& app_id) {
713   for (IDToItemControllerMap::const_iterator i =
714            id_to_item_controller_map_.begin();
715        i != id_to_item_controller_map_.end(); ++i) {
716     if (i->second->type() == LauncherItemController::TYPE_APP_PANEL)
717       continue;  // Don't include panels
718     if (i->second->app_id() == app_id)
719       return i->first;
720   }
721   return 0;
722 }
723
724 const std::string& ChromeLauncherController::GetAppIDForShelfID(
725     ash::ShelfID id) {
726   CHECK(HasItemController(id));
727   return id_to_item_controller_map_[id]->app_id();
728 }
729
730 void ChromeLauncherController::SetAppImage(const std::string& id,
731                                            const gfx::ImageSkia& image) {
732   // TODO: need to get this working for shortcuts.
733   for (IDToItemControllerMap::const_iterator i =
734            id_to_item_controller_map_.begin();
735        i != id_to_item_controller_map_.end(); ++i) {
736     LauncherItemController* controller = i->second;
737     if (controller->app_id() != id)
738       continue;
739     if (controller->image_set_by_controller())
740       continue;
741     int index = model_->ItemIndexByID(i->first);
742     if (index == -1)
743       continue;
744     ash::ShelfItem item = model_->items()[index];
745     item.image = image;
746     model_->Set(index, item);
747     // It's possible we're waiting on more than one item, so don't break.
748   }
749 }
750
751 void ChromeLauncherController::OnAutoHideBehaviorChanged(
752     aura::Window* root_window,
753     ash::ShelfAutoHideBehavior new_behavior) {
754   SetShelfAutoHideBehaviorPrefs(new_behavior, root_window);
755 }
756
757 void ChromeLauncherController::SetLauncherItemImage(
758     ash::ShelfID shelf_id,
759     const gfx::ImageSkia& image) {
760   int index = model_->ItemIndexByID(shelf_id);
761   if (index == -1)
762     return;
763   ash::ShelfItem item = model_->items()[index];
764   item.image = image;
765   model_->Set(index, item);
766 }
767
768 bool ChromeLauncherController::CanPin() const {
769   const PrefService::Preference* pref =
770       profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
771   return pref && pref->IsUserModifiable();
772 }
773
774 bool ChromeLauncherController::IsAppPinned(const std::string& app_id) {
775   for (IDToItemControllerMap::const_iterator i =
776            id_to_item_controller_map_.begin();
777        i != id_to_item_controller_map_.end(); ++i) {
778     if (IsPinned(i->first) && i->second->app_id() == app_id)
779       return true;
780   }
781   return false;
782 }
783
784 bool ChromeLauncherController::IsWindowedAppInLauncher(
785     const std::string& app_id) {
786   int index = model_->ItemIndexByID(GetShelfIDForAppID(app_id));
787   if (index < 0)
788     return false;
789
790   ash::ShelfItemType type = model_->items()[index].type;
791   return type == ash::TYPE_WINDOWED_APP;
792 }
793
794 void ChromeLauncherController::PinAppWithID(const std::string& app_id) {
795   if (CanPin())
796     DoPinAppWithID(app_id);
797   else
798     NOTREACHED();
799 }
800
801 void ChromeLauncherController::SetLaunchType(
802     ash::ShelfID id,
803     extensions::LaunchType launch_type) {
804   if (!HasItemController(id))
805     return;
806
807   extensions::SetLaunchType(profile_->GetExtensionService(),
808                             id_to_item_controller_map_[id]->app_id(),
809                             launch_type);
810 }
811
812 void ChromeLauncherController::UnpinAppWithID(const std::string& app_id) {
813   if (CanPin())
814     DoUnpinAppWithID(app_id);
815   else
816     NOTREACHED();
817 }
818
819 bool ChromeLauncherController::IsLoggedInAsGuest() {
820   return profile_->IsGuestSession();
821 }
822
823 void ChromeLauncherController::CreateNewWindow() {
824   // Use the currently active user.
825   chrome::NewEmptyWindow(profile_, chrome::HOST_DESKTOP_TYPE_ASH);
826 }
827
828 void ChromeLauncherController::CreateNewIncognitoWindow() {
829   // Use the currently active user.
830   chrome::NewEmptyWindow(profile_->GetOffTheRecordProfile(),
831                          chrome::HOST_DESKTOP_TYPE_ASH);
832 }
833
834 void ChromeLauncherController::PersistPinnedState() {
835   if (ignore_persist_pinned_state_change_)
836     return;
837   // It is a coding error to call PersistPinnedState() if the pinned apps are
838   // not user-editable. The code should check earlier and not perform any
839   // modification actions that trigger persisting the state.
840   if (!CanPin()) {
841     NOTREACHED() << "Can't pin but pinned state being updated";
842     return;
843   }
844   // Mutating kPinnedLauncherApps is going to notify us and trigger us to
845   // process the change. We don't want that to happen so remove ourselves as a
846   // listener.
847   pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
848   {
849     ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
850     updater->Clear();
851     for (size_t i = 0; i < model_->items().size(); ++i) {
852       if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
853         ash::ShelfID id = model_->items()[i].id;
854         if (HasItemController(id) && IsPinned(id)) {
855           base::DictionaryValue* app_value = ash::CreateAppDict(
856               id_to_item_controller_map_[id]->app_id());
857           if (app_value)
858             updater->Append(app_value);
859         }
860       } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) {
861         PersistChromeItemIndex(i);
862       } else if (model_->items()[i].type == ash::TYPE_APP_LIST) {
863         base::DictionaryValue* app_value = ash::CreateAppDict(
864             kAppShelfIdPlaceholder);
865         if (app_value)
866           updater->Append(app_value);
867       }
868     }
869   }
870   pref_change_registrar_.Add(
871       prefs::kPinnedLauncherApps,
872       base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
873                  base::Unretained(this)));
874 }
875
876 ash::ShelfModel* ChromeLauncherController::model() {
877   return model_;
878 }
879
880 Profile* ChromeLauncherController::profile() {
881   return profile_;
882 }
883
884 ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior(
885     aura::Window* root_window) const {
886   // Don't show the shelf in app mode.
887   if (chrome::IsRunningInAppMode())
888     return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
889
890   // See comment in |kShelfAlignment| as to why we consider two prefs.
891   const std::string behavior_value(
892       GetPrefForRootWindow(profile_->GetPrefs(),
893                            root_window,
894                            prefs::kShelfAutoHideBehaviorLocal,
895                            prefs::kShelfAutoHideBehavior));
896
897   // Note: To maintain sync compatibility with old images of chrome/chromeos
898   // the set of values that may be encountered includes the now-extinct
899   // "Default" as well as "Never" and "Always", "Default" should now
900   // be treated as "Never" (http://crbug.com/146773).
901   if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
902     return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
903   return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
904 }
905
906 bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior(
907     aura::Window* root_window) const {
908   return !ash::Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled() &&
909       profile_->GetPrefs()->FindPreference(
910           prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable();
911 }
912
913 void ChromeLauncherController::ToggleShelfAutoHideBehavior(
914     aura::Window* root_window) {
915   ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) ==
916       ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
917           ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER :
918           ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
919   SetShelfAutoHideBehaviorPrefs(behavior, root_window);
920   return;
921 }
922
923 void ChromeLauncherController::UpdateAppState(content::WebContents* contents,
924                                               AppState app_state) {
925   std::string app_id = app_tab_helper_->GetAppID(contents);
926
927   // Check if the gMail app is loaded and it matches the given content.
928   // This special treatment is needed to address crbug.com/234268.
929   if (app_id.empty() && ContentCanBeHandledByGmailApp(contents))
930     app_id = kGmailAppId;
931
932   // Check the old |app_id| for a tab. If the contents has changed we need to
933   // remove it from the previous app.
934   if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) {
935     std::string last_app_id = web_contents_to_app_id_[contents];
936     if (last_app_id != app_id) {
937       ash::ShelfID id = GetShelfIDForAppID(last_app_id);
938       if (id) {
939         // Since GetAppState() will use |web_contents_to_app_id_| we remove
940         // the connection before calling it.
941         web_contents_to_app_id_.erase(contents);
942         SetItemStatus(id, GetAppState(last_app_id));
943       }
944     }
945   }
946
947   if (app_state == APP_STATE_REMOVED)
948     web_contents_to_app_id_.erase(contents);
949   else
950     web_contents_to_app_id_[contents] = app_id;
951
952   ash::ShelfID id = GetShelfIDForAppID(app_id);
953   if (id) {
954     SetItemStatus(id, (app_state == APP_STATE_WINDOW_ACTIVE ||
955                        app_state == APP_STATE_ACTIVE) ? ash::STATUS_ACTIVE :
956                                                         GetAppState(app_id));
957   }
958 }
959
960 ash::ShelfID ChromeLauncherController::GetShelfIDForWebContents(
961     content::WebContents* contents) {
962   DCHECK(contents);
963
964   std::string app_id = app_tab_helper_->GetAppID(contents);
965
966   if (app_id.empty() && ContentCanBeHandledByGmailApp(contents))
967     app_id = kGmailAppId;
968
969   ash::ShelfID id = GetShelfIDForAppID(app_id);
970
971   if (app_id.empty() || !id) {
972     int browser_index = model_->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
973     return model_->items()[browser_index].id;
974   }
975
976   return id;
977 }
978
979 void ChromeLauncherController::SetRefocusURLPatternForTest(ash::ShelfID id,
980                                                            const GURL& url) {
981   DCHECK(HasItemController(id));
982   LauncherItemController* controller = id_to_item_controller_map_[id];
983
984   int index = model_->ItemIndexByID(id);
985   if (index == -1) {
986     NOTREACHED() << "Invalid launcher id";
987     return;
988   }
989
990   ash::ShelfItemType type = model_->items()[index].type;
991   if (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_WINDOWED_APP) {
992     AppShortcutLauncherItemController* app_controller =
993         static_cast<AppShortcutLauncherItemController*>(controller);
994     app_controller->set_refocus_url(url);
995   } else {
996     NOTREACHED() << "Invalid launcher type";
997   }
998 }
999
1000 const Extension* ChromeLauncherController::GetExtensionForAppID(
1001     const std::string& app_id) const {
1002   // Some unit tests do not have a real extension.
1003   return (profile_->GetExtensionService()) ?
1004       profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL;
1005 }
1006
1007 void ChromeLauncherController::ActivateWindowOrMinimizeIfActive(
1008     ui::BaseWindow* window,
1009     bool allow_minimize) {
1010   // In separated desktop mode we might have to teleport a window back to the
1011   // current user.
1012   if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
1013           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) {
1014     aura::Window* native_window = window->GetNativeWindow();
1015     const std::string& current_user =
1016         multi_user_util::GetUserIDFromProfile(profile());
1017     chrome::MultiUserWindowManager* manager =
1018         chrome::MultiUserWindowManager::GetInstance();
1019     if (!manager->IsWindowOnDesktopOfUser(native_window, current_user)) {
1020       ash::MultiProfileUMA::RecordTeleportAction(
1021           ash::MultiProfileUMA::TELEPORT_WINDOW_RETURN_BY_LAUNCHER);
1022       manager->ShowWindowForUser(native_window, current_user);
1023       window->Activate();
1024       return;
1025     }
1026   }
1027
1028   if (window->IsActive() && allow_minimize) {
1029     if (CommandLine::ForCurrentProcess()->HasSwitch(
1030             switches::kDisableMinimizeOnSecondLauncherItemClick)) {
1031       AnimateWindow(window->GetNativeWindow(),
1032                     wm::WINDOW_ANIMATION_TYPE_BOUNCE);
1033     } else {
1034       window->Minimize();
1035     }
1036   } else {
1037     window->Show();
1038     window->Activate();
1039   }
1040 }
1041
1042 void ChromeLauncherController::OnShelfCreated(ash::Shelf* shelf) {
1043   shelves_.insert(shelf);
1044   shelf->shelf_widget()->shelf_layout_manager()->AddObserver(this);
1045 }
1046
1047 void ChromeLauncherController::OnShelfDestroyed(ash::Shelf* shelf) {
1048   shelves_.erase(shelf);
1049   // RemoveObserver is not called here, since by the time this method is called
1050   // Shelf is already in its destructor.
1051 }
1052
1053 void ChromeLauncherController::ShelfItemAdded(int index) {
1054   // The app list launcher can get added to the shelf after we applied the
1055   // preferences. In that case the item might be at the wrong spot. As such we
1056   // call the function again.
1057   if (model_->items()[index].type == ash::TYPE_APP_LIST)
1058     UpdateAppLaunchersFromPref();
1059 }
1060
1061 void ChromeLauncherController::ShelfItemRemoved(int index, ash::ShelfID id) {
1062 }
1063
1064 void ChromeLauncherController::ShelfItemMoved(int start_index,
1065                                               int target_index) {
1066   const ash::ShelfItem& item = model_->items()[target_index];
1067   // We remember the moved item position if it is either pinnable or
1068   // it is the app list with the alternate shelf layout.
1069   if ((HasItemController(item.id) && IsPinned(item.id)) ||
1070        item.type == ash::TYPE_APP_LIST)
1071     PersistPinnedState();
1072 }
1073
1074 void ChromeLauncherController::ShelfItemChanged(
1075     int index,
1076     const ash::ShelfItem& old_item) {
1077 }
1078
1079 void ChromeLauncherController::ShelfStatusChanged() {
1080 }
1081
1082 void ChromeLauncherController::ActiveUserChanged(
1083     const std::string& user_email) {
1084   // Store the order of running applications for the user which gets inactive.
1085   RememberUnpinnedRunningApplicationOrder();
1086   // Coming here the default profile is already switched. All profile specific
1087   // resources get released and the new profile gets attached instead.
1088   ReleaseProfile();
1089   // When coming here, the active user has already be changed so that we can
1090   // set it as active.
1091   AttachProfile(ProfileManager::GetActiveUserProfile());
1092   // Update the V1 applications.
1093   browser_status_monitor_->ActiveUserChanged(user_email);
1094   // Switch the running applications to the new user.
1095   app_window_controller_->ActiveUserChanged(user_email);
1096   // Update the user specific shell properties from the new user profile.
1097   UpdateAppLaunchersFromPref();
1098   SetShelfAlignmentFromPrefs();
1099   SetShelfAutoHideBehaviorFromPrefs();
1100   SetShelfBehaviorsFromPrefs();
1101   // Restore the order of running, but unpinned applications for the activated
1102   // user.
1103   RestoreUnpinnedRunningApplicationOrder(user_email);
1104   // Inform the system tray of the change.
1105   ash::Shell::GetInstance()->system_tray_delegate()->ActiveUserWasChanged();
1106   // Force on-screen keyboard to reset.
1107   if (keyboard::IsKeyboardEnabled())
1108     ash::Shell::GetInstance()->CreateKeyboard();
1109 }
1110
1111 void ChromeLauncherController::AdditionalUserAddedToSession(Profile* profile) {
1112   // Switch the running applications to the new user.
1113   app_window_controller_->AdditionalUserAddedToSession(profile);
1114 }
1115
1116 void ChromeLauncherController::Observe(
1117     int type,
1118     const content::NotificationSource& source,
1119     const content::NotificationDetails& details) {
1120   switch (type) {
1121     case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
1122       const Extension* extension =
1123           content::Details<const Extension>(details).ptr();
1124       if (IsAppPinned(extension->id())) {
1125         // Clear and re-fetch to ensure icon is up-to-date.
1126         app_icon_loader_->ClearImage(extension->id());
1127         app_icon_loader_->FetchImage(extension->id());
1128       }
1129
1130       UpdateAppLaunchersFromPref();
1131       break;
1132     }
1133     case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
1134       const content::Details<UnloadedExtensionInfo>& unload_info(details);
1135       const Extension* extension = unload_info->extension;
1136       const std::string& id = extension->id();
1137       // Since we might have windowed apps of this type which might have
1138       // outstanding locks which needs to be removed.
1139       if (GetShelfIDForAppID(id) &&
1140           unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) {
1141         CloseWindowedAppsFromRemovedExtension(id);
1142       }
1143
1144       if (IsAppPinned(id)) {
1145         if (unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) {
1146           DoUnpinAppWithID(id);
1147           app_icon_loader_->ClearImage(id);
1148         } else {
1149           app_icon_loader_->UpdateImage(id);
1150         }
1151       }
1152       break;
1153     }
1154     default:
1155       NOTREACHED() << "Unexpected notification type=" << type;
1156   }
1157 }
1158
1159 void ChromeLauncherController::OnShelfAlignmentChanged(
1160     aura::Window* root_window) {
1161   const char* pref_value = NULL;
1162   switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) {
1163     case ash::SHELF_ALIGNMENT_BOTTOM:
1164       pref_value = ash::kShelfAlignmentBottom;
1165       break;
1166     case ash::SHELF_ALIGNMENT_LEFT:
1167       pref_value = ash::kShelfAlignmentLeft;
1168       break;
1169     case ash::SHELF_ALIGNMENT_RIGHT:
1170       pref_value = ash::kShelfAlignmentRight;
1171       break;
1172     case ash::SHELF_ALIGNMENT_TOP:
1173       pref_value = ash::kShelfAlignmentTop;
1174   }
1175
1176   UpdatePerDisplayPref(
1177       profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value);
1178
1179   if (root_window == ash::Shell::GetPrimaryRootWindow()) {
1180     // See comment in |kShelfAlignment| about why we have two prefs here.
1181     profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
1182     profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
1183   }
1184 }
1185
1186 void ChromeLauncherController::OnDisplayConfigurationChanged() {
1187   SetShelfBehaviorsFromPrefs();
1188 }
1189
1190 void ChromeLauncherController::OnIsSyncingChanged() {
1191   PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
1192   MaybePropagatePrefToLocal(prefs,
1193                             prefs::kShelfAlignmentLocal,
1194                             prefs::kShelfAlignment);
1195   MaybePropagatePrefToLocal(prefs,
1196                             prefs::kShelfAutoHideBehaviorLocal,
1197                             prefs::kShelfAutoHideBehavior);
1198 }
1199
1200 void ChromeLauncherController::OnAppSyncUIStatusChanged() {
1201   if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
1202     model_->SetStatus(ash::ShelfModel::STATUS_LOADING);
1203   else
1204     model_->SetStatus(ash::ShelfModel::STATUS_NORMAL);
1205 }
1206
1207 void ChromeLauncherController::ExtensionEnableFlowFinished() {
1208   LaunchApp(extension_enable_flow_->extension_id(),
1209             ash::LAUNCH_FROM_UNKNOWN,
1210             ui::EF_NONE);
1211   extension_enable_flow_.reset();
1212 }
1213
1214 void ChromeLauncherController::ExtensionEnableFlowAborted(bool user_initiated) {
1215   extension_enable_flow_.reset();
1216 }
1217
1218 ChromeLauncherAppMenuItems ChromeLauncherController::GetApplicationList(
1219     const ash::ShelfItem& item,
1220     int event_flags) {
1221   // Make sure that there is a controller associated with the id and that the
1222   // extension itself is a valid application and not a panel.
1223   if (!HasItemController(item.id) ||
1224       !GetShelfIDForAppID(id_to_item_controller_map_[item.id]->app_id()))
1225     return ChromeLauncherAppMenuItems().Pass();
1226
1227   return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags);
1228 }
1229
1230 std::vector<content::WebContents*>
1231 ChromeLauncherController::GetV1ApplicationsFromAppId(std::string app_id) {
1232   ash::ShelfID id = GetShelfIDForAppID(app_id);
1233
1234   // If there is no such an item pinned to the launcher, no menu gets created.
1235   if (id) {
1236     LauncherItemController* controller = id_to_item_controller_map_[id];
1237     DCHECK(controller);
1238     if (controller->type() == LauncherItemController::TYPE_SHORTCUT)
1239       return GetV1ApplicationsFromController(controller);
1240   }
1241   return std::vector<content::WebContents*>();
1242 }
1243
1244 void ChromeLauncherController::ActivateShellApp(const std::string& app_id,
1245                                                 int index) {
1246   ash::ShelfID id = GetShelfIDForAppID(app_id);
1247   if (id) {
1248     LauncherItemController* controller = id_to_item_controller_map_[id];
1249     if (controller->type() == LauncherItemController::TYPE_APP) {
1250       AppWindowLauncherItemController* app_window_controller =
1251           static_cast<AppWindowLauncherItemController*>(controller);
1252       app_window_controller->ActivateIndexedApp(index);
1253     }
1254   }
1255 }
1256
1257 bool ChromeLauncherController::IsWebContentHandledByApplication(
1258     content::WebContents* web_contents,
1259     const std::string& app_id) {
1260   if ((web_contents_to_app_id_.find(web_contents) !=
1261        web_contents_to_app_id_.end()) &&
1262       (web_contents_to_app_id_[web_contents] == app_id))
1263     return true;
1264   return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents));
1265 }
1266
1267 bool ChromeLauncherController::ContentCanBeHandledByGmailApp(
1268     content::WebContents* web_contents) {
1269   ash::ShelfID id = GetShelfIDForAppID(kGmailAppId);
1270   if (id) {
1271     const GURL url = web_contents->GetURL();
1272     // We need to extend the application matching for the gMail app beyond the
1273     // manifest file's specification. This is required because of the namespace
1274     // overlap with the offline app ("/mail/mu/").
1275     if (!MatchPattern(url.path(), "/mail/mu/*") &&
1276         MatchPattern(url.path(), "/mail/*") &&
1277         GetExtensionForAppID(kGmailAppId) &&
1278         GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url))
1279       return true;
1280   }
1281   return false;
1282 }
1283
1284 gfx::Image ChromeLauncherController::GetAppListIcon(
1285     content::WebContents* web_contents) const {
1286   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1287   if (IsIncognito(web_contents))
1288     return rb.GetImageNamed(IDR_ASH_SHELF_LIST_INCOGNITO_BROWSER);
1289   FaviconTabHelper* favicon_tab_helper =
1290       FaviconTabHelper::FromWebContents(web_contents);
1291   gfx::Image result = favicon_tab_helper->GetFavicon();
1292   if (result.IsEmpty())
1293     return rb.GetImageNamed(IDR_DEFAULT_FAVICON);
1294   return result;
1295 }
1296
1297 base::string16 ChromeLauncherController::GetAppListTitle(
1298     content::WebContents* web_contents) const {
1299   base::string16 title = web_contents->GetTitle();
1300   if (!title.empty())
1301     return title;
1302   WebContentsToAppIDMap::const_iterator iter =
1303       web_contents_to_app_id_.find(web_contents);
1304   if (iter != web_contents_to_app_id_.end()) {
1305     std::string app_id = iter->second;
1306     const extensions::Extension* extension = GetExtensionForAppID(app_id);
1307     if (extension)
1308       return base::UTF8ToUTF16(extension->name());
1309   }
1310   return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
1311 }
1312
1313 ash::ShelfID ChromeLauncherController::CreateAppShortcutLauncherItem(
1314     const std::string& app_id,
1315     int index) {
1316   return CreateAppShortcutLauncherItemWithType(app_id,
1317                                                index,
1318                                                ash::TYPE_APP_SHORTCUT);
1319 }
1320
1321 void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) {
1322   app_tab_helper_.reset(helper);
1323 }
1324
1325 void ChromeLauncherController::SetAppIconLoaderForTest(
1326     extensions::AppIconLoader* loader) {
1327   app_icon_loader_.reset(loader);
1328 }
1329
1330 const std::string& ChromeLauncherController::GetAppIdFromShelfIdForTest(
1331     ash::ShelfID id) {
1332   return id_to_item_controller_map_[id]->app_id();
1333 }
1334
1335 void ChromeLauncherController::SetShelfItemDelegateManagerForTest(
1336     ash::ShelfItemDelegateManager* manager) {
1337   item_delegate_manager_ = manager;
1338 }
1339
1340 void ChromeLauncherController::RememberUnpinnedRunningApplicationOrder() {
1341   RunningAppListIds list;
1342   for (int i = 0; i < model_->item_count(); i++) {
1343     ash::ShelfItemType type = model_->items()[i].type;
1344     if (type == ash::TYPE_WINDOWED_APP || type == ash::TYPE_PLATFORM_APP)
1345       list.push_back(GetAppIDForShelfID(model_->items()[i].id));
1346   }
1347   last_used_running_application_order_[
1348       multi_user_util::GetUserIDFromProfile(profile_)] = list;
1349 }
1350
1351 void ChromeLauncherController::RestoreUnpinnedRunningApplicationOrder(
1352     const std::string& user_id) {
1353   const RunningAppListIdMap::iterator app_id_list =
1354       last_used_running_application_order_.find(user_id);
1355   if (app_id_list == last_used_running_application_order_.end())
1356     return;
1357
1358   // Find the first insertion point for running applications.
1359   int running_index = model_->FirstRunningAppIndex();
1360   for (RunningAppListIds::iterator app_id = app_id_list->second.begin();
1361        app_id != app_id_list->second.end(); ++app_id) {
1362     ash::ShelfID shelf_id = GetShelfIDForAppID(*app_id);
1363     if (shelf_id) {
1364       int app_index = model_->ItemIndexByID(shelf_id);
1365       DCHECK_GE(app_index, 0);
1366       ash::ShelfItemType type = model_->items()[app_index].type;
1367       if (type == ash::TYPE_WINDOWED_APP || type == ash::TYPE_PLATFORM_APP) {
1368         if (running_index != app_index)
1369           model_->Move(running_index, app_index);
1370         running_index++;
1371       }
1372     }
1373   }
1374 }
1375
1376 ash::ShelfID ChromeLauncherController::CreateAppShortcutLauncherItemWithType(
1377     const std::string& app_id,
1378     int index,
1379     ash::ShelfItemType shelf_item_type) {
1380   AppShortcutLauncherItemController* controller =
1381       new AppShortcutLauncherItemController(app_id, this);
1382   ash::ShelfID shelf_id = InsertAppLauncherItem(
1383       controller, app_id, ash::STATUS_CLOSED, index, shelf_item_type);
1384   return shelf_id;
1385 }
1386
1387 LauncherItemController* ChromeLauncherController::GetLauncherItemController(
1388     const ash::ShelfID id) {
1389   if (!HasItemController(id))
1390     return NULL;
1391   return id_to_item_controller_map_[id];
1392 }
1393
1394 bool ChromeLauncherController::IsBrowserFromActiveUser(Browser* browser) {
1395   // If running multi user mode with separate desktops, we have to check if the
1396   // browser is from the active user.
1397   if (chrome::MultiUserWindowManager::GetMultiProfileMode() !=
1398           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED)
1399     return true;
1400   return multi_user_util::IsProfileFromActiveUser(browser->profile());
1401 }
1402
1403 void ChromeLauncherController::LauncherItemClosed(ash::ShelfID id) {
1404   IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
1405   CHECK(iter != id_to_item_controller_map_.end());
1406   CHECK(iter->second);
1407   app_icon_loader_->ClearImage(iter->second->app_id());
1408   id_to_item_controller_map_.erase(iter);
1409   int index = model_->ItemIndexByID(id);
1410   // A "browser proxy" is not known to the model and this removal does
1411   // therefore not need to be propagated to the model.
1412   if (index != -1)
1413     model_->RemoveItemAt(index);
1414 }
1415
1416 void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) {
1417   // If there is an item, do nothing and return.
1418   if (IsAppPinned(app_id))
1419     return;
1420
1421   ash::ShelfID shelf_id = GetShelfIDForAppID(app_id);
1422   if (shelf_id) {
1423     // App item exists, pin it
1424     Pin(shelf_id);
1425   } else {
1426     // Otherwise, create a shortcut item for it.
1427     CreateAppShortcutLauncherItem(app_id, model_->item_count());
1428     if (CanPin())
1429       PersistPinnedState();
1430   }
1431 }
1432
1433 void ChromeLauncherController::DoUnpinAppWithID(const std::string& app_id) {
1434   ash::ShelfID shelf_id = GetShelfIDForAppID(app_id);
1435   if (shelf_id && IsPinned(shelf_id))
1436     Unpin(shelf_id);
1437 }
1438
1439 int ChromeLauncherController::PinRunningAppInternal(int index,
1440                                                     ash::ShelfID shelf_id) {
1441   int running_index = model_->ItemIndexByID(shelf_id);
1442   ash::ShelfItem item = model_->items()[running_index];
1443   DCHECK(item.type == ash::TYPE_WINDOWED_APP ||
1444          item.type == ash::TYPE_PLATFORM_APP);
1445   item.type = ash::TYPE_APP_SHORTCUT;
1446   model_->Set(running_index, item);
1447   // The |ShelfModel|'s weight system might reposition the item to a
1448   // new index, so we get the index again.
1449   running_index = model_->ItemIndexByID(shelf_id);
1450   if (running_index < index)
1451     --index;
1452   if (running_index != index)
1453     model_->Move(running_index, index);
1454   return index;
1455 }
1456
1457 void ChromeLauncherController::UnpinRunningAppInternal(int index) {
1458   DCHECK_GE(index, 0);
1459   ash::ShelfItem item = model_->items()[index];
1460   DCHECK_EQ(item.type, ash::TYPE_APP_SHORTCUT);
1461   item.type = ash::TYPE_WINDOWED_APP;
1462   // A platform app and a windowed app are sharing TYPE_APP_SHORTCUT. As such
1463   // we have to check here what this was before it got a shortcut.
1464   if (HasItemController(item.id) &&
1465       id_to_item_controller_map_[item.id]->type() ==
1466           LauncherItemController::TYPE_APP)
1467     item.type = ash::TYPE_PLATFORM_APP;
1468   model_->Set(index, item);
1469 }
1470
1471 void ChromeLauncherController::UpdateAppLaunchersFromPref() {
1472   // There are various functions which will trigger a |PersistPinnedState| call
1473   // like a direct call to |DoPinAppWithID|, or an indirect call to the menu
1474   // model which will use weights to re-arrange the icons to new positions.
1475   // Since this function is meant to synchronize the "is state" with the
1476   // "sync state", it makes no sense to store any changes by this function back
1477   // into the pref state. Therefore we tell |persistPinnedState| to ignore any
1478   // invocations while we are running.
1479   base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true);
1480   std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser();
1481
1482   int index = 0;
1483   int max_index = model_->item_count();
1484
1485   // When one of the two special items cannot be moved (and we do not know where
1486   // yet), we remember the current location in one of these variables.
1487   int chrome_index = -1;
1488   int app_list_index = -1;
1489
1490   // Walk the model and |pinned_apps| from the pref lockstep, adding and
1491   // removing items as necessary. NB: This code uses plain old indexing instead
1492   // of iterators because of model mutations as part of the loop.
1493   std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
1494   for (; index < max_index && pref_app_id != pinned_apps.end(); ++index) {
1495     // Check if we have an item which we need to handle.
1496     if (*pref_app_id == extension_misc::kChromeAppId ||
1497         *pref_app_id == kAppShelfIdPlaceholder ||
1498         IsAppPinned(*pref_app_id)) {
1499       for (; index < max_index; ++index) {
1500         const ash::ShelfItem& item(model_->items()[index]);
1501         bool is_app_list = item.type == ash::TYPE_APP_LIST;
1502         bool is_chrome = item.type == ash::TYPE_BROWSER_SHORTCUT;
1503         if (item.type != ash::TYPE_APP_SHORTCUT && !is_app_list && !is_chrome)
1504           continue;
1505         IDToItemControllerMap::const_iterator entry =
1506             id_to_item_controller_map_.find(item.id);
1507         if ((kAppShelfIdPlaceholder == *pref_app_id && is_app_list) ||
1508             (extension_misc::kChromeAppId == *pref_app_id && is_chrome) ||
1509             (entry != id_to_item_controller_map_.end() &&
1510              entry->second->app_id() == *pref_app_id)) {
1511           // Check if an item needs to be moved here.
1512           MoveChromeOrApplistToFinalPosition(
1513               is_chrome, is_app_list, index, &chrome_index, &app_list_index);
1514           ++pref_app_id;
1515           break;
1516         } else {
1517           if (is_chrome || is_app_list) {
1518             // We cannot delete any of these shortcuts. As such we remember
1519             // their positions and move them later where they belong.
1520             if (is_chrome)
1521               chrome_index = index;
1522             else
1523               app_list_index = index;
1524             // And skip the item - or exit the loop if end is reached (note that
1525             // in that case we will reduce the index again by one and this only
1526             // compensates for it).
1527             if (index >= max_index - 1)
1528               break;
1529             ++index;
1530           } else {
1531             // Check if this is a platform or a windowed app.
1532             if (item.type == ash::TYPE_APP_SHORTCUT &&
1533                 (id_to_item_controller_map_[item.id]->locked() ||
1534                  id_to_item_controller_map_[item.id]->type() ==
1535                      LauncherItemController::TYPE_APP)) {
1536               // Note: This will not change the amount of items (|max_index|).
1537               // Even changes to the actual |index| due to item weighting
1538               // changes should be fine.
1539               UnpinRunningAppInternal(index);
1540             } else {
1541               LauncherItemClosed(item.id);
1542               --max_index;
1543             }
1544           }
1545           --index;
1546         }
1547       }
1548       // If the item wasn't found, that means id_to_item_controller_map_
1549       // is out of sync.
1550       DCHECK(index <= max_index);
1551     } else {
1552       // Check if the item was already running but not yet pinned.
1553       ash::ShelfID shelf_id = GetShelfIDForAppID(*pref_app_id);
1554       if (shelf_id) {
1555         // This app is running but not yet pinned. So pin and move it.
1556         index = PinRunningAppInternal(index, shelf_id);
1557       } else {
1558         // This app wasn't pinned before, insert a new entry.
1559         shelf_id = CreateAppShortcutLauncherItem(*pref_app_id, index);
1560         ++max_index;
1561         index = model_->ItemIndexByID(shelf_id);
1562       }
1563       ++pref_app_id;
1564     }
1565   }
1566
1567   // Remove any trailing existing items.
1568   while (index < model_->item_count()) {
1569     const ash::ShelfItem& item(model_->items()[index]);
1570     if (item.type == ash::TYPE_APP_SHORTCUT) {
1571       if (id_to_item_controller_map_[item.id]->locked() ||
1572           id_to_item_controller_map_[item.id]->type() ==
1573               LauncherItemController::TYPE_APP)
1574         UnpinRunningAppInternal(index);
1575       else
1576         LauncherItemClosed(item.id);
1577     } else {
1578       if (item.type == ash::TYPE_BROWSER_SHORTCUT)
1579         chrome_index = index;
1580       else if (item.type == ash::TYPE_APP_LIST)
1581         app_list_index = index;
1582       ++index;
1583     }
1584   }
1585
1586   // Append unprocessed items from the pref to the end of the model.
1587   for (; pref_app_id != pinned_apps.end(); ++pref_app_id) {
1588     // All items but the chrome and / or app list shortcut needs to be added.
1589     bool is_chrome = *pref_app_id == extension_misc::kChromeAppId;
1590     bool is_app_list = *pref_app_id == kAppShelfIdPlaceholder;
1591     // Coming here we know the next item which can be finalized, either the
1592     // chrome item or the app launcher. The final position is the end of the
1593     // list. The menu model will make sure that the item is grouped according
1594     // to its weight (which we do not know here).
1595     if (!is_chrome && !is_app_list) {
1596       DoPinAppWithID(*pref_app_id);
1597       int target_index = FindInsertionPoint(false);
1598       ash::ShelfID id = GetShelfIDForAppID(*pref_app_id);
1599       int source_index = model_->ItemIndexByID(id);
1600       if (source_index != target_index)
1601         model_->Move(source_index, target_index);
1602
1603       // Needed for the old layout - the weight might force it to be lower in
1604       // rank.
1605       if (app_list_index != -1 && target_index <= app_list_index)
1606         ++app_list_index;
1607     } else {
1608       int target_index = FindInsertionPoint(is_app_list);
1609       MoveChromeOrApplistToFinalPosition(
1610           is_chrome, is_app_list, target_index, &chrome_index, &app_list_index);
1611     }
1612   }
1613 }
1614
1615 void ChromeLauncherController::SetShelfAutoHideBehaviorPrefs(
1616     ash::ShelfAutoHideBehavior behavior,
1617     aura::Window* root_window) {
1618   const char* value = NULL;
1619   switch (behavior) {
1620     case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
1621       value = ash::kShelfAutoHideBehaviorAlways;
1622       break;
1623     case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
1624       value = ash::kShelfAutoHideBehaviorNever;
1625       break;
1626     case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
1627       // This one should not be a valid preference option for now. We only want
1628       // to completely hide it when we run in app mode - or while we temporarily
1629       // hide the shelf as part of an animation (e.g. the multi user change).
1630       return;
1631   }
1632
1633   UpdatePerDisplayPref(
1634       profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value);
1635
1636   if (root_window == ash::Shell::GetPrimaryRootWindow()) {
1637     // See comment in |kShelfAlignment| about why we have two prefs here.
1638     profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
1639     profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
1640   }
1641 }
1642
1643 void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() {
1644   aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1645
1646   for (aura::Window::Windows::const_iterator iter = root_windows.begin();
1647        iter != root_windows.end(); ++iter) {
1648     ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
1649         GetShelfAutoHideBehavior(*iter), *iter);
1650   }
1651 }
1652
1653 void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
1654   if (!ash::ShelfWidget::ShelfAlignmentAllowed())
1655     return;
1656
1657   aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
1658
1659   for (aura::Window::Windows::const_iterator iter = root_windows.begin();
1660        iter != root_windows.end(); ++iter) {
1661     // See comment in |kShelfAlignment| as to why we consider two prefs.
1662     const std::string alignment_value(
1663         GetPrefForRootWindow(profile_->GetPrefs(),
1664                              *iter,
1665                              prefs::kShelfAlignmentLocal,
1666                              prefs::kShelfAlignment));
1667     ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
1668     if (alignment_value == ash::kShelfAlignmentLeft)
1669       alignment = ash::SHELF_ALIGNMENT_LEFT;
1670     else if (alignment_value == ash::kShelfAlignmentRight)
1671       alignment = ash::SHELF_ALIGNMENT_RIGHT;
1672     else if (alignment_value == ash::kShelfAlignmentTop)
1673       alignment = ash::SHELF_ALIGNMENT_TOP;
1674     ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter);
1675   }
1676 }
1677
1678 void ChromeLauncherController::SetShelfBehaviorsFromPrefs() {
1679   SetShelfAutoHideBehaviorFromPrefs();
1680   SetShelfAlignmentFromPrefs();
1681 }
1682
1683 ash::ShelfItemStatus ChromeLauncherController::GetAppState(
1684     const::std::string& app_id) {
1685   ash::ShelfItemStatus status = ash::STATUS_CLOSED;
1686   for (WebContentsToAppIDMap::iterator it = web_contents_to_app_id_.begin();
1687        it != web_contents_to_app_id_.end();
1688        ++it) {
1689     if (it->second == app_id) {
1690       Browser* browser = chrome::FindBrowserWithWebContents(it->first);
1691       // Usually there should never be an item in our |web_contents_to_app_id_|
1692       // list which got deleted already. However - in some situations e.g.
1693       // Browser::SwapTabContent there is temporarily no associated browser.
1694       if (!browser)
1695         continue;
1696       if (browser->window()->IsActive()) {
1697         return browser->tab_strip_model()->GetActiveWebContents() == it->first ?
1698             ash::STATUS_ACTIVE : ash::STATUS_RUNNING;
1699       } else {
1700         status = ash::STATUS_RUNNING;
1701       }
1702     }
1703   }
1704   return status;
1705 }
1706
1707 ash::ShelfID ChromeLauncherController::InsertAppLauncherItem(
1708     LauncherItemController* controller,
1709     const std::string& app_id,
1710     ash::ShelfItemStatus status,
1711     int index,
1712     ash::ShelfItemType shelf_item_type) {
1713   ash::ShelfID id = model_->next_id();
1714   CHECK(!HasItemController(id));
1715   CHECK(controller);
1716   id_to_item_controller_map_[id] = controller;
1717   controller->set_shelf_id(id);
1718
1719   ash::ShelfItem item;
1720   item.type = shelf_item_type;
1721   item.image = extensions::util::GetDefaultAppIcon();
1722
1723   ash::ShelfItemStatus new_state = GetAppState(app_id);
1724   if (new_state != ash::STATUS_CLOSED)
1725     status = new_state;
1726
1727   item.status = status;
1728
1729   model_->AddAt(index, item);
1730
1731   app_icon_loader_->FetchImage(app_id);
1732
1733   SetShelfItemDelegate(id, controller);
1734
1735   return id;
1736 }
1737
1738 bool ChromeLauncherController::HasItemController(ash::ShelfID id) const {
1739   return id_to_item_controller_map_.find(id) !=
1740          id_to_item_controller_map_.end();
1741 }
1742
1743 std::vector<content::WebContents*>
1744 ChromeLauncherController::GetV1ApplicationsFromController(
1745     LauncherItemController* controller) {
1746   DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT);
1747   AppShortcutLauncherItemController* app_controller =
1748       static_cast<AppShortcutLauncherItemController*>(controller);
1749   return app_controller->GetRunningApplications();
1750 }
1751
1752 BrowserShortcutLauncherItemController*
1753 ChromeLauncherController::GetBrowserShortcutLauncherItemController() {
1754   for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
1755       i != id_to_item_controller_map_.end(); ++i) {
1756     int index = model_->ItemIndexByID(i->first);
1757     const ash::ShelfItem& item = model_->items()[index];
1758     if (item.type == ash::TYPE_BROWSER_SHORTCUT)
1759       return static_cast<BrowserShortcutLauncherItemController*>(i->second);
1760   }
1761   // Create a LauncherItemController for the Browser shortcut if it does not
1762   // exist yet.
1763   ash::ShelfID id = CreateBrowserShortcutLauncherItem();
1764   DCHECK(id_to_item_controller_map_[id]);
1765   return static_cast<BrowserShortcutLauncherItemController*>(
1766       id_to_item_controller_map_[id]);
1767 }
1768
1769 ash::ShelfID ChromeLauncherController::CreateBrowserShortcutLauncherItem() {
1770   ash::ShelfItem browser_shortcut;
1771   browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT;
1772   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
1773   browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32);
1774   ash::ShelfID id = model_->next_id();
1775   size_t index = GetChromeIconIndexForCreation();
1776   model_->AddAt(index, browser_shortcut);
1777   id_to_item_controller_map_[id] =
1778       new BrowserShortcutLauncherItemController(this);
1779   id_to_item_controller_map_[id]->set_shelf_id(id);
1780   // ShelfItemDelegateManager owns BrowserShortcutLauncherItemController.
1781   SetShelfItemDelegate(id, id_to_item_controller_map_[id]);
1782   return id;
1783 }
1784
1785 void ChromeLauncherController::PersistChromeItemIndex(int index) {
1786   profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index);
1787 }
1788
1789 int ChromeLauncherController::GetChromeIconIndexFromPref() const {
1790   size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex);
1791   const base::ListValue* pinned_apps_pref =
1792       profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
1793   return std::max(static_cast<size_t>(0),
1794                   std::min(pinned_apps_pref->GetSize(), index));
1795 }
1796
1797 void ChromeLauncherController::MoveChromeOrApplistToFinalPosition(
1798     bool is_chrome,
1799     bool is_app_list,
1800     int target_index,
1801     int* chrome_index,
1802     int* app_list_index) {
1803   if (is_chrome && *chrome_index != -1) {
1804     model_->Move(*chrome_index, target_index);
1805     if (*app_list_index != -1 &&
1806         *chrome_index < *app_list_index &&
1807         target_index > *app_list_index)
1808       --(*app_list_index);
1809     *chrome_index = -1;
1810   } else if (is_app_list && *app_list_index != -1) {
1811     model_->Move(*app_list_index, target_index);
1812     if (*chrome_index != -1 &&
1813         *app_list_index < *chrome_index &&
1814         target_index > *chrome_index)
1815       --(*chrome_index);
1816     *app_list_index = -1;
1817   }
1818 }
1819
1820 int ChromeLauncherController::FindInsertionPoint(bool is_app_list) {
1821   // Keeping this change small to backport to M33&32 (see crbug.com/329597).
1822   // TODO(skuhne): With the removal of the legacy shelf layout we should remove
1823   // the ability to move the app list item since this was never used. We should
1824   // instead ask the ShelfModel::ValidateInsertionIndex or similir for an index.
1825   if (is_app_list)
1826     return 0;
1827
1828   for (int i = model_->item_count() - 1; i > 0; --i) {
1829     ash::ShelfItemType type = model_->items()[i].type;
1830     if (type == ash::TYPE_APP_SHORTCUT ||
1831         (is_app_list && type == ash::TYPE_APP_LIST) ||
1832         type == ash::TYPE_BROWSER_SHORTCUT) {
1833       return i;
1834     }
1835   }
1836   return 0;
1837 }
1838
1839 int ChromeLauncherController::GetChromeIconIndexForCreation() {
1840   // We get the list of pinned apps as they currently would get pinned.
1841   // Within this list the chrome icon will be the correct location.
1842   std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser();
1843
1844   std::vector<std::string>::iterator it =
1845       std::find(pinned_apps.begin(),
1846                 pinned_apps.end(),
1847                 std::string(extension_misc::kChromeAppId));
1848   DCHECK(it != pinned_apps.end());
1849   int index = it - pinned_apps.begin();
1850
1851   // We should do here a comparison between the is state and the "want to be"
1852   // state since some apps might be able to pin but are not yet. Instead - for
1853   // the time being we clamp against the amount of known items and wait for the
1854   // next |UpdateAppLaunchersFromPref()| call to correct it - it will come since
1855   // the pinning will be done then.
1856   return std::min(model_->item_count(), index);
1857 }
1858
1859 std::vector<std::string>
1860 ChromeLauncherController::GetListOfPinnedAppsAndBrowser() {
1861   // Adding the app list item to the list of items requires that the ID is not
1862   // a valid and known ID for the extension system. The ID was constructed that
1863   // way - but just to make sure...
1864   DCHECK(!app_tab_helper_->IsValidIDForCurrentUser(kAppShelfIdPlaceholder));
1865
1866   std::vector<std::string> pinned_apps;
1867
1868   // Get the new incarnation of the list.
1869   const base::ListValue* pinned_apps_pref =
1870       profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
1871
1872   // Keep track of the addition of the chrome and the app list icon.
1873   bool chrome_icon_added = false;
1874   bool app_list_icon_added = false;
1875   size_t chrome_icon_index = GetChromeIconIndexFromPref();
1876
1877   // See if the chrome string is already in the pinned list and remove it if
1878   // needed.
1879   base::Value* chrome_app = ash::CreateAppDict(extension_misc::kChromeAppId);
1880   if (chrome_app) {
1881     chrome_icon_added = pinned_apps_pref->Find(*chrome_app) !=
1882         pinned_apps_pref->end();
1883     delete chrome_app;
1884   }
1885
1886   for (size_t index = 0; index < pinned_apps_pref->GetSize(); ++index) {
1887     // We need to position the chrome icon relative to it's place in the pinned
1888     // preference list - even if an item of that list isn't shown yet.
1889     if (index == chrome_icon_index && !chrome_icon_added) {
1890       pinned_apps.push_back(extension_misc::kChromeAppId);
1891       chrome_icon_added = true;
1892     }
1893     const base::DictionaryValue* app = NULL;
1894     std::string app_id;
1895     if (pinned_apps_pref->GetDictionary(index, &app) &&
1896         app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
1897         (std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
1898              pinned_apps.end())) {
1899       if (app_id == extension_misc::kChromeAppId) {
1900         chrome_icon_added = true;
1901         pinned_apps.push_back(extension_misc::kChromeAppId);
1902       } else if (app_id == kAppShelfIdPlaceholder) {
1903         app_list_icon_added = true;
1904         pinned_apps.push_back(kAppShelfIdPlaceholder);
1905       } else if (app_tab_helper_->IsValidIDForCurrentUser(app_id)) {
1906         // Note: In multi profile scenarios we only want to show pinnable apps
1907         // here which is correct. Running applications from the other users will
1908         // continue to run. So no need for multi profile modifications.
1909         pinned_apps.push_back(app_id);
1910       }
1911     }
1912   }
1913
1914   // If not added yet, the chrome item will be the last item in the list.
1915   if (!chrome_icon_added)
1916     pinned_apps.push_back(extension_misc::kChromeAppId);
1917
1918   // If not added yet, add the app list item either at the end or at the
1919   // beginning - depending on the shelf layout.
1920   if (!app_list_icon_added) {
1921     pinned_apps.insert(pinned_apps.begin(), kAppShelfIdPlaceholder);
1922   }
1923   return pinned_apps;
1924 }
1925
1926 bool ChromeLauncherController::IsIncognito(
1927     const content::WebContents* web_contents) const {
1928   const Profile* profile =
1929       Profile::FromBrowserContext(web_contents->GetBrowserContext());
1930   return profile->IsOffTheRecord() && !profile->IsGuestSession();
1931 }
1932
1933 void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension(
1934     const std::string& app_id) {
1935   // This function cannot rely on the controller's enumeration functionality
1936   // since the extension has already be unloaded.
1937   const BrowserList* ash_browser_list =
1938       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
1939   std::vector<Browser*> browser_to_close;
1940   for (BrowserList::const_reverse_iterator
1941            it = ash_browser_list->begin_last_active();
1942        it != ash_browser_list->end_last_active(); ++it) {
1943     Browser* browser = *it;
1944     if (!browser->is_type_tabbed() &&
1945         browser->is_type_popup() &&
1946         browser->is_app() &&
1947         app_id == web_app::GetExtensionIdFromApplicationName(
1948             browser->app_name())) {
1949       browser_to_close.push_back(browser);
1950     }
1951   }
1952   while (!browser_to_close.empty()) {
1953     TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model();
1954     tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE);
1955     browser_to_close.pop_back();
1956   }
1957 }
1958
1959 void ChromeLauncherController::SetShelfItemDelegate(
1960     ash::ShelfID id,
1961     ash::ShelfItemDelegate* item_delegate) {
1962   DCHECK_GT(id, 0);
1963   DCHECK(item_delegate);
1964   DCHECK(item_delegate_manager_);
1965   item_delegate_manager_->SetShelfItemDelegate(
1966       id, scoped_ptr<ash::ShelfItemDelegate>(item_delegate).Pass());
1967 }
1968
1969 void ChromeLauncherController::AttachProfile(Profile* profile) {
1970   profile_ = profile;
1971   // Either add the profile to the list of known profiles and make it the active
1972   // one for some functions of AppTabHelper or create a new one.
1973   if (!app_tab_helper_.get())
1974     app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
1975   else
1976     app_tab_helper_->SetCurrentUser(profile_);
1977   // TODO(skuhne): The AppIconLoaderImpl has the same problem. Each loaded
1978   // image is associated with a profile (it's loader requires the profile).
1979   // Since icon size changes are possible, the icon could be requested to be
1980   // reloaded. However - having it not multi profile aware would cause problems
1981   // if the icon cache gets deleted upon user switch.
1982   app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
1983       profile_, extension_misc::EXTENSION_ICON_SMALL, this));
1984
1985   pref_change_registrar_.Init(profile_->GetPrefs());
1986   pref_change_registrar_.Add(
1987       prefs::kPinnedLauncherApps,
1988       base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref,
1989                  base::Unretained(this)));
1990   pref_change_registrar_.Add(
1991       prefs::kShelfAlignmentLocal,
1992       base::Bind(&ChromeLauncherController::SetShelfAlignmentFromPrefs,
1993                  base::Unretained(this)));
1994   pref_change_registrar_.Add(
1995       prefs::kShelfAutoHideBehaviorLocal,
1996       base::Bind(&ChromeLauncherController::
1997                      SetShelfAutoHideBehaviorFromPrefs,
1998                  base::Unretained(this)));
1999   pref_change_registrar_.Add(
2000       prefs::kShelfPreferences,
2001       base::Bind(&ChromeLauncherController::SetShelfBehaviorsFromPrefs,
2002                  base::Unretained(this)));
2003 }
2004
2005 void ChromeLauncherController::ReleaseProfile() {
2006   if (app_sync_ui_state_)
2007     app_sync_ui_state_->RemoveObserver(this);
2008
2009   PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
2010
2011   pref_change_registrar_.RemoveAll();
2012 }