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