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