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