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 HasWindow(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->HasWindow(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 (!CommandLine::ForCurrentProcess()->HasSwitch( 1338 switches::kShowShelfAlignmentMenu)) 1339 return; 1340 1341 ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); 1342 1343 for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); 1344 iter != root_windows.end(); ++iter) { 1345 // See comment in |kShelfAlignment| as to why we consider two prefs. 1346 const std::string alignment_value( 1347 GetPrefForRootWindow(profile_->GetPrefs(), 1348 *iter, 1349 prefs::kShelfAlignmentLocal, 1350 prefs::kShelfAlignment)); 1351 ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; 1352 if (alignment_value == ash::kShelfAlignmentLeft) 1353 alignment = ash::SHELF_ALIGNMENT_LEFT; 1354 else if (alignment_value == ash::kShelfAlignmentRight) 1355 alignment = ash::SHELF_ALIGNMENT_RIGHT; 1356 else if (alignment_value == ash::kShelfAlignmentTop) 1357 alignment = ash::SHELF_ALIGNMENT_TOP; 1358 ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter); 1359 } 1360 } 1361 1362 void ChromeLauncherControllerPerBrowser::SetShelfBehaviorsFromPrefs() { 1363 SetShelfAutoHideBehaviorFromPrefs(); 1364 SetShelfAlignmentFromPrefs(); 1365 } 1366 1367 WebContents* ChromeLauncherControllerPerBrowser::GetLastActiveWebContents( 1368 const std::string& app_id) { 1369 AppIDToWebContentsListMap::const_iterator i = 1370 app_id_to_web_contents_list_.find(app_id); 1371 if (i == app_id_to_web_contents_list_.end()) 1372 return NULL; 1373 DCHECK_GT(i->second.size(), 0u); 1374 return *i->second.begin(); 1375 } 1376 1377 ash::LauncherID ChromeLauncherControllerPerBrowser::InsertAppLauncherItem( 1378 LauncherItemController* controller, 1379 const std::string& app_id, 1380 ash::LauncherItemStatus status, 1381 int index) { 1382 ash::LauncherID id = model_->next_id(); 1383 DCHECK(!HasItemController(id)); 1384 DCHECK(controller); 1385 id_to_item_controller_map_[id] = controller; 1386 controller->set_launcher_id(id); 1387 1388 ash::LauncherItem item; 1389 item.type = controller->GetLauncherItemType(); 1390 item.is_incognito = false; 1391 item.image = extensions::IconsInfo::GetDefaultAppIcon(); 1392 1393 WebContents* active_tab = GetLastActiveWebContents(app_id); 1394 if (active_tab) { 1395 Browser* browser = chrome::FindBrowserWithWebContents(active_tab); 1396 DCHECK(browser); 1397 if (browser->window()->IsActive()) 1398 status = ash::STATUS_ACTIVE; 1399 else 1400 status = ash::STATUS_RUNNING; 1401 } 1402 item.status = status; 1403 1404 model_->AddAt(index, item); 1405 1406 app_icon_loader_->FetchImage(app_id); 1407 1408 return id; 1409 } 1410 1411 bool ChromeLauncherControllerPerBrowser::HasItemController( 1412 ash::LauncherID id) const { 1413 return id_to_item_controller_map_.find(id) != 1414 id_to_item_controller_map_.end(); 1415 } 1416 1417 ash::LauncherID 1418 ChromeLauncherControllerPerBrowser::CreateBrowserShortcutLauncherItem() { 1419 ash::LauncherItem browser_shortcut; 1420 browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; 1421 browser_shortcut.is_incognito = false; 1422 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 1423 browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); 1424 ash::LauncherID id = model_->next_id(); 1425 size_t index = GetChromeIconIndexFromPref(); 1426 model_->AddAt(index, browser_shortcut); 1427 return id; 1428 } 1429 1430 void ChromeLauncherControllerPerBrowser::SetChromeIconIndexToPref(int index) { 1431 profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index); 1432 } 1433 1434 int ChromeLauncherControllerPerBrowser::GetChromeIconIndexFromPref() const { 1435 size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex); 1436 const base::ListValue* pinned_apps_pref = 1437 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); 1438 if (ash::switches::UseAlternateShelfLayout()) 1439 return std::max(static_cast<size_t>(1), 1440 std::min(pinned_apps_pref->GetSize(), index)); 1441 return std::max(static_cast<size_t>(0), 1442 std::min(pinned_apps_pref->GetSize(), index)); 1443 } 1444 1445 ash::LauncherID 1446 ChromeLauncherControllerPerBrowser::CreateAppShortcutLauncherItem( 1447 const std::string& app_id, 1448 int index) { 1449 AppShortcutLauncherItemController* controller = 1450 new AppShortcutLauncherItemController(app_id, this); 1451 ash::LauncherID launcher_id = InsertAppLauncherItem( 1452 controller, app_id, ash::STATUS_CLOSED, index); 1453 return launcher_id; 1454 } 1455 1456 void ChromeLauncherControllerPerBrowser::SetAppTabHelperForTest( 1457 AppTabHelper* helper) { 1458 app_tab_helper_.reset(helper); 1459 } 1460 1461 void ChromeLauncherControllerPerBrowser::SetAppIconLoaderForTest( 1462 extensions::AppIconLoader* loader) { 1463 app_icon_loader_.reset(loader); 1464 } 1465 1466 const std::string& 1467 ChromeLauncherControllerPerBrowser::GetAppIdFromLauncherIdForTest( 1468 ash::LauncherID id) { 1469 return id_to_item_controller_map_[id]->app_id(); 1470 } 1471 1472 void ChromeLauncherControllerPerBrowser:: 1473 MoveItemWithoutPinnedStateChangeNotification(int source_index, 1474 int target_index) { 1475 base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true); 1476 model_->Move(source_index, target_index); 1477 } 1478