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