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