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/views/ash/launcher/chrome_launcher_controller.h" | |
6 | |
7 #include <set> | |
8 #include <vector> | |
9 | |
10 #include "ash/launcher/launcher_model.h" | |
11 #include "ash/launcher/launcher_types.h" | |
12 #include "ash/shell.h" | |
13 #include "ash/wm/window_util.h" | |
14 #include "base/command_line.h" | |
15 #include "base/utf_string_conversions.h" | |
16 #include "base/values.h" | |
17 #include "chrome/browser/defaults.h" | |
18 #include "chrome/browser/extensions/extension_service.h" | |
19 #include "chrome/browser/prefs/incognito_mode_prefs.h" | |
20 #include "chrome/browser/prefs/pref_service.h" | |
21 #include "chrome/browser/prefs/scoped_user_pref_update.h" | |
22 #include "chrome/browser/profiles/profile.h" | |
23 #include "chrome/browser/profiles/profile_manager.h" | |
24 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" | |
25 #include "chrome/browser/ui/ash/extension_utils.h" | |
26 #include "chrome/browser/ui/browser.h" | |
27 #include "chrome/browser/ui/browser_commands.h" | |
28 #include "chrome/browser/ui/browser_finder.h" | |
29 #include "chrome/browser/ui/browser_window.h" | |
30 #include "chrome/browser/ui/extensions/shell_window.h" | |
31 #include "chrome/browser/ui/tab_contents/tab_contents.h" | |
32 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
33 #include "chrome/browser/ui/views/ash/launcher/browser_launcher_item_controller.
h" | |
34 #include "chrome/browser/ui/views/ash/launcher/launcher_app_icon_loader.h" | |
35 #include "chrome/browser/ui/views/ash/launcher/launcher_app_tab_helper.h" | |
36 #include "chrome/browser/ui/views/ash/launcher/launcher_context_menu.h" | |
37 #include "chrome/browser/web_applications/web_app.h" | |
38 #include "chrome/common/chrome_notification_types.h" | |
39 #include "chrome/common/chrome_switches.h" | |
40 #include "chrome/common/extensions/extension.h" | |
41 #include "chrome/common/extensions/extension_resource.h" | |
42 #include "chrome/common/pref_names.h" | |
43 #include "content/public/browser/notification_service.h" | |
44 #include "content/public/browser/web_contents.h" | |
45 #include "grit/theme_resources.h" | |
46 #include "ui/aura/client/activation_client.h" | |
47 #include "ui/aura/window.h" | |
48 #include "ui/views/widget/widget.h" | |
49 | |
50 using extensions::Extension; | |
51 | |
52 // ChromeLauncherController::Item ---------------------------------------------- | |
53 | |
54 ChromeLauncherController::Item::Item() | |
55 : item_type(TYPE_TABBED_BROWSER), | |
56 controller(NULL) { | |
57 } | |
58 | |
59 ChromeLauncherController::Item::~Item() { | |
60 } | |
61 | |
62 // ChromeLauncherController ---------------------------------------------------- | |
63 | |
64 // static | |
65 ChromeLauncherController* ChromeLauncherController::instance_ = NULL; | |
66 | |
67 ChromeLauncherController::ChromeLauncherController(Profile* profile, | |
68 ash::LauncherModel* model) | |
69 : model_(model), | |
70 profile_(profile), | |
71 activation_client_(NULL) { | |
72 if (!profile_) { | |
73 // Use the original profile as on chromeos we may get a temporary off the | |
74 // record profile. | |
75 profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile(); | |
76 } | |
77 instance_ = this; | |
78 model_->AddObserver(this); | |
79 ShellWindowRegistry::Get(profile_)->AddObserver(this); | |
80 app_tab_helper_.reset(new LauncherAppTabHelper(profile_)); | |
81 app_icon_loader_.reset(new LauncherAppIconLoader(profile_, this)); | |
82 | |
83 notification_registrar_.Add(this, | |
84 chrome::NOTIFICATION_EXTENSION_LOADED, | |
85 content::Source<Profile>(profile_)); | |
86 notification_registrar_.Add(this, | |
87 chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
88 content::Source<Profile>(profile_)); | |
89 pref_change_registrar_.Init(profile_->GetPrefs()); | |
90 pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this); | |
91 pref_change_registrar_.Add(prefs::kShelfAlignment, this); | |
92 pref_change_registrar_.Add(prefs::kShelfAutoHideBehavior, this); | |
93 } | |
94 | |
95 ChromeLauncherController::~ChromeLauncherController() { | |
96 ShellWindowRegistry::Get(profile_)->RemoveObserver(this); | |
97 model_->RemoveObserver(this); | |
98 for (IDToItemMap::iterator i = id_to_item_map_.begin(); | |
99 i != id_to_item_map_.end(); ++i) { | |
100 model_->RemoveItemAt(model_->ItemIndexByID(i->first)); | |
101 } | |
102 if (instance_ == this) | |
103 instance_ = NULL; | |
104 if (activation_client_) | |
105 activation_client_->RemoveObserver(this); | |
106 | |
107 for (WindowToIDMap::iterator i = window_to_id_map_.begin(); | |
108 i != window_to_id_map_.end(); ++i) { | |
109 i->first->RemoveObserver(this); | |
110 } | |
111 | |
112 if (ash::Shell::HasInstance()) | |
113 ash::Shell::GetInstance()->RemoveShellObserver(this); | |
114 } | |
115 | |
116 void ChromeLauncherController::Init() { | |
117 // TODO(xiyuan): Remove migration code and kUseDefaultPinnedApp after M20. | |
118 // Migration cases: | |
119 // - Users that unpin all apps: | |
120 // - have default pinned apps | |
121 // - kUseDefaultPinnedApps set to false | |
122 // Migrate them by setting an empty list for kPinnedLauncherApps. | |
123 // | |
124 // - Users that have customized pinned apps: | |
125 // - have non-default non-empty pinned apps list | |
126 // - kUseDefaultPinnedApps set to false | |
127 // Nothing needs to be done because customized pref overrides default. | |
128 // | |
129 // - Users that have default apps (i.e. new user or never pin/unpin): | |
130 // - have default pinned apps | |
131 // - kUseDefaultPinnedApps is still true | |
132 // Nothing needs to be done because they should get the default. | |
133 if (profile_->GetPrefs()->FindPreference( | |
134 prefs::kPinnedLauncherApps)->IsDefaultValue() && | |
135 !profile_->GetPrefs()->GetBoolean(prefs::kUseDefaultPinnedApps)) { | |
136 ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); | |
137 updater.Get()->Clear(); | |
138 } | |
139 | |
140 UpdateAppLaunchersFromPref(); | |
141 | |
142 // TODO(sky): update unit test so that this test isn't necessary. | |
143 if (ash::Shell::HasInstance()) { | |
144 SetShelfAutoHideBehaviorFromPrefs(); | |
145 SetShelfAlignmentFromPrefs(); | |
146 | |
147 activation_client_ = | |
148 aura::client::GetActivationClient( | |
149 ash::Shell::GetInstance()->GetPrimaryRootWindow()); | |
150 activation_client_->AddObserver(this); | |
151 | |
152 ash::Shell::GetInstance()->AddShellObserver(this); | |
153 } | |
154 } | |
155 | |
156 ash::LauncherID ChromeLauncherController::CreateTabbedLauncherItem( | |
157 BrowserLauncherItemController* controller, | |
158 IncognitoState is_incognito, | |
159 ash::LauncherItemStatus status) { | |
160 ash::LauncherID id = model_->next_id(); | |
161 DCHECK(id_to_item_map_.find(id) == id_to_item_map_.end()); | |
162 id_to_item_map_[id].item_type = TYPE_TABBED_BROWSER; | |
163 id_to_item_map_[id].controller = controller; | |
164 | |
165 ash::LauncherItem item; | |
166 item.type = ash::TYPE_TABBED; | |
167 item.is_incognito = (is_incognito == STATE_INCOGNITO); | |
168 item.status = status; | |
169 model_->Add(item); | |
170 return id; | |
171 } | |
172 | |
173 ash::LauncherID ChromeLauncherController::CreateAppLauncherItem( | |
174 BrowserLauncherItemController* controller, | |
175 const std::string& app_id, | |
176 ash::LauncherItemStatus status) { | |
177 return InsertAppLauncherItem(controller, app_id, status, | |
178 model_->item_count()); | |
179 } | |
180 | |
181 void ChromeLauncherController::SetItemStatus(ash::LauncherID id, | |
182 ash::LauncherItemStatus status) { | |
183 int index = model_->ItemIndexByID(id); | |
184 DCHECK_GE(index, 0); | |
185 ash::LauncherItem item = model_->items()[index]; | |
186 item.status = status; | |
187 model_->Set(index, item); | |
188 } | |
189 | |
190 void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) { | |
191 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
192 id_to_item_map_.erase(id); | |
193 model_->RemoveItemAt(model_->ItemIndexByID(id)); | |
194 } | |
195 | |
196 void ChromeLauncherController::Unpin(ash::LauncherID id) { | |
197 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
198 DCHECK(!id_to_item_map_[id].controller); | |
199 | |
200 if (ShellWindowRegistry::Get(profile_)->GetShellWindowsForApp( | |
201 id_to_item_map_[id].app_id).size() > 0) { | |
202 int index = model_->ItemIndexByID(id); | |
203 ash::LauncherItem item = model_->items()[index]; | |
204 item.type = ash::TYPE_PLATFORM_APP; | |
205 model_->Set(index, item); | |
206 } else { | |
207 LauncherItemClosed(id); | |
208 } | |
209 if (CanPin()) | |
210 PersistPinnedState(); | |
211 } | |
212 | |
213 void ChromeLauncherController::Pin(ash::LauncherID id) { | |
214 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
215 DCHECK(!id_to_item_map_[id].controller); | |
216 | |
217 int index = model_->ItemIndexByID(id); | |
218 ash::LauncherItem item = model_->items()[index]; | |
219 | |
220 if (item.type != ash::TYPE_PLATFORM_APP) | |
221 return; | |
222 | |
223 item.type = ash::TYPE_APP_SHORTCUT; | |
224 model_->Set(index, item); | |
225 | |
226 if (CanPin()) | |
227 PersistPinnedState(); | |
228 } | |
229 | |
230 bool ChromeLauncherController::IsPinned(ash::LauncherID id) { | |
231 int index = model_->ItemIndexByID(id); | |
232 ash::LauncherItemType type = model_->items()[index].type; | |
233 return type == ash::TYPE_APP_SHORTCUT; | |
234 } | |
235 | |
236 void ChromeLauncherController::TogglePinned(ash::LauncherID id) { | |
237 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
238 return; // May happen if item closed with menu open. | |
239 | |
240 if (IsPinned(id)) | |
241 Unpin(id); | |
242 else | |
243 Pin(id); | |
244 } | |
245 | |
246 bool ChromeLauncherController::IsPinnable(ash::LauncherID id) const { | |
247 int index = model_->ItemIndexByID(id); | |
248 if (index == -1) | |
249 return false; | |
250 | |
251 ash::LauncherItemType type = model_->items()[index].type; | |
252 return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) && | |
253 CanPin()); | |
254 } | |
255 | |
256 void ChromeLauncherController::Open(ash::LauncherID id, int event_flags) { | |
257 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
258 return; // In case invoked from menu and item closed while menu up. | |
259 | |
260 BrowserLauncherItemController* controller = id_to_item_map_[id].controller; | |
261 if (controller) { | |
262 controller->window()->Show(); | |
263 ash::wm::ActivateWindow(controller->window()); | |
264 } else { | |
265 DCHECK_EQ(TYPE_APP, id_to_item_map_[id].item_type); | |
266 | |
267 // Do nothing for pending app shortcut. | |
268 if (GetItemStatus(id) == ash::STATUS_IS_PENDING) | |
269 return; | |
270 | |
271 OpenAppID(id_to_item_map_[id].app_id, event_flags); | |
272 } | |
273 } | |
274 | |
275 void ChromeLauncherController::OpenAppID( | |
276 const std::string& app_id, | |
277 int event_flags) { | |
278 ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); | |
279 // Check if this item has any windows in the activation list. | |
280 for (WindowList::const_iterator i = platform_app_windows_.begin(); | |
281 i != platform_app_windows_.end(); ++i) { | |
282 if (window_to_id_map_[*i] == launcher_id) { | |
283 (*i)->Show(); | |
284 ash::wm::ActivateWindow(*i); | |
285 return; | |
286 } | |
287 } | |
288 | |
289 // Check if there are any open tabs for this app. | |
290 AppIDToTabContentsListMap::iterator i = | |
291 app_id_to_tab_contents_list_.find(app_id); | |
292 | |
293 if (i != app_id_to_tab_contents_list_.end()) { | |
294 DCHECK(!i->second.empty()); | |
295 TabContents* tab = i->second.front(); | |
296 Browser* browser = browser::FindBrowserWithWebContents( | |
297 tab->web_contents()); | |
298 TabStripModel* tab_strip = browser->tab_strip_model(); | |
299 int index = tab_strip->GetIndexOfTabContents(tab); | |
300 DCHECK_NE(TabStripModel::kNoTab, index); | |
301 tab_strip->ActivateTabAt(index, false); | |
302 browser->window()->Show(); | |
303 ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); | |
304 } else { | |
305 const Extension* extension = | |
306 profile_->GetExtensionService()->GetInstalledExtension(app_id); | |
307 extension_utils::OpenExtension(GetProfileForNewWindows(), | |
308 extension, | |
309 event_flags); | |
310 } | |
311 } | |
312 | |
313 void ChromeLauncherController::Close(ash::LauncherID id) { | |
314 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
315 return; // May happen if menu closed. | |
316 | |
317 if (!id_to_item_map_[id].controller) | |
318 return; // TODO: maybe should treat as unpin? | |
319 | |
320 views::Widget* widget = views::Widget::GetWidgetForNativeView( | |
321 id_to_item_map_[id].controller->window()); | |
322 if (widget) | |
323 widget->Close(); | |
324 } | |
325 | |
326 bool ChromeLauncherController::IsOpen(ash::LauncherID id) { | |
327 return id_to_item_map_.find(id) != id_to_item_map_.end() && | |
328 id_to_item_map_[id].controller != NULL; | |
329 } | |
330 | |
331 extensions::ExtensionPrefs::LaunchType ChromeLauncherController::GetLaunchType( | |
332 ash::LauncherID id) { | |
333 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
334 | |
335 return profile_->GetExtensionService()->extension_prefs()->GetLaunchType( | |
336 id_to_item_map_[id].app_id, extensions::ExtensionPrefs::LAUNCH_DEFAULT); | |
337 } | |
338 | |
339 std::string ChromeLauncherController::GetAppID(TabContents* tab) { | |
340 return app_tab_helper_->GetAppID(tab); | |
341 } | |
342 | |
343 ash::LauncherID ChromeLauncherController::GetLauncherIDForAppID( | |
344 const std::string& app_id) { | |
345 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
346 i != id_to_item_map_.end(); ++i) { | |
347 if (i->second.app_id == app_id) | |
348 return i->first; | |
349 } | |
350 return 0; | |
351 } | |
352 | |
353 void ChromeLauncherController::SetAppImage(const std::string& id, | |
354 const gfx::ImageSkia& image) { | |
355 // TODO: need to get this working for shortcuts. | |
356 | |
357 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
358 i != id_to_item_map_.end(); ++i) { | |
359 if (i->second.app_id != id) | |
360 continue; | |
361 | |
362 // Panel items may share the same app_id as the app that created them, | |
363 // but they set their icon image in | |
364 // BrowserLauncherItemController::UpdateLauncher(), so do not set panel | |
365 // images here. | |
366 if (i->second.controller && | |
367 i->second.controller->type() == | |
368 BrowserLauncherItemController::TYPE_EXTENSION_PANEL) { | |
369 continue; | |
370 } | |
371 | |
372 int index = model_->ItemIndexByID(i->first); | |
373 ash::LauncherItem item = model_->items()[index]; | |
374 item.image = image; | |
375 model_->Set(index, item); | |
376 // It's possible we're waiting on more than one item, so don't break. | |
377 } | |
378 } | |
379 | |
380 bool ChromeLauncherController::IsAppPinned(const std::string& app_id) { | |
381 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
382 i != id_to_item_map_.end(); ++i) { | |
383 if (IsPinned(i->first) && i->second.app_id == app_id) | |
384 return true; | |
385 } | |
386 return false; | |
387 } | |
388 | |
389 void ChromeLauncherController::PinAppWithID(const std::string& app_id) { | |
390 if (CanPin()) | |
391 DoPinAppWithID(app_id); | |
392 else | |
393 NOTREACHED(); | |
394 } | |
395 | |
396 void ChromeLauncherController::SetLaunchType( | |
397 ash::LauncherID id, | |
398 extensions::ExtensionPrefs::LaunchType launch_type) { | |
399 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
400 return; | |
401 | |
402 return profile_->GetExtensionService()->extension_prefs()->SetLaunchType( | |
403 id_to_item_map_[id].app_id, launch_type); | |
404 } | |
405 | |
406 void ChromeLauncherController::UnpinAppsWithID(const std::string& app_id) { | |
407 if (CanPin()) | |
408 DoUnpinAppsWithID(app_id); | |
409 else | |
410 NOTREACHED(); | |
411 } | |
412 | |
413 bool ChromeLauncherController::IsLoggedInAsGuest() { | |
414 return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord(); | |
415 } | |
416 | |
417 void ChromeLauncherController::CreateNewIncognitoWindow() { | |
418 chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile()); | |
419 } | |
420 | |
421 bool ChromeLauncherController::CanPin() const { | |
422 const PrefService::Preference* pref = | |
423 profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps); | |
424 return pref && pref->IsUserModifiable(); | |
425 } | |
426 | |
427 void ChromeLauncherController::SetAutoHideBehavior( | |
428 ash::ShelfAutoHideBehavior behavior) { | |
429 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(behavior); | |
430 const char* value = NULL; | |
431 switch (behavior) { | |
432 case ash::SHELF_AUTO_HIDE_BEHAVIOR_DEFAULT: | |
433 value = ash::kShelfAutoHideBehaviorDefault; | |
434 break; | |
435 case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: | |
436 value = ash::kShelfAutoHideBehaviorAlways; | |
437 break; | |
438 case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER: | |
439 value = ash::kShelfAutoHideBehaviorNever; | |
440 break; | |
441 } | |
442 profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value); | |
443 } | |
444 | |
445 void ChromeLauncherController::RemoveTabFromRunningApp( | |
446 TabContents* tab, | |
447 const std::string& app_id) { | |
448 tab_contents_to_app_id_.erase(tab); | |
449 AppIDToTabContentsListMap::iterator i_app_id = | |
450 app_id_to_tab_contents_list_.find(app_id); | |
451 if (i_app_id != app_id_to_tab_contents_list_.end()) { | |
452 TabContentsList* tab_list = &i_app_id->second; | |
453 tab_list->remove(tab); | |
454 if (tab_list->empty()) { | |
455 app_id_to_tab_contents_list_.erase(i_app_id); | |
456 i_app_id = app_id_to_tab_contents_list_.end(); | |
457 ash::LauncherID id = GetLauncherIDForAppID(app_id); | |
458 if (id > 0) | |
459 SetItemStatus(id, ash::STATUS_CLOSED); | |
460 } | |
461 } | |
462 } | |
463 | |
464 void ChromeLauncherController::UpdateAppState(TabContents* tab, | |
465 AppState app_state) { | |
466 std::string app_id = GetAppID(tab); | |
467 | |
468 // Check the old |app_id| for a tab. If the contents has changed we need to | |
469 // remove it from the previous app. | |
470 if (tab_contents_to_app_id_.find(tab) != tab_contents_to_app_id_.end()) { | |
471 std::string last_app_id = tab_contents_to_app_id_[tab]; | |
472 if (last_app_id != app_id) | |
473 RemoveTabFromRunningApp(tab, last_app_id); | |
474 } | |
475 | |
476 if (app_id.empty()) | |
477 return; | |
478 | |
479 tab_contents_to_app_id_[tab] = app_id; | |
480 | |
481 if (app_state == APP_STATE_REMOVED) { | |
482 // The tab has gone away. | |
483 RemoveTabFromRunningApp(tab, app_id); | |
484 } else { | |
485 TabContentsList& tab_list(app_id_to_tab_contents_list_[app_id]); | |
486 if (app_state == APP_STATE_INACTIVE) { | |
487 TabContentsList::const_iterator i_tab = | |
488 std::find(tab_list.begin(), tab_list.end(), tab); | |
489 if (i_tab == tab_list.end()) | |
490 tab_list.push_back(tab); | |
491 } else { | |
492 tab_list.remove(tab); | |
493 tab_list.push_front(tab); | |
494 } | |
495 ash::LauncherID id = GetLauncherIDForAppID(app_id); | |
496 if (id > 0) { | |
497 // If the window is active, mark the app as active. | |
498 SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? | |
499 ash::STATUS_ACTIVE : ash::STATUS_RUNNING); | |
500 } | |
501 } | |
502 } | |
503 | |
504 void ChromeLauncherController::CreateNewTab() { | |
505 Browser* last_browser = browser::FindTabbedBrowser( | |
506 GetProfileForNewWindows(), true); | |
507 | |
508 if (!last_browser) { | |
509 CreateNewWindow(); | |
510 return; | |
511 } | |
512 | |
513 chrome::NewTab(last_browser); | |
514 aura::Window* window = last_browser->window()->GetNativeWindow(); | |
515 window->Show(); | |
516 ash::wm::ActivateWindow(window); | |
517 } | |
518 | |
519 void ChromeLauncherController::CreateNewWindow() { | |
520 chrome::NewEmptyWindow(GetProfileForNewWindows()); | |
521 } | |
522 | |
523 void ChromeLauncherController::ItemClicked(const ash::LauncherItem& item, | |
524 int event_flags) { | |
525 DCHECK(id_to_item_map_.find(item.id) != id_to_item_map_.end()); | |
526 BrowserLauncherItemController* controller = | |
527 id_to_item_map_[item.id].controller; | |
528 if (controller) { | |
529 views::Widget* widget = | |
530 views::Widget::GetWidgetForNativeView(controller->window()); | |
531 if (widget && widget->IsActive()) { | |
532 widget->Minimize(); | |
533 return; | |
534 } | |
535 // else case, fall through to show window. | |
536 } | |
537 Open(item.id, event_flags); | |
538 } | |
539 | |
540 int ChromeLauncherController::GetBrowserShortcutResourceId() { | |
541 return IDR_PRODUCT_LOGO_32; | |
542 } | |
543 | |
544 string16 ChromeLauncherController::GetTitle(const ash::LauncherItem& item) { | |
545 DCHECK(id_to_item_map_.find(item.id) != id_to_item_map_.end()); | |
546 BrowserLauncherItemController* controller = | |
547 id_to_item_map_[item.id].controller; | |
548 if (controller) { | |
549 if (id_to_item_map_[item.id].item_type == TYPE_TABBED_BROWSER) { | |
550 return controller->tab_model()->GetActiveTabContents() ? | |
551 controller->tab_model()->GetActiveTabContents()->web_contents()-> | |
552 GetTitle() : string16(); | |
553 } | |
554 // Fall through to get title from extension. | |
555 } | |
556 const Extension* extension = profile_->GetExtensionService()-> | |
557 GetInstalledExtension(id_to_item_map_[item.id].app_id); | |
558 return extension ? UTF8ToUTF16(extension->name()) : string16(); | |
559 } | |
560 | |
561 ui::MenuModel* ChromeLauncherController::CreateContextMenu( | |
562 const ash::LauncherItem& item) { | |
563 return new LauncherContextMenu(this, &item); | |
564 } | |
565 | |
566 ui::MenuModel* ChromeLauncherController::CreateContextMenuForLauncher() { | |
567 return new LauncherContextMenu(this, NULL); | |
568 } | |
569 | |
570 ash::LauncherID ChromeLauncherController::GetIDByWindow( | |
571 aura::Window* window) { | |
572 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
573 i != id_to_item_map_.end(); ++i) { | |
574 if (i->second.controller && i->second.controller->window() == window) | |
575 return i->first; | |
576 } | |
577 return 0; | |
578 } | |
579 | |
580 bool ChromeLauncherController::IsDraggable(const ash::LauncherItem& item) { | |
581 return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true; | |
582 } | |
583 | |
584 void ChromeLauncherController::LauncherItemAdded(int index) { | |
585 } | |
586 | |
587 void ChromeLauncherController::LauncherItemRemoved(int index, | |
588 ash::LauncherID id) { | |
589 } | |
590 | |
591 void ChromeLauncherController::LauncherItemMoved( | |
592 int start_index, | |
593 int target_index) { | |
594 ash::LauncherID id = model_->items()[target_index].id; | |
595 if (id_to_item_map_.find(id) != id_to_item_map_.end() && IsPinned(id)) | |
596 PersistPinnedState(); | |
597 } | |
598 | |
599 void ChromeLauncherController::LauncherItemChanged( | |
600 int index, | |
601 const ash::LauncherItem& old_item) { | |
602 if (model_->items()[index].status == ash::STATUS_ACTIVE && | |
603 old_item.status == ash::STATUS_RUNNING) { | |
604 ash::LauncherID id = model_->items()[index].id; | |
605 if (id_to_item_map_[id].controller) { | |
606 aura::Window* window_to_activate = | |
607 id_to_item_map_[id].controller->window(); | |
608 if (window_to_activate && ash::wm::IsActiveWindow(window_to_activate)) | |
609 return; | |
610 window_to_activate->Show(); | |
611 ash::wm::ActivateWindow(window_to_activate); | |
612 } | |
613 } | |
614 } | |
615 | |
616 void ChromeLauncherController::Observe( | |
617 int type, | |
618 const content::NotificationSource& source, | |
619 const content::NotificationDetails& details) { | |
620 switch (type) { | |
621 case chrome::NOTIFICATION_EXTENSION_LOADED: { | |
622 UpdateAppLaunchersFromPref(); | |
623 break; | |
624 } | |
625 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { | |
626 const content::Details<extensions::UnloadedExtensionInfo> unload_info( | |
627 details); | |
628 const Extension* extension = unload_info->extension; | |
629 if (IsAppPinned(extension->id())) { | |
630 if (unload_info->reason == extension_misc::UNLOAD_REASON_UPDATE) | |
631 MarkAppPending(extension->id()); | |
632 else | |
633 DoUnpinAppsWithID(extension->id()); | |
634 } | |
635 break; | |
636 } | |
637 case chrome::NOTIFICATION_PREF_CHANGED: { | |
638 const std::string& pref_name( | |
639 *content::Details<std::string>(details).ptr()); | |
640 if (pref_name == prefs::kPinnedLauncherApps) | |
641 UpdateAppLaunchersFromPref(); | |
642 else if (pref_name == prefs::kShelfAlignment) | |
643 SetShelfAlignmentFromPrefs(); | |
644 else if (pref_name == prefs::kShelfAutoHideBehavior) | |
645 SetShelfAutoHideBehaviorFromPrefs(); | |
646 else | |
647 NOTREACHED() << "Unexpected pref change for " << pref_name; | |
648 break; | |
649 } | |
650 default: | |
651 NOTREACHED() << "Unexpected notification type=" << type; | |
652 } | |
653 } | |
654 | |
655 void ChromeLauncherController::OnShellWindowAdded(ShellWindow* shell_window) { | |
656 aura::Window* window = shell_window->GetNativeWindow(); | |
657 ash::LauncherItemStatus status = ash::wm::IsActiveWindow(window) ? | |
658 ash::STATUS_ACTIVE : ash::STATUS_RUNNING; | |
659 window->AddObserver(this); | |
660 const std::string app_id = shell_window->extension()->id(); | |
661 ash::LauncherID id = 0; | |
662 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
663 i != id_to_item_map_.end(); ++i) { | |
664 if (i->second.app_id == app_id) { | |
665 id = i->first; | |
666 SetItemStatus(id, status); | |
667 break; | |
668 } | |
669 } | |
670 if (id == 0) | |
671 id = CreateAppLauncherItem(NULL, app_id, status); | |
672 window_to_id_map_[window] = id; | |
673 if (status == ash::STATUS_ACTIVE) | |
674 platform_app_windows_.push_front(window); | |
675 else | |
676 platform_app_windows_.push_back(window); | |
677 } | |
678 | |
679 void ChromeLauncherController::OnShellWindowRemoved(ShellWindow* shell_window) { | |
680 // Window removal is handled in OnWindowRemovingFromRootWindow() below. | |
681 } | |
682 | |
683 void ChromeLauncherController::OnWindowActivated(aura::Window* active, | |
684 aura::Window* old_active) { | |
685 if (window_to_id_map_.find(active) != window_to_id_map_.end()) { | |
686 ash::LauncherID active_id = window_to_id_map_[active]; | |
687 platform_app_windows_.remove(active); | |
688 platform_app_windows_.push_front(active); | |
689 if (window_to_id_map_.find(old_active) != window_to_id_map_.end() && | |
690 window_to_id_map_[old_active] == active_id) { | |
691 // Old and new windows are for the same item. Don't change the status. | |
692 return; | |
693 } | |
694 SetItemStatus(active_id, ash::STATUS_ACTIVE); | |
695 } | |
696 if (window_to_id_map_.find(old_active) != window_to_id_map_.end()) | |
697 SetItemStatus(window_to_id_map_[old_active], ash::STATUS_RUNNING); | |
698 } | |
699 | |
700 void ChromeLauncherController::OnWindowRemovingFromRootWindow( | |
701 aura::Window* window) { | |
702 window->RemoveObserver(this); | |
703 DCHECK(window_to_id_map_.find(window) != window_to_id_map_.end()); | |
704 ash::LauncherID id = window_to_id_map_[window]; | |
705 window_to_id_map_.erase(window); | |
706 | |
707 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
708 platform_app_windows_.remove(window); | |
709 ShellWindowRegistry::ShellWindowSet remaining_windows = | |
710 ShellWindowRegistry::Get(profile_)->GetShellWindowsForApp( | |
711 id_to_item_map_[id].app_id); | |
712 | |
713 // We can't count on getting called before or after the ShellWindowRegistry. | |
714 if (remaining_windows.size() > 1 || | |
715 (remaining_windows.size() == 1 && | |
716 (*remaining_windows.begin())->GetNativeWindow() != window)) { | |
717 return; | |
718 } | |
719 | |
720 // Close or remove item. | |
721 int index = model_->ItemIndexByID(id); | |
722 DCHECK_GE(index, 0); | |
723 ash::LauncherItem item = model_->items()[index]; | |
724 if (item.type == ash::TYPE_APP_SHORTCUT) | |
725 SetItemStatus(id, ash::STATUS_CLOSED); | |
726 else | |
727 LauncherItemClosed(id); | |
728 } | |
729 | |
730 void ChromeLauncherController::OnShelfAlignmentChanged() { | |
731 const char* pref_value = NULL; | |
732 switch (ash::Shell::GetInstance()->GetShelfAlignment()) { | |
733 case ash::SHELF_ALIGNMENT_BOTTOM: | |
734 pref_value = ash::kShelfAlignmentBottom; | |
735 break; | |
736 case ash::SHELF_ALIGNMENT_LEFT: | |
737 pref_value = ash::kShelfAlignmentLeft; | |
738 break; | |
739 case ash::SHELF_ALIGNMENT_RIGHT: | |
740 pref_value = ash::kShelfAlignmentRight; | |
741 break; | |
742 } | |
743 profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value); | |
744 } | |
745 | |
746 void ChromeLauncherController::PersistPinnedState() { | |
747 // It is a coding error to call PersistPinnedState() if the pinned apps are | |
748 // not user-editable. The code should check earlier and not perform any | |
749 // modification actions that trigger persisting the state. | |
750 if (!CanPin()) { | |
751 NOTREACHED() << "Can't pin but pinned state being updated"; | |
752 return; | |
753 } | |
754 | |
755 // Set kUseDefaultPinnedApps to false and use pinned apps list from prefs | |
756 // from now on. | |
757 profile_->GetPrefs()->SetBoolean(prefs::kUseDefaultPinnedApps, false); | |
758 | |
759 // Mutating kPinnedLauncherApps is going to notify us and trigger us to | |
760 // process the change. We don't want that to happen so remove ourselves as a | |
761 // listener. | |
762 pref_change_registrar_.Remove(prefs::kPinnedLauncherApps, this); | |
763 { | |
764 ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); | |
765 updater->Clear(); | |
766 for (size_t i = 0; i < model_->items().size(); ++i) { | |
767 if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { | |
768 ash::LauncherID id = model_->items()[i].id; | |
769 if (id_to_item_map_.find(id) != id_to_item_map_.end() && | |
770 IsPinned(id)) { | |
771 base::DictionaryValue* app_value = ash::CreateAppDict( | |
772 id_to_item_map_[id].app_id); | |
773 if (app_value) | |
774 updater->Append(app_value); | |
775 } | |
776 } | |
777 } | |
778 } | |
779 pref_change_registrar_.Add(prefs::kPinnedLauncherApps, this); | |
780 } | |
781 | |
782 void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) { | |
783 app_tab_helper_.reset(helper); | |
784 } | |
785 | |
786 void ChromeLauncherController::SetAppIconLoaderForTest(AppIconLoader* loader) { | |
787 app_icon_loader_.reset(loader); | |
788 } | |
789 | |
790 Profile* ChromeLauncherController::GetProfileForNewWindows() { | |
791 return ProfileManager::GetDefaultProfileOrOffTheRecord(); | |
792 } | |
793 | |
794 ash::LauncherItemStatus ChromeLauncherController::GetItemStatus( | |
795 ash::LauncherID id) const { | |
796 int index = model_->ItemIndexByID(id); | |
797 DCHECK_GE(index, 0); | |
798 const ash::LauncherItem& item = model_->items()[index]; | |
799 return item.status; | |
800 } | |
801 | |
802 void ChromeLauncherController::MarkAppPending(const std::string& app_id) { | |
803 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
804 i != id_to_item_map_.end(); ++i) { | |
805 if (i->second.item_type == TYPE_APP && i->second.app_id == app_id) { | |
806 if (GetItemStatus(i->first) == ash::STATUS_CLOSED) | |
807 SetItemStatus(i->first, ash::STATUS_IS_PENDING); | |
808 | |
809 break; | |
810 } | |
811 } | |
812 } | |
813 | |
814 void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) { | |
815 // If there is an item, do nothing and return. | |
816 if (IsAppPinned(app_id)) | |
817 return; | |
818 | |
819 // Otherwise, create an item for it. | |
820 CreateAppLauncherItem(NULL, app_id, ash::STATUS_CLOSED); | |
821 if (CanPin()) | |
822 PersistPinnedState(); | |
823 } | |
824 | |
825 void ChromeLauncherController::DoUnpinAppsWithID(const std::string& app_id) { | |
826 for (IDToItemMap::iterator i = id_to_item_map_.begin(); | |
827 i != id_to_item_map_.end(); ) { | |
828 IDToItemMap::iterator current(i); | |
829 ++i; | |
830 if (current->second.app_id == app_id && IsPinned(current->first)) | |
831 Unpin(current->first); | |
832 } | |
833 } | |
834 | |
835 void ChromeLauncherController::UpdateAppLaunchersFromPref() { | |
836 // Construct a vector representation of to-be-pinned apps from the pref. | |
837 std::vector<std::string> pinned_apps; | |
838 const base::ListValue* pinned_apps_pref = | |
839 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); | |
840 for (base::ListValue::const_iterator it(pinned_apps_pref->begin()); | |
841 it != pinned_apps_pref->end(); ++it) { | |
842 DictionaryValue* app = NULL; | |
843 std::string app_id; | |
844 if ((*it)->GetAsDictionary(&app) && | |
845 app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) && | |
846 std::find(pinned_apps.begin(), pinned_apps.end(), app_id) == | |
847 pinned_apps.end() && | |
848 app_tab_helper_->IsValidID(app_id)) { | |
849 pinned_apps.push_back(app_id); | |
850 } | |
851 } | |
852 | |
853 // Walk the model and |pinned_apps| from the pref lockstep, adding and | |
854 // removing items as necessary. NB: This code uses plain old indexing instead | |
855 // of iterators because of model mutations as part of the loop. | |
856 std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin()); | |
857 int index = 0; | |
858 for (; index < model_->item_count() && pref_app_id != pinned_apps.end(); | |
859 ++index) { | |
860 // If the next app launcher according to the pref is present in the model, | |
861 // delete all app launcher entries in between. | |
862 if (IsAppPinned(*pref_app_id)) { | |
863 for (; index < model_->item_count(); ++index) { | |
864 const ash::LauncherItem& item(model_->items()[index]); | |
865 if (item.type != ash::TYPE_APP_SHORTCUT) | |
866 continue; | |
867 | |
868 IDToItemMap::const_iterator entry(id_to_item_map_.find(item.id)); | |
869 if (entry != id_to_item_map_.end() && | |
870 entry->second.app_id == *pref_app_id) { | |
871 // Current item will be kept. Reset its pending state and ensure | |
872 // its icon is loaded since it has to be valid to be in |pinned_apps|. | |
873 if (item.status == ash::STATUS_IS_PENDING) { | |
874 SetItemStatus(item.id, ash::STATUS_CLOSED); | |
875 app_icon_loader_->FetchImage(*pref_app_id); | |
876 } | |
877 | |
878 ++pref_app_id; | |
879 break; | |
880 } else { | |
881 LauncherItemClosed(item.id); | |
882 --index; | |
883 } | |
884 } | |
885 // If the item wasn't found, that means id_to_item_map_ is out of sync. | |
886 DCHECK(index < model_->item_count()); | |
887 } else { | |
888 // This app wasn't pinned before, insert a new entry. | |
889 ash::LauncherID id = InsertAppLauncherItem(NULL, *pref_app_id, | |
890 ash::STATUS_CLOSED, index); | |
891 index = model_->ItemIndexByID(id); | |
892 ++pref_app_id; | |
893 } | |
894 } | |
895 | |
896 // Remove any trailing existing items. | |
897 while (index < model_->item_count()) { | |
898 const ash::LauncherItem& item(model_->items()[index]); | |
899 if (item.type == ash::TYPE_APP_SHORTCUT) | |
900 LauncherItemClosed(item.id); | |
901 else | |
902 ++index; | |
903 } | |
904 | |
905 // Append unprocessed items from the pref to the end of the model. | |
906 for (; pref_app_id != pinned_apps.end(); ++pref_app_id) | |
907 CreateAppLauncherItem(NULL, *pref_app_id, ash::STATUS_CLOSED); | |
908 } | |
909 | |
910 void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() { | |
911 const std::string behavior_value( | |
912 profile_->GetPrefs()->GetString(prefs::kShelfAutoHideBehavior)); | |
913 ash::ShelfAutoHideBehavior behavior = | |
914 ash::SHELF_AUTO_HIDE_BEHAVIOR_DEFAULT; | |
915 if (behavior_value == ash::kShelfAutoHideBehaviorNever) | |
916 behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER; | |
917 else if (behavior_value == ash::kShelfAutoHideBehaviorAlways) | |
918 behavior = ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; | |
919 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(behavior); | |
920 } | |
921 | |
922 void ChromeLauncherController::SetShelfAlignmentFromPrefs() { | |
923 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
924 switches::kShowLauncherAlignmentMenu)) | |
925 return; | |
926 | |
927 const std::string alignment_value( | |
928 profile_->GetPrefs()->GetString(prefs::kShelfAlignment)); | |
929 ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; | |
930 if (alignment_value == ash::kShelfAlignmentLeft) | |
931 alignment = ash::SHELF_ALIGNMENT_LEFT; | |
932 else if (alignment_value == ash::kShelfAlignmentRight) | |
933 alignment = ash::SHELF_ALIGNMENT_RIGHT; | |
934 ash::Shell::GetInstance()->SetShelfAlignment(alignment); | |
935 } | |
936 | |
937 TabContents* ChromeLauncherController::GetLastActiveTabContents( | |
938 const std::string& app_id) { | |
939 AppIDToTabContentsListMap::const_iterator i = | |
940 app_id_to_tab_contents_list_.find(app_id); | |
941 if (i == app_id_to_tab_contents_list_.end()) | |
942 return NULL; | |
943 DCHECK_GT(i->second.size(), 0u); | |
944 return *i->second.begin(); | |
945 } | |
946 | |
947 ash::LauncherID ChromeLauncherController::InsertAppLauncherItem( | |
948 BrowserLauncherItemController* controller, | |
949 const std::string& app_id, | |
950 ash::LauncherItemStatus status, | |
951 int index) { | |
952 ash::LauncherID id = model_->next_id(); | |
953 DCHECK(id_to_item_map_.find(id) == id_to_item_map_.end()); | |
954 id_to_item_map_[id].item_type = TYPE_APP; | |
955 id_to_item_map_[id].app_id = app_id; | |
956 id_to_item_map_[id].controller = controller; | |
957 | |
958 ash::LauncherItem item; | |
959 if (!controller) { | |
960 if (status == ash::STATUS_CLOSED) | |
961 item.type = ash::TYPE_APP_SHORTCUT; | |
962 else | |
963 item.type = ash::TYPE_PLATFORM_APP; | |
964 } else if (controller->type() == | |
965 BrowserLauncherItemController::TYPE_APP_PANEL || | |
966 controller->type() == | |
967 BrowserLauncherItemController::TYPE_EXTENSION_PANEL) { | |
968 item.type = ash::TYPE_APP_PANEL; | |
969 } else { | |
970 item.type = ash::TYPE_TABBED; | |
971 } | |
972 item.is_incognito = false; | |
973 item.image = Extension::GetDefaultIcon(true); | |
974 if (item.type == ash::TYPE_APP_SHORTCUT && | |
975 !app_tab_helper_->IsValidID(app_id)) { | |
976 item.status = ash::STATUS_IS_PENDING; | |
977 } else { | |
978 TabContents* active_tab = GetLastActiveTabContents(app_id); | |
979 if (active_tab) { | |
980 Browser* browser = browser::FindBrowserWithWebContents( | |
981 active_tab->web_contents()); | |
982 DCHECK(browser); | |
983 if (browser->window()->IsActive()) | |
984 status = ash::STATUS_ACTIVE; | |
985 else | |
986 status = ash::STATUS_RUNNING; | |
987 } | |
988 item.status = status; | |
989 } | |
990 model_->AddAt(index, item); | |
991 | |
992 if (!controller || controller->type() != | |
993 BrowserLauncherItemController::TYPE_EXTENSION_PANEL) { | |
994 if (item.status != ash::STATUS_IS_PENDING) | |
995 app_icon_loader_->FetchImage(app_id); | |
996 } | |
997 return id; | |
998 } | |
OLD | NEW |