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