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