Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(419)

Side by Side Diff: chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc

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

Powered by Google App Engine
This is Rietveld 408576698