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