Home | History | Annotate | Download | only in launcher
      1 // Copyright (c) 2012 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_per_browser.h"
      6 
      7 #include <vector>
      8 
      9 #include "ash/ash_switches.h"
     10 #include "ash/launcher/launcher_model.h"
     11 #include "ash/root_window_controller.h"
     12 #include "ash/shelf/shelf_layout_manager.h"
     13 #include "ash/shelf/shelf_widget.h"
     14 #include "ash/shell.h"
     15 #include "ash/wm/window_util.h"
     16 #include "base/command_line.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/values.h"
     19 #include "chrome/browser/app_mode/app_mode_utils.h"
     20 #include "chrome/browser/defaults.h"
     21 #include "chrome/browser/chrome_notification_types.h"
     22 #include "chrome/browser/extensions/app_icon_loader_impl.h"
     23 #include "chrome/browser/extensions/extension_service.h"
     24 #include "chrome/browser/extensions/extension_system.h"
     25 #include "chrome/browser/prefs/incognito_mode_prefs.h"
     26 #include "chrome/browser/prefs/pref_service_syncable.h"
     27 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     28 #include "chrome/browser/profiles/profile.h"
     29 #include "chrome/browser/profiles/profile_manager.h"
     30 #include "chrome/browser/ui/ash/app_sync_ui_state.h"
     31 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
     32 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
     33 #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h"
     34 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
     35 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
     36 #include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h"
     37 #include "chrome/browser/ui/browser.h"
     38 #include "chrome/browser/ui/browser_commands.h"
     39 #include "chrome/browser/ui/browser_finder.h"
     40 #include "chrome/browser/ui/browser_tabstrip.h"
     41 #include "chrome/browser/ui/browser_window.h"
     42 #include "chrome/browser/ui/extensions/application_launch.h"
     43 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     44 #include "chrome/browser/ui/host_desktop.h"
     45 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     46 #include "chrome/browser/web_applications/web_app.h"
     47 #include "chrome/common/chrome_switches.h"
     48 #include "chrome/common/extensions/extension.h"
     49 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     50 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     51 #include "chrome/common/pref_names.h"
     52 #include "chrome/common/url_constants.h"
     53 #include "content/public/browser/navigation_entry.h"
     54 #include "content/public/browser/notification_service.h"
     55 #include "content/public/browser/web_contents.h"
     56 #include "extensions/common/url_pattern.h"
     57 #include "grit/chromium_strings.h"
     58 #include "grit/theme_resources.h"
     59 #include "ui/aura/root_window.h"
     60 #include "ui/aura/window.h"
     61 #include "ui/base/l10n/l10n_util.h"
     62 
     63 #if defined(OS_CHROMEOS)
     64 #include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h"
     65 #endif
     66 
     67 using content::WebContents;
     68 using extensions::Extension;
     69 
     70 namespace {
     71 
     72 // Item controller for an app shortcut. Shortcuts track app and launcher ids,
     73 // but do not have any associated windows (opening a shortcut will replace the
     74 // item with the appropriate LauncherItemController type).
     75 class AppShortcutLauncherItemController : public LauncherItemController {
     76  public:
     77   AppShortcutLauncherItemController(
     78       const std::string& app_id,
     79       ChromeLauncherControllerPerBrowser* controller)
     80       : LauncherItemController(TYPE_SHORTCUT, app_id, controller) {
     81     // Google Drive should just refocus to it's main app UI.
     82     // TODO(davemoore): Generalize this for other applications.
     83     if (app_id == "apdfllckaahabafndbhieahigkjlhalf") {
     84       const Extension* extension =
     85           launcher_controller()->GetExtensionForAppID(app_id);
     86       refocus_url_ = GURL(
     87           extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*");
     88     }
     89   }
     90 
     91   virtual ~AppShortcutLauncherItemController() {}
     92 
     93   // LauncherItemController overrides:
     94   virtual string16 GetTitle() OVERRIDE {
     95     return GetAppTitle();
     96   }
     97 
     98   virtual bool HasWindow(aura::Window* window) const OVERRIDE {
     99     return false;
    100   }
    101 
    102   virtual bool IsOpen() const OVERRIDE {
    103     return false;
    104   }
    105 
    106   virtual bool IsVisible() const OVERRIDE {
    107     return false;
    108   }
    109 
    110   virtual void Launch(int event_flags) OVERRIDE {
    111     launcher_controller()->LaunchApp(app_id(), event_flags);
    112   }
    113 
    114   virtual void Activate() OVERRIDE {
    115     launcher_controller()->ActivateApp(app_id(), ui::EF_NONE);
    116   }
    117 
    118   virtual void Close() OVERRIDE {
    119     // TODO: maybe should treat as unpin?
    120   }
    121 
    122   virtual void Clicked(const ui::Event& event) OVERRIDE {
    123     Activate();
    124   }
    125 
    126   virtual void OnRemoved() OVERRIDE {
    127     // AppShortcutLauncherItemController is unowned; delete on removal.
    128     delete this;
    129   }
    130 
    131   virtual void LauncherItemChanged(
    132       int model_index,
    133       const ash::LauncherItem& old_item) OVERRIDE {
    134   }
    135 
    136   virtual ChromeLauncherAppMenuItems GetApplicationList(
    137       int event_flags) OVERRIDE {
    138     ChromeLauncherAppMenuItems items;
    139     return items.Pass();
    140   }
    141 
    142   // Stores the optional refocus url pattern for this item.
    143   const GURL& refocus_url() const { return refocus_url_; }
    144   void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; }
    145 
    146  private:
    147   GURL refocus_url_;
    148   DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController);
    149 };
    150 
    151 std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) {
    152   gfx::Display display = gfx::Screen::GetScreenFor(
    153       root_window)->GetDisplayNearestWindow(root_window);
    154   DCHECK(display.is_valid());
    155 
    156   return base::Int64ToString(display.id());
    157 }
    158 
    159 void UpdatePerDisplayPref(PrefService* pref_service,
    160                           aura::RootWindow* root_window,
    161                           const char* pref_key,
    162                           const std::string& value) {
    163   std::string key = GetPrefKeyForRootWindow(root_window);
    164   if (key.empty())
    165     return;
    166 
    167   DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences);
    168   base::DictionaryValue* shelf_prefs = update.Get();
    169   base::DictionaryValue* prefs = NULL;
    170   if (!shelf_prefs->GetDictionary(key, &prefs)) {
    171     prefs = new base::DictionaryValue();
    172     shelf_prefs->Set(key, prefs);
    173   }
    174   prefs->SetStringWithoutPathExpansion(pref_key, value);
    175 }
    176 
    177 // Returns a pref value in |pref_service| for the display of |root_window|. The
    178 // pref value is stored in |local_path| and |path|, but |pref_service| may have
    179 // per-display preferences and the value can be specified by policy. Here is
    180 // the priority:
    181 //  * A value managed by policy. This is a single value that applies to all
    182 //    displays.
    183 //  * A user-set value for the specified display.
    184 //  * A user-set value in |local_path| or |path|, if no per-display settings are
    185 //    ever specified (see http://crbug.com/173719 for why). |local_path| is
    186 //    preferred. See comment in |kShelfAlignment| as to why we consider two
    187 //    prefs and why |local_path| is preferred.
    188 //  * A value recommended by policy. This is a single value that applies to all
    189 //    root windows.
    190 //  * The default value for |local_path| if the value is not recommended by
    191 //    policy.
    192 std::string GetPrefForRootWindow(PrefService* pref_service,
    193                                  aura::RootWindow* root_window,
    194                                  const char* local_path,
    195                                  const char* path) {
    196   const PrefService::Preference* local_pref =
    197       pref_service->FindPreference(local_path);
    198   const std::string value(pref_service->GetString(local_path));
    199   if (local_pref->IsManaged())
    200     return value;
    201 
    202   std::string pref_key = GetPrefKeyForRootWindow(root_window);
    203   bool has_per_display_prefs = false;
    204   if (!pref_key.empty()) {
    205     const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary(
    206         prefs::kShelfPreferences);
    207     const base::DictionaryValue* display_pref = NULL;
    208     std::string per_display_value;
    209     if (shelf_prefs->GetDictionary(pref_key, &display_pref) &&
    210         display_pref->GetString(path, &per_display_value))
    211       return per_display_value;
    212 
    213     // If the pref for the specified display is not found, scan the whole prefs
    214     // and check if the prefs for other display is already specified.
    215     std::string unused_value;
    216     for (base::DictionaryValue::Iterator iter(*shelf_prefs);
    217          !iter.IsAtEnd(); iter.Advance()) {
    218       const base::DictionaryValue* display_pref = NULL;
    219       if (iter.value().GetAsDictionary(&display_pref) &&
    220           display_pref->GetString(path, &unused_value)) {
    221         has_per_display_prefs = true;
    222         break;
    223       }
    224     }
    225   }
    226 
    227   if (local_pref->IsRecommended() || !has_per_display_prefs)
    228     return value;
    229 
    230   const base::Value* default_value =
    231       pref_service->GetDefaultPrefValue(local_path);
    232   std::string default_string;
    233   default_value->GetAsString(&default_string);
    234   return default_string;
    235 }
    236 
    237 // If prefs have synced and no user-set value exists at |local_path|, the value
    238 // from |synced_path| is copied to |local_path|.
    239 void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service,
    240                                const char* local_path,
    241                                const char* synced_path) {
    242   if (!pref_service->FindPreference(local_path)->HasUserSetting() &&
    243       pref_service->IsSyncing()) {
    244     // First time the user is using this machine, propagate from remote to
    245     // local.
    246     pref_service->SetString(local_path, pref_service->GetString(synced_path));
    247   }
    248 }
    249 
    250 }  // namespace
    251 
    252 // ChromeLauncherControllerPerBrowser -----------------------------------------
    253 
    254 ChromeLauncherControllerPerBrowser::ChromeLauncherControllerPerBrowser(
    255     Profile* profile,
    256     ash::LauncherModel* model)
    257     : model_(model),
    258       profile_(profile),
    259       app_sync_ui_state_(NULL),
    260       ignore_persist_pinned_state_change_(false) {
    261   if (!profile_) {
    262     // Use the original profile as on chromeos we may get a temporary off the
    263     // record profile.
    264     profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile();
    265 
    266     app_sync_ui_state_ = AppSyncUIState::Get(profile_);
    267     if (app_sync_ui_state_)
    268       app_sync_ui_state_->AddObserver(this);
    269   }
    270 
    271   model_->AddObserver(this);
    272   // Right now ash::Shell isn't created for tests.
    273   // TODO(mukai): Allows it to observe display change and write tests.
    274   if (ash::Shell::HasInstance())
    275     ash::Shell::GetInstance()->display_controller()->AddObserver(this);
    276   // TODO(stevenjb): Find a better owner for shell_window_controller_?
    277   shell_window_controller_.reset(new ShellWindowLauncherController(this));
    278   app_tab_helper_.reset(new LauncherAppTabHelper(profile_));
    279   app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
    280       profile_, extension_misc::EXTENSION_ICON_SMALL, this));
    281 
    282   notification_registrar_.Add(this,
    283                               chrome::NOTIFICATION_EXTENSION_LOADED,
    284                               content::Source<Profile>(profile_));
    285   notification_registrar_.Add(this,
    286                               chrome::NOTIFICATION_EXTENSION_UNLOADED,
    287                               content::Source<Profile>(profile_));
    288   pref_change_registrar_.Init(profile_->GetPrefs());
    289   pref_change_registrar_.Add(
    290       prefs::kPinnedLauncherApps,
    291       base::Bind(&ChromeLauncherControllerPerBrowser::
    292                      UpdateAppLaunchersFromPref,
    293                  base::Unretained(this)));
    294   pref_change_registrar_.Add(
    295       prefs::kShelfAlignmentLocal,
    296       base::Bind(&ChromeLauncherControllerPerBrowser::
    297                      SetShelfAlignmentFromPrefs,
    298                  base::Unretained(this)));
    299   pref_change_registrar_.Add(
    300       prefs::kShelfAutoHideBehaviorLocal,
    301       base::Bind(&ChromeLauncherControllerPerBrowser::
    302                      SetShelfAutoHideBehaviorFromPrefs,
    303                  base::Unretained(this)));
    304   pref_change_registrar_.Add(
    305       prefs::kShelfPreferences,
    306       base::Bind(&ChromeLauncherControllerPerBrowser::
    307                      SetShelfBehaviorsFromPrefs,
    308                  base::Unretained(this)));
    309 }
    310 
    311 ChromeLauncherControllerPerBrowser::~ChromeLauncherControllerPerBrowser() {
    312   // Reset the shell window controller here since it has a weak pointer to
    313   // this.
    314   shell_window_controller_.reset();
    315 
    316   for (std::set<ash::Launcher*>::iterator iter = launchers_.begin();
    317        iter != launchers_.end();
    318        ++iter)
    319     (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this);
    320 
    321   model_->RemoveObserver(this);
    322   if (ash::Shell::HasInstance())
    323     ash::Shell::GetInstance()->display_controller()->RemoveObserver(this);
    324   for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
    325        i != id_to_item_controller_map_.end(); ++i) {
    326     i->second->OnRemoved();
    327     model_->RemoveItemAt(model_->ItemIndexByID(i->first));
    328   }
    329 
    330   if (ash::Shell::HasInstance())
    331     ash::Shell::GetInstance()->RemoveShellObserver(this);
    332 
    333   if (app_sync_ui_state_)
    334     app_sync_ui_state_->RemoveObserver(this);
    335 
    336   PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this);
    337 }
    338 
    339 void ChromeLauncherControllerPerBrowser::Init() {
    340   UpdateAppLaunchersFromPref();
    341   CreateBrowserShortcutLauncherItem();
    342 
    343   // TODO(sky): update unit test so that this test isn't necessary.
    344   if (ash::Shell::HasInstance()) {
    345     SetShelfAutoHideBehaviorFromPrefs();
    346     SetShelfAlignmentFromPrefs();
    347     PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
    348     if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() ||
    349         !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)->
    350             HasUserSetting()) {
    351       // This causes OnIsSyncingChanged to be called when the value of
    352       // PrefService::IsSyncing() changes.
    353       prefs->AddObserver(this);
    354     }
    355     ash::Shell::GetInstance()->AddShellObserver(this);
    356   }
    357 }
    358 
    359 ChromeLauncherControllerPerApp*
    360 ChromeLauncherControllerPerBrowser::GetPerAppInterface() {
    361   return NULL;
    362 }
    363 
    364 ash::LauncherID ChromeLauncherControllerPerBrowser::CreateTabbedLauncherItem(
    365     LauncherItemController* controller,
    366     IncognitoState is_incognito,
    367     ash::LauncherItemStatus status) {
    368   ash::LauncherID id = model_->next_id();
    369   DCHECK(!HasItemController(id));
    370   DCHECK(controller);
    371   id_to_item_controller_map_[id] = controller;
    372   controller->set_launcher_id(id);
    373 
    374   ash::LauncherItem item;
    375   item.type = ash::TYPE_TABBED;
    376   item.is_incognito = (is_incognito == STATE_INCOGNITO);
    377   item.status = status;
    378   model_->Add(item);
    379   return id;
    380 }
    381 
    382 ash::LauncherID ChromeLauncherControllerPerBrowser::CreateAppLauncherItem(
    383     LauncherItemController* controller,
    384     const std::string& app_id,
    385     ash::LauncherItemStatus status) {
    386   DCHECK(controller);
    387   int index = 0;
    388   // Panels are inserted on the left so as not to push all existing panels over.
    389   if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) {
    390     index = model_->item_count();
    391     // For the alternate shelf layout increment index (insert after app icon).
    392     if (ash::switches::UseAlternateShelfLayout())
    393       ++index;
    394   }
    395   return InsertAppLauncherItem(controller, app_id, status, index);
    396 }
    397 
    398 void ChromeLauncherControllerPerBrowser::SetItemStatus(
    399     ash::LauncherID id,
    400     ash::LauncherItemStatus status) {
    401   int index = model_->ItemIndexByID(id);
    402   DCHECK_GE(index, 0);
    403   ash::LauncherItem item = model_->items()[index];
    404   item.status = status;
    405   model_->Set(index, item);
    406 }
    407 
    408 void ChromeLauncherControllerPerBrowser::SetItemController(
    409     ash::LauncherID id,
    410     LauncherItemController* controller) {
    411   IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
    412   DCHECK(iter != id_to_item_controller_map_.end());
    413   iter->second->OnRemoved();
    414   iter->second = controller;
    415   controller->set_launcher_id(id);
    416 }
    417 
    418 void ChromeLauncherControllerPerBrowser::CloseLauncherItem(
    419     ash::LauncherID id) {
    420   if (IsPinned(id)) {
    421     // Create a new shortcut controller.
    422     IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
    423     DCHECK(iter != id_to_item_controller_map_.end());
    424     SetItemStatus(id, ash::STATUS_CLOSED);
    425     std::string app_id = iter->second->app_id();
    426     iter->second->OnRemoved();
    427     iter->second = new AppShortcutLauncherItemController(app_id, this);
    428     iter->second->set_launcher_id(id);
    429   } else {
    430     LauncherItemClosed(id);
    431   }
    432 }
    433 
    434 void ChromeLauncherControllerPerBrowser::Unpin(ash::LauncherID id) {
    435   DCHECK(HasItemController(id));
    436 
    437   LauncherItemController* controller = id_to_item_controller_map_[id];
    438   if (controller->type() == LauncherItemController::TYPE_APP) {
    439     int index = model_->ItemIndexByID(id);
    440     ash::LauncherItem item = model_->items()[index];
    441     item.type = ash::TYPE_PLATFORM_APP;
    442     model_->Set(index, item);
    443   } else {
    444     LauncherItemClosed(id);
    445   }
    446   if (CanPin())
    447     PersistPinnedState();
    448 }
    449 
    450 void ChromeLauncherControllerPerBrowser::Pin(ash::LauncherID id) {
    451   DCHECK(HasItemController(id));
    452 
    453   int index = model_->ItemIndexByID(id);
    454   ash::LauncherItem item = model_->items()[index];
    455 
    456   if (item.type != ash::TYPE_PLATFORM_APP)
    457     return;
    458 
    459   item.type = ash::TYPE_APP_SHORTCUT;
    460   model_->Set(index, item);
    461 
    462   if (CanPin())
    463     PersistPinnedState();
    464 }
    465 
    466 bool ChromeLauncherControllerPerBrowser::IsPinned(ash::LauncherID id) {
    467   int index = model_->ItemIndexByID(id);
    468   ash::LauncherItemType type = model_->items()[index].type;
    469   return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT);
    470 }
    471 
    472 void ChromeLauncherControllerPerBrowser::TogglePinned(ash::LauncherID id) {
    473   if (!HasItemController(id))
    474     return;  // May happen if item closed with menu open.
    475 
    476   if (IsPinned(id))
    477     Unpin(id);
    478   else
    479     Pin(id);
    480 }
    481 
    482 bool ChromeLauncherControllerPerBrowser::IsPinnable(ash::LauncherID id) const {
    483   int index = model_->ItemIndexByID(id);
    484   if (index == -1)
    485     return false;
    486 
    487   ash::LauncherItemType type = model_->items()[index].type;
    488   return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) &&
    489           CanPin());
    490 }
    491 
    492 void ChromeLauncherControllerPerBrowser::LockV1AppWithID(
    493     const std::string& app_id) {
    494 }
    495 
    496 void ChromeLauncherControllerPerBrowser::UnlockV1AppWithID(
    497     const std::string& app_id) {
    498 }
    499 
    500 void ChromeLauncherControllerPerBrowser::Launch(
    501     ash::LauncherID id, int event_flags) {
    502   if (!HasItemController(id))
    503     return;  // In case invoked from menu and item closed while menu up.
    504   id_to_item_controller_map_[id]->Launch(event_flags);
    505 }
    506 
    507 void ChromeLauncherControllerPerBrowser::Close(ash::LauncherID id) {
    508   if (!HasItemController(id))
    509     return;  // May happen if menu closed.
    510   id_to_item_controller_map_[id]->Close();
    511 }
    512 
    513 bool ChromeLauncherControllerPerBrowser::IsOpen(ash::LauncherID id) {
    514   if (!HasItemController(id))
    515     return false;
    516   return id_to_item_controller_map_[id]->IsOpen();
    517 }
    518 
    519 bool ChromeLauncherControllerPerBrowser::IsPlatformApp(ash::LauncherID id) {
    520   if (!HasItemController(id))
    521     return false;
    522 
    523   std::string app_id = GetAppIDForLauncherID(id);
    524   const Extension* extension = GetExtensionForAppID(app_id);
    525   DCHECK(extension);
    526   return extension->is_platform_app();
    527 }
    528 
    529 void ChromeLauncherControllerPerBrowser::LaunchApp(const std::string& app_id,
    530                                                    int event_flags) {
    531   // |extension| could be NULL when it is being unloaded for updating.
    532   const Extension* extension = GetExtensionForAppID(app_id);
    533   if (!extension)
    534     return;
    535 
    536   const ExtensionService* service =
    537       extensions::ExtensionSystem::Get(profile_)->extension_service();
    538   if (!service->IsExtensionEnabledForLauncher(app_id)) {
    539     // Do nothing if there is already a running enable flow.
    540     if (extension_enable_flow_)
    541       return;
    542 
    543     extension_enable_flow_.reset(
    544         new ExtensionEnableFlow(profile_, app_id, this));
    545     extension_enable_flow_->StartForNativeWindow(NULL);
    546     return;
    547   }
    548 
    549   chrome::OpenApplication(chrome::AppLaunchParams(GetProfileForNewWindows(),
    550                                                   extension,
    551                                                   event_flags));
    552 }
    553 
    554 void ChromeLauncherControllerPerBrowser::ActivateApp(const std::string& app_id,
    555                                                      int event_flags) {
    556   if (app_id == extension_misc::kChromeAppId) {
    557     BrowserShortcutClicked(event_flags);
    558     return;
    559   }
    560 
    561   // If there is an existing non-shortcut controller for this app, open it.
    562   ash::LauncherID id = GetLauncherIDForAppID(app_id);
    563   URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
    564   refocus_pattern.SetMatchAllURLs(true);
    565 
    566   if (id > 0) {
    567     LauncherItemController* controller = id_to_item_controller_map_[id];
    568     if (controller->type() != LauncherItemController::TYPE_SHORTCUT) {
    569       controller->Activate();
    570       return;
    571     }
    572 
    573     AppShortcutLauncherItemController* app_controller =
    574         static_cast<AppShortcutLauncherItemController*>(controller);
    575     const GURL refocus_url = app_controller->refocus_url();
    576 
    577     if (!refocus_url.is_empty())
    578       refocus_pattern.Parse(refocus_url.spec());
    579   }
    580 
    581   // Check if there are any open tabs for this app.
    582   AppIDToWebContentsListMap::iterator app_i =
    583       app_id_to_web_contents_list_.find(app_id);
    584   if (app_i != app_id_to_web_contents_list_.end()) {
    585     for (WebContentsList::iterator tab_i = app_i->second.begin();
    586          tab_i != app_i->second.end();
    587          ++tab_i) {
    588       WebContents* tab = *tab_i;
    589       const GURL tab_url = tab->GetURL();
    590       if (refocus_pattern.MatchesURL(tab_url)) {
    591         Browser* browser = chrome::FindBrowserWithWebContents(tab);
    592         TabStripModel* tab_strip = browser->tab_strip_model();
    593         int index = tab_strip->GetIndexOfWebContents(tab);
    594         DCHECK_NE(TabStripModel::kNoTab, index);
    595         tab_strip->ActivateTabAt(index, false);
    596         browser->window()->Show();
    597         ash::wm::ActivateWindow(browser->window()->GetNativeWindow());
    598         return;
    599       }
    600     }
    601   }
    602 
    603   LaunchApp(app_id, event_flags);
    604 }
    605 
    606 extensions::ExtensionPrefs::LaunchType
    607     ChromeLauncherControllerPerBrowser::GetLaunchType(ash::LauncherID id) {
    608   DCHECK(HasItemController(id));
    609 
    610   const Extension* extension = GetExtensionForAppID(
    611       id_to_item_controller_map_[id]->app_id());
    612   return profile_->GetExtensionService()->extension_prefs()->GetLaunchType(
    613       extension,
    614       extensions::ExtensionPrefs::LAUNCH_DEFAULT);
    615 }
    616 
    617 std::string ChromeLauncherControllerPerBrowser::GetAppID(
    618     content::WebContents* tab) {
    619   return app_tab_helper_->GetAppID(tab);
    620 }
    621 
    622 ash::LauncherID ChromeLauncherControllerPerBrowser::GetLauncherIDForAppID(
    623     const std::string& app_id) {
    624   for (IDToItemControllerMap::const_iterator i =
    625            id_to_item_controller_map_.begin();
    626        i != id_to_item_controller_map_.end(); ++i) {
    627     if (i->second->type() == LauncherItemController::TYPE_APP_PANEL)
    628       continue;  // Don't include panels
    629     if (i->second->app_id() == app_id)
    630       return i->first;
    631   }
    632   return 0;
    633 }
    634 
    635 std::string ChromeLauncherControllerPerBrowser::GetAppIDForLauncherID(
    636     ash::LauncherID id) {
    637   DCHECK(HasItemController(id));
    638   return id_to_item_controller_map_[id]->app_id();
    639 }
    640 
    641 void ChromeLauncherControllerPerBrowser::SetAppImage(
    642     const std::string& id,
    643     const gfx::ImageSkia& image) {
    644   // TODO: need to get this working for shortcuts.
    645 
    646   for (IDToItemControllerMap::const_iterator i =
    647            id_to_item_controller_map_.begin();
    648        i != id_to_item_controller_map_.end(); ++i) {
    649     if (i->second->app_id() != id)
    650       continue;
    651 
    652     int index = model_->ItemIndexByID(i->first);
    653     ash::LauncherItem item = model_->items()[index];
    654     item.image = image;
    655     model_->Set(index, item);
    656     // It's possible we're waiting on more than one item, so don't break.
    657   }
    658 }
    659 
    660 void ChromeLauncherControllerPerBrowser::OnAutoHideBehaviorChanged(
    661     aura::RootWindow* root_window,
    662     ash::ShelfAutoHideBehavior new_behavior) {
    663   SetShelfAutoHideBehaviorPrefs(new_behavior, root_window);
    664 }
    665 
    666 void ChromeLauncherControllerPerBrowser::SetLauncherItemImage(
    667     ash::LauncherID launcher_id,
    668     const gfx::ImageSkia& image) {
    669   int index = model_->ItemIndexByID(launcher_id);
    670   if (index == -1)
    671     return;
    672   ash::LauncherItem item = model_->items()[index];
    673   item.image = image;
    674   model_->Set(index, item);
    675 }
    676 
    677 bool ChromeLauncherControllerPerBrowser::IsAppPinned(
    678     const std::string& app_id) {
    679   // Check the LauncherModel since there is no controller for the browser item.
    680   if (app_id == extension_misc::kChromeAppId) {
    681     for (size_t index = 0; index < model_->items().size(); index++) {
    682       if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT)
    683         return true;
    684     }
    685     return false;
    686   }
    687   for (IDToItemControllerMap::const_iterator i =
    688            id_to_item_controller_map_.begin();
    689        i != id_to_item_controller_map_.end(); ++i) {
    690     if (IsPinned(i->first) && i->second->app_id() == app_id)
    691       return true;
    692   }
    693   return false;
    694 }
    695 
    696 void ChromeLauncherControllerPerBrowser::PinAppWithID(
    697     const std::string& app_id) {
    698   if (CanPin())
    699     DoPinAppWithID(app_id);
    700   else
    701     NOTREACHED();
    702 }
    703 
    704 void ChromeLauncherControllerPerBrowser::SetLaunchType(
    705     ash::LauncherID id,
    706     extensions::ExtensionPrefs::LaunchType launch_type) {
    707   if (!HasItemController(id))
    708     return;
    709 
    710   return profile_->GetExtensionService()->extension_prefs()->SetLaunchType(
    711       id_to_item_controller_map_[id]->app_id(), launch_type);
    712 }
    713 
    714 void ChromeLauncherControllerPerBrowser::UnpinAppsWithID(
    715     const std::string& app_id) {
    716   if (CanPin())
    717     DoUnpinAppsWithID(app_id);
    718   else
    719     NOTREACHED();
    720 }
    721 
    722 bool ChromeLauncherControllerPerBrowser::IsLoggedInAsGuest() {
    723   return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord();
    724 }
    725 
    726 void ChromeLauncherControllerPerBrowser::CreateNewWindow() {
    727   chrome::NewEmptyWindow(
    728       GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH);
    729 }
    730 
    731 void ChromeLauncherControllerPerBrowser::CreateNewIncognitoWindow() {
    732   chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile(),
    733                          chrome::HOST_DESKTOP_TYPE_ASH);
    734 }
    735 
    736 bool ChromeLauncherControllerPerBrowser::CanPin() const {
    737   const PrefService::Preference* pref =
    738       profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps);
    739   return pref && pref->IsUserModifiable();
    740 }
    741 
    742 ash::ShelfAutoHideBehavior
    743     ChromeLauncherControllerPerBrowser::GetShelfAutoHideBehavior(
    744         aura::RootWindow* root_window) const {
    745   // Don't show the shelf in the app mode.
    746   if (chrome::IsRunningInAppMode())
    747     return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
    748 
    749   // See comment in |kShelfAlignment| as to why we consider two prefs.
    750   const std::string behavior_value(
    751       GetPrefForRootWindow(profile_->GetPrefs(),
    752                            root_window,
    753                            prefs::kShelfAutoHideBehaviorLocal,
    754                            prefs::kShelfAutoHideBehavior));
    755 
    756   // Note: To maintain sync compatibility with old images of chrome/chromeos
    757   // the set of values that may be encountered includes the now-extinct
    758   // "Default" as well as "Never" and "Always", "Default" should now
    759   // be treated as "Never" (http://crbug.com/146773).
    760   if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
    761     return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
    762   return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
    763 }
    764 
    765 bool ChromeLauncherControllerPerBrowser::CanUserModifyShelfAutoHideBehavior(
    766     aura::RootWindow* root_window) const {
    767   return profile_->GetPrefs()->
    768       FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable();
    769 }
    770 
    771 void ChromeLauncherControllerPerBrowser::ToggleShelfAutoHideBehavior(
    772     aura::RootWindow* root_window) {
    773   ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) ==
    774       ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
    775           ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER :
    776           ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
    777   SetShelfAutoHideBehaviorPrefs(behavior, root_window);
    778   return;
    779 }
    780 
    781 void ChromeLauncherControllerPerBrowser::RemoveTabFromRunningApp(
    782     WebContents* tab,
    783     const std::string& app_id) {
    784   web_contents_to_app_id_.erase(tab);
    785   AppIDToWebContentsListMap::iterator i_app_id =
    786       app_id_to_web_contents_list_.find(app_id);
    787   if (i_app_id != app_id_to_web_contents_list_.end()) {
    788     WebContentsList* tab_list = &i_app_id->second;
    789     tab_list->remove(tab);
    790     if (tab_list->empty()) {
    791       app_id_to_web_contents_list_.erase(i_app_id);
    792       i_app_id = app_id_to_web_contents_list_.end();
    793       ash::LauncherID id = GetLauncherIDForAppID(app_id);
    794       if (id > 0)
    795         SetItemStatus(id, ash::STATUS_CLOSED);
    796     }
    797   }
    798 }
    799 
    800 void ChromeLauncherControllerPerBrowser::UpdateAppState(
    801     content::WebContents* contents,
    802     AppState app_state) {
    803   std::string app_id = GetAppID(contents);
    804 
    805   // Check the old |app_id| for a tab. If the contents has changed we need to
    806   // remove it from the previous app.
    807   if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) {
    808     std::string last_app_id = web_contents_to_app_id_[contents];
    809     if (last_app_id != app_id)
    810       RemoveTabFromRunningApp(contents, last_app_id);
    811   }
    812 
    813   if (app_id.empty())
    814     return;
    815 
    816   web_contents_to_app_id_[contents] = app_id;
    817 
    818   if (app_state == APP_STATE_REMOVED) {
    819     // The tab has gone away.
    820     RemoveTabFromRunningApp(contents, app_id);
    821   } else {
    822     WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]);
    823 
    824     if (app_state == APP_STATE_INACTIVE) {
    825       WebContentsList::const_iterator i_tab =
    826           std::find(tab_list.begin(), tab_list.end(), contents);
    827       if (i_tab == tab_list.end())
    828         tab_list.push_back(contents);
    829       if (i_tab != tab_list.begin()) {
    830         // Going inactive, but wasn't the front tab, indicating that a new
    831         // tab has already become active.
    832         return;
    833       }
    834     } else {
    835       tab_list.remove(contents);
    836       tab_list.push_front(contents);
    837     }
    838     ash::LauncherID id = GetLauncherIDForAppID(app_id);
    839     if (id > 0) {
    840       // If the window is active, mark the app as active.
    841       SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ?
    842           ash::STATUS_ACTIVE : ash::STATUS_RUNNING);
    843     }
    844   }
    845 }
    846 
    847 void ChromeLauncherControllerPerBrowser::SetRefocusURLPatternForTest(
    848     ash::LauncherID id,
    849     const GURL& url) {
    850   DCHECK(HasItemController(id));
    851   LauncherItemController* controller = id_to_item_controller_map_[id];
    852 
    853   int index = model_->ItemIndexByID(id);
    854   if (index == -1) {
    855     NOTREACHED() << "Invalid launcher id";
    856     return;
    857   }
    858 
    859   ash::LauncherItemType type = model_->items()[index].type;
    860   if (type == ash::TYPE_APP_SHORTCUT) {
    861     AppShortcutLauncherItemController* app_controller =
    862         static_cast<AppShortcutLauncherItemController*>(controller);
    863     app_controller->set_refocus_url(url);
    864   } else {
    865     NOTREACHED() << "Invalid launcher type";
    866   }
    867 }
    868 
    869 const Extension* ChromeLauncherControllerPerBrowser::GetExtensionForAppID(
    870     const std::string& app_id) const {
    871   return profile_->GetExtensionService()->GetInstalledExtension(app_id);
    872 }
    873 
    874 void ChromeLauncherControllerPerBrowser::ActivateWindowOrMinimizeIfActive(
    875     ui::BaseWindow* window,
    876     bool allow_minimize) {
    877   window->Show();
    878   window->Activate();
    879 }
    880 
    881 void ChromeLauncherControllerPerBrowser::BrowserShortcutClicked(
    882     int event_flags) {
    883 #if defined(OS_CHROMEOS)
    884   chromeos::default_pinned_apps_field_trial::RecordShelfClick(
    885       chromeos::default_pinned_apps_field_trial::CHROME);
    886 #endif
    887   if (event_flags & ui::EF_CONTROL_DOWN) {
    888     CreateNewWindow();
    889     return;
    890   }
    891 
    892   Browser* last_browser = chrome::FindTabbedBrowser(
    893       GetProfileForNewWindows(), true, chrome::HOST_DESKTOP_TYPE_ASH);
    894 
    895   if (!last_browser) {
    896     CreateNewWindow();
    897     return;
    898   }
    899 
    900   aura::Window* window = last_browser->window()->GetNativeWindow();
    901   window->Show();
    902   ash::wm::ActivateWindow(window);
    903 }
    904 
    905 void ChromeLauncherControllerPerBrowser::ItemSelected(
    906     const ash::LauncherItem& item,
    907     const ui::Event& event) {
    908   if (item.type == ash::TYPE_BROWSER_SHORTCUT) {
    909     BrowserShortcutClicked(event.flags());
    910     return;
    911   }
    912 
    913   DCHECK(HasItemController(item.id));
    914   LauncherItemController* item_controller = id_to_item_controller_map_[item.id];
    915 #if defined(OS_CHROMEOS)
    916   if (!item_controller->app_id().empty()) {
    917     chromeos::default_pinned_apps_field_trial::RecordShelfAppClick(
    918         item_controller->app_id());
    919   }
    920 #endif
    921   item_controller->Clicked(event);
    922 }
    923 
    924 string16 ChromeLauncherControllerPerBrowser::GetTitle(
    925     const ash::LauncherItem& item) {
    926   if (item.type == ash::TYPE_BROWSER_SHORTCUT)
    927     return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
    928 
    929   DCHECK(HasItemController(item.id));
    930   return id_to_item_controller_map_[item.id]->GetTitle();
    931 }
    932 
    933 ui::MenuModel* ChromeLauncherControllerPerBrowser::CreateContextMenu(
    934     const ash::LauncherItem& item,
    935     aura::RootWindow* root_window) {
    936   return new LauncherContextMenu(this, &item, root_window);
    937 }
    938 
    939 ash::LauncherMenuModel*
    940 ChromeLauncherControllerPerBrowser::CreateApplicationMenu(
    941     const ash::LauncherItem& item,
    942     int event_flags) {
    943   // Not used by this launcher type.
    944   return NULL;
    945 }
    946 
    947 ash::LauncherID ChromeLauncherControllerPerBrowser::GetIDByWindow(
    948     aura::Window* window) {
    949   for (IDToItemControllerMap::const_iterator i =
    950            id_to_item_controller_map_.begin();
    951        i != id_to_item_controller_map_.end(); ++i) {
    952     if (i->second->HasWindow(window))
    953       return i->first;
    954   }
    955   return 0;
    956 }
    957 
    958 bool ChromeLauncherControllerPerBrowser::IsDraggable(
    959     const ash::LauncherItem& item) {
    960   return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true;
    961 }
    962 
    963 bool ChromeLauncherControllerPerBrowser::ShouldShowTooltip(
    964     const ash::LauncherItem& item) {
    965   if (item.type == ash::TYPE_APP_PANEL &&
    966       id_to_item_controller_map_[item.id]->IsVisible())
    967     return false;
    968   return true;
    969 }
    970 
    971 void ChromeLauncherControllerPerBrowser::OnLauncherCreated(
    972     ash::Launcher* launcher) {
    973   launchers_.insert(launcher);
    974   launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this);
    975 }
    976 
    977 void ChromeLauncherControllerPerBrowser::OnLauncherDestroyed(
    978     ash::Launcher* launcher) {
    979   launchers_.erase(launcher);
    980   // RemoveObserver is not called here, since by the time this method is called
    981   // Launcher is already in its destructor.
    982 }
    983 
    984 void ChromeLauncherControllerPerBrowser::LauncherItemAdded(int index) {
    985 }
    986 
    987 void ChromeLauncherControllerPerBrowser::LauncherItemRemoved(
    988     int index,
    989     ash::LauncherID id) {
    990 }
    991 
    992 void ChromeLauncherControllerPerBrowser::LauncherItemMoved(
    993     int start_index,
    994     int target_index) {
    995   ash::LauncherID id = model_->items()[target_index].id;
    996   if (HasItemController(id) && IsPinned(id))
    997     PersistPinnedState();
    998   else if (!HasItemController(id) &&
    999            model_->items()[target_index].type == ash::TYPE_BROWSER_SHORTCUT)
   1000     PersistPinnedState();
   1001 }
   1002 
   1003 void ChromeLauncherControllerPerBrowser::LauncherItemChanged(
   1004     int index,
   1005     const ash::LauncherItem& old_item) {
   1006   ash::LauncherID id = model_->items()[index].id;
   1007   id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item);
   1008 }
   1009 
   1010 void ChromeLauncherControllerPerBrowser::LauncherStatusChanged() {
   1011 }
   1012 
   1013 void ChromeLauncherControllerPerBrowser::Observe(
   1014     int type,
   1015     const content::NotificationSource& source,
   1016     const content::NotificationDetails& details) {
   1017   switch (type) {
   1018     case chrome::NOTIFICATION_EXTENSION_LOADED: {
   1019       const Extension* extension =
   1020           content::Details<const Extension>(details).ptr();
   1021       if (IsAppPinned(extension->id())) {
   1022         // Clear and re-fetch to ensure icon is up-to-date.
   1023         app_icon_loader_->ClearImage(extension->id());
   1024         app_icon_loader_->FetchImage(extension->id());
   1025       }
   1026 
   1027       UpdateAppLaunchersFromPref();
   1028       break;
   1029     }
   1030     case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
   1031       const content::Details<extensions::UnloadedExtensionInfo>& unload_info(
   1032           details);
   1033       const Extension* extension = unload_info->extension;
   1034       if (IsAppPinned(extension->id())) {
   1035         if (unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) {
   1036           DoUnpinAppsWithID(extension->id());
   1037           app_icon_loader_->ClearImage(extension->id());
   1038         } else {
   1039           app_icon_loader_->UpdateImage(extension->id());
   1040         }
   1041       }
   1042       break;
   1043     }
   1044     default:
   1045       NOTREACHED() << "Unexpected notification type=" << type;
   1046   }
   1047 }
   1048 
   1049 void ChromeLauncherControllerPerBrowser::OnShelfAlignmentChanged(
   1050     aura::RootWindow* root_window) {
   1051   const char* pref_value = NULL;
   1052   switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) {
   1053     case ash::SHELF_ALIGNMENT_BOTTOM:
   1054       pref_value = ash::kShelfAlignmentBottom;
   1055       break;
   1056     case ash::SHELF_ALIGNMENT_LEFT:
   1057       pref_value = ash::kShelfAlignmentLeft;
   1058       break;
   1059     case ash::SHELF_ALIGNMENT_RIGHT:
   1060       pref_value = ash::kShelfAlignmentRight;
   1061       break;
   1062     case ash::SHELF_ALIGNMENT_TOP:
   1063       pref_value = ash::kShelfAlignmentTop;
   1064       break;
   1065   }
   1066 
   1067   UpdatePerDisplayPref(
   1068       profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value);
   1069 
   1070   if (root_window == ash::Shell::GetPrimaryRootWindow()) {
   1071     // See comment in |kShelfAlignment| about why we have two prefs here.
   1072     profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value);
   1073     profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value);
   1074   }
   1075 }
   1076 
   1077 void ChromeLauncherControllerPerBrowser::OnDisplayConfigurationChanging() {
   1078 }
   1079 
   1080 void ChromeLauncherControllerPerBrowser::OnDisplayConfigurationChanged() {
   1081   SetShelfBehaviorsFromPrefs();
   1082 }
   1083 
   1084 void ChromeLauncherControllerPerBrowser::OnIsSyncingChanged() {
   1085   PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
   1086   MaybePropagatePrefToLocal(prefs,
   1087                             prefs::kShelfAlignmentLocal,
   1088                             prefs::kShelfAlignment);
   1089   MaybePropagatePrefToLocal(prefs,
   1090                             prefs::kShelfAutoHideBehaviorLocal,
   1091                             prefs::kShelfAutoHideBehavior);
   1092 }
   1093 
   1094 void ChromeLauncherControllerPerBrowser::OnAppSyncUIStatusChanged() {
   1095   if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING)
   1096     model_->SetStatus(ash::LauncherModel::STATUS_LOADING);
   1097   else
   1098     model_->SetStatus(ash::LauncherModel::STATUS_NORMAL);
   1099 }
   1100 
   1101 void ChromeLauncherControllerPerBrowser::ExtensionEnableFlowFinished() {
   1102   LaunchApp(extension_enable_flow_->extension_id(), ui::EF_NONE);
   1103   extension_enable_flow_.reset();
   1104 }
   1105 
   1106 void ChromeLauncherControllerPerBrowser::ExtensionEnableFlowAborted(
   1107     bool user_initiated) {
   1108   extension_enable_flow_.reset();
   1109 }
   1110 
   1111 void ChromeLauncherControllerPerBrowser::PersistPinnedState() {
   1112   if (ignore_persist_pinned_state_change_)
   1113     return;
   1114   // It is a coding error to call PersistPinnedState() if the pinned apps are
   1115   // not user-editable. The code should check earlier and not perform any
   1116   // modification actions that trigger persisting the state.
   1117   if (!CanPin()) {
   1118     NOTREACHED() << "Can't pin but pinned state being updated";
   1119     return;
   1120   }
   1121 
   1122   // Mutating kPinnedLauncherApps is going to notify us and trigger us to
   1123   // process the change. We don't want that to happen so remove ourselves as a
   1124   // listener.
   1125   pref_change_registrar_.Remove(prefs::kPinnedLauncherApps);
   1126   {
   1127     ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps);
   1128     updater->Clear();
   1129     for (size_t i = 0; i < model_->items().size(); ++i) {
   1130       if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) {
   1131         ash::LauncherID id = model_->items()[i].id;
   1132         if (HasItemController(id) && IsPinned(id)) {
   1133           base::DictionaryValue* app_value = ash::CreateAppDict(
   1134               id_to_item_controller_map_[id]->app_id());
   1135           if (app_value)
   1136             updater->Append(app_value);
   1137         }
   1138       } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) {
   1139           SetChromeIconIndexToPref(i);
   1140       }
   1141     }
   1142   }
   1143   pref_change_registrar_.Add(
   1144       prefs::kPinnedLauncherApps,
   1145       base::Bind(&ChromeLauncherControllerPerBrowser::
   1146                      UpdateAppLaunchersFromPref,
   1147                  base::Unretained(this)));
   1148 }
   1149 
   1150 ash::LauncherModel* ChromeLauncherControllerPerBrowser::model() {
   1151   return model_;
   1152 }
   1153 
   1154 Profile* ChromeLauncherControllerPerBrowser::profile() {
   1155   return profile_;
   1156 }
   1157 
   1158 Profile* ChromeLauncherControllerPerBrowser::GetProfileForNewWindows() {
   1159   return ProfileManager::GetDefaultProfileOrOffTheRecord();
   1160 }
   1161 
   1162 void ChromeLauncherControllerPerBrowser::LauncherItemClosed(
   1163     ash::LauncherID id) {
   1164   IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
   1165   DCHECK(iter != id_to_item_controller_map_.end());
   1166   app_icon_loader_->ClearImage(iter->second->app_id());
   1167   iter->second->OnRemoved();
   1168   id_to_item_controller_map_.erase(iter);
   1169   model_->RemoveItemAt(model_->ItemIndexByID(id));
   1170 }
   1171 
   1172 void ChromeLauncherControllerPerBrowser::DoPinAppWithID(
   1173     const std::string& app_id) {
   1174   // If there is an item, do nothing and return.
   1175   if (IsAppPinned(app_id))
   1176     return;
   1177 
   1178   ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id);
   1179   if (launcher_id) {
   1180     // App item exists, pin it
   1181     Pin(launcher_id);
   1182   } else {
   1183     // Otherwise, create a shortcut item for it.
   1184     CreateAppShortcutLauncherItem(app_id, model_->item_count());
   1185     if (CanPin())
   1186       PersistPinnedState();
   1187   }
   1188 }
   1189 
   1190 void ChromeLauncherControllerPerBrowser::DoUnpinAppsWithID(
   1191     const std::string& app_id) {
   1192   for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin();
   1193        i != id_to_item_controller_map_.end(); ) {
   1194     IDToItemControllerMap::iterator current(i);
   1195     ++i;
   1196     if (current->second->app_id() == app_id && IsPinned(current->first))
   1197       Unpin(current->first);
   1198   }
   1199 }
   1200 
   1201 void ChromeLauncherControllerPerBrowser::UpdateAppLaunchersFromPref() {
   1202   // Construct a vector representation of to-be-pinned apps from the pref.
   1203   std::vector<std::string> pinned_apps;
   1204   int chrome_icon_index = GetChromeIconIndexFromPref();
   1205   const base::ListValue* pinned_apps_pref =
   1206       profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
   1207   for (base::ListValue::const_iterator it(pinned_apps_pref->begin());
   1208        it != pinned_apps_pref->end(); ++it) {
   1209     // To preserve the Chrome icon position, we insert a dummy slot for it - if
   1210     // the model has a Chrome item. While initializing we can come here with no
   1211     // item in which case the count would be 1 or below.
   1212     if (it - pinned_apps_pref->begin() == chrome_icon_index &&
   1213         model_->item_count() > 1) {
   1214       pinned_apps.push_back(extension_misc::kChromeAppId);
   1215     }
   1216     DictionaryValue* app = NULL;
   1217     std::string app_id;
   1218     if ((*it)->GetAsDictionary(&app) &&
   1219         app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) &&
   1220         std::find(pinned_apps.begin(), pinned_apps.end(), app_id) ==
   1221             pinned_apps.end() &&
   1222         app_tab_helper_->IsValidID(app_id)) {
   1223       pinned_apps.push_back(app_id);
   1224     }
   1225   }
   1226 
   1227   // Walk the model and |pinned_apps| from the pref lockstep, adding and
   1228   // removing items as necessary. NB: This code uses plain old indexing instead
   1229   // of iterators because of model mutations as part of the loop.
   1230   std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin());
   1231   int index = 0;
   1232   int max_index = model_->item_count();
   1233   if (ash::switches::UseAlternateShelfLayout()) {
   1234     ++index;
   1235     ++max_index;
   1236   }
   1237   for (; index < model_->item_count() && pref_app_id != pinned_apps.end();
   1238        ++index) {
   1239     // If the next app launcher according to the pref is present in the model,
   1240     // delete all app launcher entries in between.
   1241     if (*pref_app_id == extension_misc::kChromeAppId ||
   1242         IsAppPinned(*pref_app_id)) {
   1243       for (; index < model_->item_count(); ++index) {
   1244         const ash::LauncherItem& item(model_->items()[index]);
   1245         if (item.type != ash::TYPE_APP_SHORTCUT &&
   1246             item.type != ash::TYPE_BROWSER_SHORTCUT)
   1247           continue;
   1248 
   1249         IDToItemControllerMap::const_iterator entry =
   1250             id_to_item_controller_map_.find(item.id);
   1251         if ((extension_misc::kChromeAppId == *pref_app_id &&
   1252              item.type == ash::TYPE_BROWSER_SHORTCUT) ||
   1253             (entry != id_to_item_controller_map_.end() &&
   1254              entry->second->app_id() == *pref_app_id)) {
   1255           ++pref_app_id;
   1256           break;
   1257         } else {
   1258           if (item.type == ash::TYPE_BROWSER_SHORTCUT) {
   1259             // We cannot delete the browser shortcut. As such we move it up by
   1260             // one. To avoid any side effects from our pinned state observer, we
   1261             // do not call the model directly.
   1262             MoveItemWithoutPinnedStateChangeNotification(index, index + 1);
   1263           } else {
   1264             LauncherItemClosed(item.id);
   1265             --max_index;
   1266           }
   1267           --index;
   1268         }
   1269       }
   1270       // If the item wasn't found, that means id_to_item_controller_map_
   1271       // is out of sync.
   1272       DCHECK(index < model_->item_count());
   1273     } else {
   1274       // This app wasn't pinned before, insert a new entry.
   1275       ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index);
   1276       index = model_->ItemIndexByID(id);
   1277       ++pref_app_id;
   1278     }
   1279   }
   1280 
   1281   // Remove any trailing existing items.
   1282   while (index < model_->item_count()) {
   1283     const ash::LauncherItem& item(model_->items()[index]);
   1284     if (item.type == ash::TYPE_APP_SHORTCUT)
   1285       LauncherItemClosed(item.id);
   1286     else
   1287       ++index;
   1288   }
   1289 
   1290   // Append unprocessed items from the pref to the end of the model.
   1291   for (; pref_app_id != pinned_apps.end(); ++pref_app_id) {
   1292     // Ignore the chrome icon.
   1293     if (*pref_app_id != extension_misc::kChromeAppId)
   1294       DoPinAppWithID(*pref_app_id);
   1295   }
   1296 }
   1297 
   1298 void ChromeLauncherControllerPerBrowser::SetShelfAutoHideBehaviorPrefs(
   1299     ash::ShelfAutoHideBehavior behavior,
   1300     aura::RootWindow* root_window) {
   1301   const char* value = NULL;
   1302   switch (behavior) {
   1303     case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
   1304       value = ash::kShelfAutoHideBehaviorAlways;
   1305       break;
   1306     case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
   1307       value = ash::kShelfAutoHideBehaviorNever;
   1308       break;
   1309     case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
   1310       // This one should not be a valid preference option for now. We only want
   1311       // to completely hide it when we run app mode.
   1312       NOTREACHED();
   1313       return;
   1314   }
   1315 
   1316   UpdatePerDisplayPref(
   1317       profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value);
   1318 
   1319   if (root_window == ash::Shell::GetPrimaryRootWindow()) {
   1320     // See comment in |kShelfAlignment| about why we have two prefs here.
   1321     profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value);
   1322     profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value);
   1323   }
   1324 }
   1325 
   1326 void ChromeLauncherControllerPerBrowser::SetShelfAutoHideBehaviorFromPrefs() {
   1327   ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
   1328 
   1329   for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin();
   1330        iter != root_windows.end(); ++iter) {
   1331     ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
   1332         GetShelfAutoHideBehavior(*iter), *iter);
   1333   }
   1334 }
   1335 
   1336 void ChromeLauncherControllerPerBrowser::SetShelfAlignmentFromPrefs() {
   1337   if (!CommandLine::ForCurrentProcess()->HasSwitch(
   1338           switches::kShowShelfAlignmentMenu))
   1339     return;
   1340 
   1341   ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
   1342 
   1343   for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin();
   1344        iter != root_windows.end(); ++iter) {
   1345     // See comment in |kShelfAlignment| as to why we consider two prefs.
   1346     const std::string alignment_value(
   1347         GetPrefForRootWindow(profile_->GetPrefs(),
   1348                              *iter,
   1349                              prefs::kShelfAlignmentLocal,
   1350                              prefs::kShelfAlignment));
   1351     ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
   1352     if (alignment_value == ash::kShelfAlignmentLeft)
   1353       alignment = ash::SHELF_ALIGNMENT_LEFT;
   1354     else if (alignment_value == ash::kShelfAlignmentRight)
   1355       alignment = ash::SHELF_ALIGNMENT_RIGHT;
   1356     else if (alignment_value == ash::kShelfAlignmentTop)
   1357       alignment = ash::SHELF_ALIGNMENT_TOP;
   1358     ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter);
   1359   }
   1360 }
   1361 
   1362 void ChromeLauncherControllerPerBrowser::SetShelfBehaviorsFromPrefs() {
   1363   SetShelfAutoHideBehaviorFromPrefs();
   1364   SetShelfAlignmentFromPrefs();
   1365 }
   1366 
   1367 WebContents* ChromeLauncherControllerPerBrowser::GetLastActiveWebContents(
   1368     const std::string& app_id) {
   1369   AppIDToWebContentsListMap::const_iterator i =
   1370       app_id_to_web_contents_list_.find(app_id);
   1371   if (i == app_id_to_web_contents_list_.end())
   1372     return NULL;
   1373   DCHECK_GT(i->second.size(), 0u);
   1374   return *i->second.begin();
   1375 }
   1376 
   1377 ash::LauncherID ChromeLauncherControllerPerBrowser::InsertAppLauncherItem(
   1378     LauncherItemController* controller,
   1379     const std::string& app_id,
   1380     ash::LauncherItemStatus status,
   1381     int index) {
   1382   ash::LauncherID id = model_->next_id();
   1383   DCHECK(!HasItemController(id));
   1384   DCHECK(controller);
   1385   id_to_item_controller_map_[id] = controller;
   1386   controller->set_launcher_id(id);
   1387 
   1388   ash::LauncherItem item;
   1389   item.type = controller->GetLauncherItemType();
   1390   item.is_incognito = false;
   1391   item.image = extensions::IconsInfo::GetDefaultAppIcon();
   1392 
   1393   WebContents* active_tab = GetLastActiveWebContents(app_id);
   1394   if (active_tab) {
   1395     Browser* browser = chrome::FindBrowserWithWebContents(active_tab);
   1396     DCHECK(browser);
   1397     if (browser->window()->IsActive())
   1398       status = ash::STATUS_ACTIVE;
   1399     else
   1400       status = ash::STATUS_RUNNING;
   1401   }
   1402   item.status = status;
   1403 
   1404   model_->AddAt(index, item);
   1405 
   1406   app_icon_loader_->FetchImage(app_id);
   1407 
   1408   return id;
   1409 }
   1410 
   1411 bool ChromeLauncherControllerPerBrowser::HasItemController(
   1412     ash::LauncherID id) const {
   1413   return id_to_item_controller_map_.find(id) !=
   1414          id_to_item_controller_map_.end();
   1415 }
   1416 
   1417 ash::LauncherID
   1418 ChromeLauncherControllerPerBrowser::CreateBrowserShortcutLauncherItem() {
   1419   ash::LauncherItem browser_shortcut;
   1420   browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT;
   1421   browser_shortcut.is_incognito = false;
   1422   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
   1423   browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32);
   1424   ash::LauncherID id = model_->next_id();
   1425   size_t index = GetChromeIconIndexFromPref();
   1426   model_->AddAt(index, browser_shortcut);
   1427   return id;
   1428 }
   1429 
   1430 void ChromeLauncherControllerPerBrowser::SetChromeIconIndexToPref(int index) {
   1431   profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index);
   1432 }
   1433 
   1434 int ChromeLauncherControllerPerBrowser::GetChromeIconIndexFromPref() const {
   1435   size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex);
   1436   const base::ListValue* pinned_apps_pref =
   1437   profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps);
   1438   if (ash::switches::UseAlternateShelfLayout())
   1439     return std::max(static_cast<size_t>(1),
   1440                     std::min(pinned_apps_pref->GetSize(), index));
   1441   return std::max(static_cast<size_t>(0),
   1442                   std::min(pinned_apps_pref->GetSize(), index));
   1443 }
   1444 
   1445 ash::LauncherID
   1446 ChromeLauncherControllerPerBrowser::CreateAppShortcutLauncherItem(
   1447     const std::string& app_id,
   1448     int index) {
   1449   AppShortcutLauncherItemController* controller =
   1450       new AppShortcutLauncherItemController(app_id, this);
   1451   ash::LauncherID launcher_id = InsertAppLauncherItem(
   1452       controller, app_id, ash::STATUS_CLOSED, index);
   1453   return launcher_id;
   1454 }
   1455 
   1456 void ChromeLauncherControllerPerBrowser::SetAppTabHelperForTest(
   1457     AppTabHelper* helper) {
   1458   app_tab_helper_.reset(helper);
   1459 }
   1460 
   1461 void ChromeLauncherControllerPerBrowser::SetAppIconLoaderForTest(
   1462     extensions::AppIconLoader* loader) {
   1463   app_icon_loader_.reset(loader);
   1464 }
   1465 
   1466 const std::string&
   1467 ChromeLauncherControllerPerBrowser::GetAppIdFromLauncherIdForTest(
   1468     ash::LauncherID id) {
   1469   return id_to_item_controller_map_[id]->app_id();
   1470 }
   1471 
   1472 void ChromeLauncherControllerPerBrowser::
   1473     MoveItemWithoutPinnedStateChangeNotification(int source_index,
   1474                                                  int target_index) {
   1475   base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true);
   1476   model_->Move(source_index, target_index);
   1477 }
   1478