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