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