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