1 // Copyright 2013 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.h" 6 7 #include <vector> 8 9 #include "ash/ash_switches.h" 10 #include "ash/desktop_background/desktop_background_controller.h" 11 #include "ash/launcher/launcher.h" 12 #include "ash/multi_profile_uma.h" 13 #include "ash/root_window_controller.h" 14 #include "ash/shelf/shelf_item_delegate_manager.h" 15 #include "ash/shelf/shelf_layout_manager.h" 16 #include "ash/shelf/shelf_model.h" 17 #include "ash/shelf/shelf_widget.h" 18 #include "ash/shell.h" 19 #include "ash/wm/window_util.h" 20 #include "base/command_line.h" 21 #include "base/prefs/scoped_user_pref_update.h" 22 #include "base/strings/string_number_conversions.h" 23 #include "base/strings/utf_string_conversions.h" 24 #include "base/values.h" 25 #include "chrome/browser/app_mode/app_mode_utils.h" 26 #include "chrome/browser/chrome_notification_types.h" 27 #include "chrome/browser/defaults.h" 28 #include "chrome/browser/extensions/app_icon_loader_impl.h" 29 #include "chrome/browser/extensions/extension_service.h" 30 #include "chrome/browser/extensions/extension_system.h" 31 #include "chrome/browser/extensions/extension_util.h" 32 #include "chrome/browser/extensions/launch_util.h" 33 #include "chrome/browser/favicon/favicon_tab_helper.h" 34 #include "chrome/browser/prefs/incognito_mode_prefs.h" 35 #include "chrome/browser/prefs/pref_service_syncable.h" 36 #include "chrome/browser/profiles/profile.h" 37 #include "chrome/browser/profiles/profile_manager.h" 38 #include "chrome/browser/ui/ash/app_sync_ui_state.h" 39 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" 40 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h" 41 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" 42 #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h" 43 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" 44 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h" 45 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" 46 #include "chrome/browser/ui/ash/launcher/chrome_launcher_types.h" 47 #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" 48 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" 49 #include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" 50 #include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h" 51 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" 52 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h" 53 #include "chrome/browser/ui/browser.h" 54 #include "chrome/browser/ui/browser_commands.h" 55 #include "chrome/browser/ui/browser_finder.h" 56 #include "chrome/browser/ui/browser_list.h" 57 #include "chrome/browser/ui/browser_tabstrip.h" 58 #include "chrome/browser/ui/browser_window.h" 59 #include "chrome/browser/ui/extensions/application_launch.h" 60 #include "chrome/browser/ui/extensions/extension_enable_flow.h" 61 #include "chrome/browser/ui/host_desktop.h" 62 #include "chrome/browser/ui/tabs/tab_strip_model.h" 63 #include "chrome/browser/web_applications/web_app.h" 64 #include "chrome/common/chrome_switches.h" 65 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 66 #include "chrome/common/extensions/manifest_handlers/icons_handler.h" 67 #include "chrome/common/pref_names.h" 68 #include "chrome/common/url_constants.h" 69 #include "content/public/browser/navigation_entry.h" 70 #include "content/public/browser/notification_registrar.h" 71 #include "content/public/browser/notification_service.h" 72 #include "content/public/browser/web_contents.h" 73 #include "extensions/common/extension.h" 74 #include "extensions/common/extension_resource.h" 75 #include "extensions/common/url_pattern.h" 76 #include "grit/ash_resources.h" 77 #include "grit/chromium_strings.h" 78 #include "grit/generated_resources.h" 79 #include "grit/theme_resources.h" 80 #include "grit/ui_resources.h" 81 #include "net/base/url_util.h" 82 #include "ui/aura/root_window.h" 83 #include "ui/aura/window.h" 84 #include "ui/base/l10n/l10n_util.h" 85 #include "ui/views/corewm/window_animations.h" 86 87 #if defined(OS_CHROMEOS) 88 #include "chrome/browser/browser_process.h" 89 #include "chrome/browser/chromeos/login/user_manager.h" 90 #include "chrome/browser/chromeos/login/wallpaper_manager.h" 91 #include "chrome/browser/ui/ash/chrome_shell_delegate.h" 92 #include "chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.h" 93 #include "chrome/browser/ui/ash/launcher/multi_profile_shell_window_launcher_controller.h" 94 #endif 95 96 using extensions::Extension; 97 using extensions::UnloadedExtensionInfo; 98 using extension_misc::kGmailAppId; 99 using content::WebContents; 100 101 // static 102 ChromeLauncherController* ChromeLauncherController::instance_ = NULL; 103 104 namespace { 105 106 // This will be used as placeholder in the list of the pinned applciatons. 107 // Note that this is NOT a valid extension identifier so that pre M31 versions 108 // will ignore it. 109 const char kAppLauncherIdPlaceholder[] = "AppLauncherIDPlaceholder--------"; 110 111 std::string GetPrefKeyForRootWindow(aura::Window* root_window) { 112 gfx::Display display = gfx::Screen::GetScreenFor( 113 root_window)->GetDisplayNearestWindow(root_window); 114 DCHECK(display.is_valid()); 115 116 return base::Int64ToString(display.id()); 117 } 118 119 void UpdatePerDisplayPref(PrefService* pref_service, 120 aura::Window* root_window, 121 const char* pref_key, 122 const std::string& value) { 123 std::string key = GetPrefKeyForRootWindow(root_window); 124 if (key.empty()) 125 return; 126 127 DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences); 128 base::DictionaryValue* shelf_prefs = update.Get(); 129 base::DictionaryValue* prefs = NULL; 130 if (!shelf_prefs->GetDictionary(key, &prefs)) { 131 prefs = new base::DictionaryValue(); 132 shelf_prefs->Set(key, prefs); 133 } 134 prefs->SetStringWithoutPathExpansion(pref_key, value); 135 } 136 137 // Returns a pref value in |pref_service| for the display of |root_window|. The 138 // pref value is stored in |local_path| and |path|, but |pref_service| may have 139 // per-display preferences and the value can be specified by policy. Here is 140 // the priority: 141 // * A value managed by policy. This is a single value that applies to all 142 // displays. 143 // * A user-set value for the specified display. 144 // * A user-set value in |local_path| or |path|, if no per-display settings are 145 // ever specified (see http://crbug.com/173719 for why). |local_path| is 146 // preferred. See comment in |kShelfAlignment| as to why we consider two 147 // prefs and why |local_path| is preferred. 148 // * A value recommended by policy. This is a single value that applies to all 149 // root windows. 150 // * The default value for |local_path| if the value is not recommended by 151 // policy. 152 std::string GetPrefForRootWindow(PrefService* pref_service, 153 aura::Window* root_window, 154 const char* local_path, 155 const char* path) { 156 const PrefService::Preference* local_pref = 157 pref_service->FindPreference(local_path); 158 const std::string value(pref_service->GetString(local_path)); 159 if (local_pref->IsManaged()) 160 return value; 161 162 std::string pref_key = GetPrefKeyForRootWindow(root_window); 163 bool has_per_display_prefs = false; 164 if (!pref_key.empty()) { 165 const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary( 166 prefs::kShelfPreferences); 167 const base::DictionaryValue* display_pref = NULL; 168 std::string per_display_value; 169 if (shelf_prefs->GetDictionary(pref_key, &display_pref) && 170 display_pref->GetString(path, &per_display_value)) 171 return per_display_value; 172 173 // If the pref for the specified display is not found, scan the whole prefs 174 // and check if the prefs for other display is already specified. 175 std::string unused_value; 176 for (base::DictionaryValue::Iterator iter(*shelf_prefs); 177 !iter.IsAtEnd(); iter.Advance()) { 178 const base::DictionaryValue* display_pref = NULL; 179 if (iter.value().GetAsDictionary(&display_pref) && 180 display_pref->GetString(path, &unused_value)) { 181 has_per_display_prefs = true; 182 break; 183 } 184 } 185 } 186 187 if (local_pref->IsRecommended() || !has_per_display_prefs) 188 return value; 189 190 const base::Value* default_value = 191 pref_service->GetDefaultPrefValue(local_path); 192 std::string default_string; 193 default_value->GetAsString(&default_string); 194 return default_string; 195 } 196 197 // If prefs have synced and no user-set value exists at |local_path|, the value 198 // from |synced_path| is copied to |local_path|. 199 void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service, 200 const char* local_path, 201 const char* synced_path) { 202 if (!pref_service->FindPreference(local_path)->HasUserSetting() && 203 pref_service->IsSyncing()) { 204 // First time the user is using this machine, propagate from remote to 205 // local. 206 pref_service->SetString(local_path, pref_service->GetString(synced_path)); 207 } 208 } 209 210 std::string GetSourceFromAppListSource(ash::LaunchSource source) { 211 switch (source) { 212 case ash::LAUNCH_FROM_APP_LIST: 213 return std::string(extension_urls::kLaunchSourceAppList); 214 case ash::LAUNCH_FROM_APP_LIST_SEARCH: 215 return std::string(extension_urls::kLaunchSourceAppListSearch); 216 default: return std::string(); 217 } 218 } 219 220 } // namespace 221 222 #if defined(OS_CHROMEOS) 223 // A class to get events from ChromeOS when a user gets changed or added. 224 class ChromeLauncherControllerUserSwitchObserverChromeOS 225 : public ChromeLauncherControllerUserSwitchObserver, 226 public chromeos::UserManager::UserSessionStateObserver, 227 content::NotificationObserver { 228 public: 229 ChromeLauncherControllerUserSwitchObserverChromeOS( 230 ChromeLauncherController* controller) 231 : controller_(controller) { 232 DCHECK(chromeos::UserManager::IsInitialized()); 233 chromeos::UserManager::Get()->AddSessionStateObserver(this); 234 // A UserAddedToSession notification can be sent before a profile is loaded. 235 // Since our observers require that we have already a profile, we might have 236 // to postpone the notification until the ProfileManager lets us know that 237 // the profile for that newly added user was added to the ProfileManager. 238 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED, 239 content::NotificationService::AllSources()); 240 } 241 virtual ~ChromeLauncherControllerUserSwitchObserverChromeOS() { 242 chromeos::UserManager::Get()->RemoveSessionStateObserver(this); 243 } 244 245 // chromeos::UserManager::UserSessionStateObserver overrides: 246 virtual void ActiveUserChanged(const chromeos::User* active_user) OVERRIDE; 247 virtual void UserAddedToSession(const chromeos::User* added_user) OVERRIDE; 248 249 // content::NotificationObserver overrides: 250 virtual void Observe(int type, 251 const content::NotificationSource& source, 252 const content::NotificationDetails& details) OVERRIDE; 253 254 private: 255 // Add a user to the session. 256 void AddUser(Profile* profile); 257 258 // The owning ChromeLauncherController. 259 ChromeLauncherController* controller_; 260 261 // The notification registrar to track the Profile creations after a user got 262 // added to the session (if required). 263 content::NotificationRegistrar registrar_; 264 265 // Users which were just added to the system, but which profiles were not yet 266 // (fully) loaded. 267 std::set<std::string> added_user_ids_waiting_for_profiles_; 268 269 DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerUserSwitchObserverChromeOS); 270 }; 271 272 void ChromeLauncherControllerUserSwitchObserverChromeOS::ActiveUserChanged( 273 const chromeos::User* active_user) { 274 const std::string& user_email = active_user->email(); 275 // Forward the OS specific event to the ChromeLauncherController. 276 controller_->ActiveUserChanged(user_email); 277 // TODO(skuhne): At the moment the login screen does the wallpaper management 278 // and wallpapers are not synchronized across multiple desktops. 279 if (chromeos::WallpaperManager::Get()) 280 chromeos::WallpaperManager::Get()->SetUserWallpaper(user_email); 281 } 282 283 void ChromeLauncherControllerUserSwitchObserverChromeOS::UserAddedToSession( 284 const chromeos::User* active_user) { 285 Profile* profile = multi_user_util::GetProfileFromUserID( 286 active_user->email()); 287 // If we do not have a profile yet, we postpone forwarding the notification 288 // until it is loaded. 289 if (!profile) 290 added_user_ids_waiting_for_profiles_.insert(active_user->email()); 291 else 292 AddUser(profile); 293 } 294 295 void ChromeLauncherControllerUserSwitchObserverChromeOS::Observe( 296 int type, 297 const content::NotificationSource& source, 298 const content::NotificationDetails& details) { 299 if (type == chrome::NOTIFICATION_PROFILE_ADDED && 300 !added_user_ids_waiting_for_profiles_.empty()) { 301 // Check if the profile is from a user which was on the waiting list. 302 Profile* profile = content::Source<Profile>(source).ptr(); 303 std::string user_id = multi_user_util::GetUserIDFromProfile(profile); 304 std::set<std::string>::iterator it = std::find( 305 added_user_ids_waiting_for_profiles_.begin(), 306 added_user_ids_waiting_for_profiles_.end(), 307 user_id); 308 if (it != added_user_ids_waiting_for_profiles_.end()) { 309 added_user_ids_waiting_for_profiles_.erase(it); 310 AddUser(profile->GetOriginalProfile()); 311 } 312 } 313 } 314 315 void ChromeLauncherControllerUserSwitchObserverChromeOS::AddUser( 316 Profile* profile) { 317 if (chrome::MultiUserWindowManager::GetMultiProfileMode() == 318 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) 319 chrome::MultiUserWindowManager::GetInstance()->AddUser(profile); 320 controller_->AdditionalUserAddedToSession(profile->GetOriginalProfile()); 321 } 322 #endif 323 324 ChromeLauncherController::ChromeLauncherController(Profile* profile, 325 ash::ShelfModel* model) 326 : model_(model), 327 item_delegate_manager_(NULL), 328 profile_(profile), 329 app_sync_ui_state_(NULL), 330 ignore_persist_pinned_state_change_(false) { 331 if (!profile_) { 332 // If no profile was passed, we take the currently active profile and use it 333 // as the owner of the current desktop. 334 // Use the original profile as on chromeos we may get a temporary off the 335 // record profile, unless in guest session (where off the record profile is 336 // the right one). 337 Profile* active_profile = ProfileManager::GetActiveUserProfile(); 338 profile_ = active_profile->IsGuestSession() ? active_profile : 339 active_profile->GetOriginalProfile(); 340 341 app_sync_ui_state_ = AppSyncUIState::Get(profile_); 342 if (app_sync_ui_state_) 343 app_sync_ui_state_->AddObserver(this); 344 } 345 346 // All profile relevant settings get bound to the current profile. 347 AttachProfile(profile_); 348 model_->AddObserver(this); 349 350 // In multi profile mode we might have a window manager. We try to create it 351 // here. If the instantiation fails, the manager is not needed. 352 chrome::MultiUserWindowManager::CreateInstance(); 353 354 #if defined(OS_CHROMEOS) 355 // On Chrome OS using multi profile we want to switch the content of the shelf 356 // with a user change. Note that for unit tests the instance can be NULL. 357 if (chrome::MultiUserWindowManager::GetMultiProfileMode() != 358 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF) { 359 user_switch_observer_.reset( 360 new ChromeLauncherControllerUserSwitchObserverChromeOS(this)); 361 } 362 363 // Create our v1/v2 application / browser monitors which will inform the 364 // launcher of status changes. 365 if (chrome::MultiUserWindowManager::GetMultiProfileMode() == 366 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) { 367 // If running in separated destkop mode, we create the multi profile version 368 // of status monitor. 369 browser_status_monitor_.reset(new MultiProfileBrowserStatusMonitor(this)); 370 shell_window_controller_.reset( 371 new MultiProfileShellWindowLauncherController(this)); 372 } else { 373 // Create our v1/v2 application / browser monitors which will inform the 374 // launcher of status changes. 375 browser_status_monitor_.reset(new BrowserStatusMonitor(this)); 376 shell_window_controller_.reset(new ShellWindowLauncherController(this)); 377 } 378 #else 379 // Create our v1/v2 application / browser monitors which will inform the 380 // launcher of status changes. 381 browser_status_monitor_.reset(new BrowserStatusMonitor(this)); 382 shell_window_controller_.reset(new ShellWindowLauncherController(this)); 383 #endif 384 385 // Right now ash::Shell isn't created for tests. 386 // TODO(mukai): Allows it to observe display change and write tests. 387 if (ash::Shell::HasInstance()) { 388 ash::Shell::GetInstance()->display_controller()->AddObserver(this); 389 item_delegate_manager_ = 390 ash::Shell::GetInstance()->shelf_item_delegate_manager(); 391 } 392 393 notification_registrar_.Add(this, 394 chrome::NOTIFICATION_EXTENSION_LOADED, 395 content::Source<Profile>(profile_)); 396 notification_registrar_.Add(this, 397 chrome::NOTIFICATION_EXTENSION_UNLOADED, 398 content::Source<Profile>(profile_)); 399 } 400 401 ChromeLauncherController::~ChromeLauncherController() { 402 // Reset the BrowserStatusMonitor as it has a weak pointer to this. 403 browser_status_monitor_.reset(); 404 405 // Reset the shell window controller here since it has a weak pointer to this. 406 shell_window_controller_.reset(); 407 408 for (std::set<ash::Launcher*>::iterator iter = launchers_.begin(); 409 iter != launchers_.end(); 410 ++iter) 411 (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this); 412 413 model_->RemoveObserver(this); 414 if (ash::Shell::HasInstance()) 415 ash::Shell::GetInstance()->display_controller()->RemoveObserver(this); 416 for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); 417 i != id_to_item_controller_map_.end(); ++i) { 418 int index = model_->ItemIndexByID(i->first); 419 // A "browser proxy" is not known to the model and this removal does 420 // therefore not need to be propagated to the model. 421 if (index != -1 && 422 model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT) 423 model_->RemoveItemAt(index); 424 } 425 426 if (ash::Shell::HasInstance()) 427 ash::Shell::GetInstance()->RemoveShellObserver(this); 428 429 // Release all profile dependent resources. 430 ReleaseProfile(); 431 if (instance_ == this) 432 instance_ = NULL; 433 434 // Get rid of the multi user window manager instance. 435 chrome::MultiUserWindowManager::DeleteInstance(); 436 } 437 438 // static 439 ChromeLauncherController* ChromeLauncherController::CreateInstance( 440 Profile* profile, 441 ash::ShelfModel* model) { 442 // We do not check here for re-creation of the ChromeLauncherController since 443 // it appears that it might be intentional that the ChromeLauncherController 444 // can be re-created. 445 instance_ = new ChromeLauncherController(profile, model); 446 return instance_; 447 } 448 449 void ChromeLauncherController::Init() { 450 CreateBrowserShortcutLauncherItem(); 451 UpdateAppLaunchersFromPref(); 452 453 // TODO(sky): update unit test so that this test isn't necessary. 454 if (ash::Shell::HasInstance()) { 455 SetShelfAutoHideBehaviorFromPrefs(); 456 SetShelfAlignmentFromPrefs(); 457 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); 458 if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() || 459 !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)-> 460 HasUserSetting()) { 461 // This causes OnIsSyncingChanged to be called when the value of 462 // PrefService::IsSyncing() changes. 463 prefs->AddObserver(this); 464 } 465 ash::Shell::GetInstance()->AddShellObserver(this); 466 } 467 } 468 469 ash::LauncherID ChromeLauncherController::CreateAppLauncherItem( 470 LauncherItemController* controller, 471 const std::string& app_id, 472 ash::LauncherItemStatus status) { 473 CHECK(controller); 474 int index = 0; 475 // Panels are inserted on the left so as not to push all existing panels over. 476 if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) 477 index = model_->item_count(); 478 return InsertAppLauncherItem(controller, 479 app_id, 480 status, 481 index, 482 controller->GetLauncherItemType()); 483 } 484 485 void ChromeLauncherController::SetItemStatus( 486 ash::LauncherID id, 487 ash::LauncherItemStatus status) { 488 int index = model_->ItemIndexByID(id); 489 ash::LauncherItemStatus old_status = model_->items()[index].status; 490 // Since ordinary browser windows are not registered, we might get a negative 491 // index here. 492 if (index >= 0 && old_status != status) { 493 ash::LauncherItem item = model_->items()[index]; 494 item.status = status; 495 model_->Set(index, item); 496 } 497 } 498 499 void ChromeLauncherController::SetItemController( 500 ash::LauncherID id, 501 LauncherItemController* controller) { 502 CHECK(controller); 503 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); 504 CHECK(iter != id_to_item_controller_map_.end()); 505 controller->set_launcher_id(id); 506 iter->second = controller; 507 // Existing controller is destroyed and replaced by registering again. 508 SetShelfItemDelegate(id, controller); 509 } 510 511 void ChromeLauncherController::CloseLauncherItem(ash::LauncherID id) { 512 CHECK(id); 513 if (IsPinned(id)) { 514 // Create a new shortcut controller. 515 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); 516 CHECK(iter != id_to_item_controller_map_.end()); 517 SetItemStatus(id, ash::STATUS_CLOSED); 518 std::string app_id = iter->second->app_id(); 519 iter->second = new AppShortcutLauncherItemController(app_id, this); 520 iter->second->set_launcher_id(id); 521 // Existing controller is destroyed and replaced by registering again. 522 SetShelfItemDelegate(id, iter->second); 523 } else { 524 LauncherItemClosed(id); 525 } 526 } 527 528 void ChromeLauncherController::Pin(ash::LauncherID id) { 529 DCHECK(HasItemController(id)); 530 531 int index = model_->ItemIndexByID(id); 532 DCHECK_GE(index, 0); 533 534 ash::LauncherItem item = model_->items()[index]; 535 536 if (item.type == ash::TYPE_PLATFORM_APP || 537 item.type == ash::TYPE_WINDOWED_APP) { 538 item.type = ash::TYPE_APP_SHORTCUT; 539 model_->Set(index, item); 540 } else if (item.type != ash::TYPE_APP_SHORTCUT) { 541 return; 542 } 543 544 if (CanPin()) 545 PersistPinnedState(); 546 } 547 548 void ChromeLauncherController::Unpin(ash::LauncherID id) { 549 DCHECK(HasItemController(id)); 550 551 LauncherItemController* controller = id_to_item_controller_map_[id]; 552 if (controller->type() == LauncherItemController::TYPE_APP || 553 controller->locked()) { 554 UnpinRunningAppInternal(model_->ItemIndexByID(id)); 555 } else { 556 LauncherItemClosed(id); 557 } 558 if (CanPin()) 559 PersistPinnedState(); 560 } 561 562 bool ChromeLauncherController::IsPinned(ash::LauncherID id) { 563 int index = model_->ItemIndexByID(id); 564 if (index < 0) 565 return false; 566 ash::LauncherItemType type = model_->items()[index].type; 567 return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT); 568 } 569 570 void ChromeLauncherController::TogglePinned(ash::LauncherID id) { 571 if (!HasItemController(id)) 572 return; // May happen if item closed with menu open. 573 574 if (IsPinned(id)) 575 Unpin(id); 576 else 577 Pin(id); 578 } 579 580 bool ChromeLauncherController::IsPinnable(ash::LauncherID id) const { 581 int index = model_->ItemIndexByID(id); 582 if (index == -1) 583 return false; 584 585 ash::LauncherItemType type = model_->items()[index].type; 586 return ((type == ash::TYPE_APP_SHORTCUT || 587 type == ash::TYPE_PLATFORM_APP || 588 type == ash::TYPE_WINDOWED_APP) && 589 CanPin()); 590 } 591 592 void ChromeLauncherController::LockV1AppWithID( 593 const std::string& app_id) { 594 ash::LauncherID id = GetLauncherIDForAppID(app_id); 595 if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) { 596 CreateAppShortcutLauncherItemWithType(app_id, 597 model_->item_count(), 598 ash::TYPE_WINDOWED_APP); 599 id = GetLauncherIDForAppID(app_id); 600 } 601 CHECK(id); 602 id_to_item_controller_map_[id]->lock(); 603 } 604 605 void ChromeLauncherController::UnlockV1AppWithID( 606 const std::string& app_id) { 607 ash::LauncherID id = GetLauncherIDForAppID(app_id); 608 CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id)); 609 CHECK(id); 610 LauncherItemController* controller = id_to_item_controller_map_[id]; 611 controller->unlock(); 612 if (!controller->locked() && !IsPinned(id)) 613 CloseLauncherItem(id); 614 } 615 616 void ChromeLauncherController::Launch(ash::LauncherID id, 617 int event_flags) { 618 if (!HasItemController(id)) 619 return; // In case invoked from menu and item closed while menu up. 620 id_to_item_controller_map_[id]->Launch(ash::LAUNCH_FROM_UNKNOWN, event_flags); 621 } 622 623 void ChromeLauncherController::Close(ash::LauncherID id) { 624 if (!HasItemController(id)) 625 return; // May happen if menu closed. 626 id_to_item_controller_map_[id]->Close(); 627 } 628 629 bool ChromeLauncherController::IsOpen(ash::LauncherID id) { 630 if (!HasItemController(id)) 631 return false; 632 return id_to_item_controller_map_[id]->IsOpen(); 633 } 634 635 bool ChromeLauncherController::IsPlatformApp(ash::LauncherID id) { 636 if (!HasItemController(id)) 637 return false; 638 639 std::string app_id = GetAppIDForLauncherID(id); 640 const Extension* extension = GetExtensionForAppID(app_id); 641 // An extension can be synced / updated at any time and therefore not be 642 // available. 643 return extension ? extension->is_platform_app() : false; 644 } 645 646 void ChromeLauncherController::LaunchApp(const std::string& app_id, 647 ash::LaunchSource source, 648 int event_flags) { 649 // |extension| could be NULL when it is being unloaded for updating. 650 const Extension* extension = GetExtensionForAppID(app_id); 651 if (!extension) 652 return; 653 654 const ExtensionService* service = 655 extensions::ExtensionSystem::Get(profile_)->extension_service(); 656 if (!extension_util::IsAppLaunchableWithoutEnabling(app_id, service)) { 657 // Do nothing if there is already a running enable flow. 658 if (extension_enable_flow_) 659 return; 660 661 extension_enable_flow_.reset( 662 new ExtensionEnableFlow(profile_, app_id, this)); 663 extension_enable_flow_->StartForNativeWindow(NULL); 664 return; 665 } 666 667 if (LaunchedInNativeDesktop(app_id)) 668 return; 669 670 // The app will be created for the currently active profile. 671 AppLaunchParams params(profile_, 672 extension, 673 event_flags, 674 chrome::HOST_DESKTOP_TYPE_ASH); 675 if (source != ash::LAUNCH_FROM_UNKNOWN && 676 app_id == extension_misc::kWebStoreAppId) { 677 // Get the corresponding source string. 678 std::string source_value = GetSourceFromAppListSource(source); 679 680 // Set an override URL to include the source. 681 GURL extension_url = extensions::AppLaunchInfo::GetFullLaunchURL(extension); 682 params.override_url = net::AppendQueryParameter( 683 extension_url, extension_urls::kWebstoreSourceField, source_value); 684 } 685 686 OpenApplication(params); 687 } 688 689 void ChromeLauncherController::ActivateApp(const std::string& app_id, 690 ash::LaunchSource source, 691 int event_flags) { 692 // If there is an existing non-shortcut controller for this app, open it. 693 ash::LauncherID id = GetLauncherIDForAppID(app_id); 694 if (id) { 695 LauncherItemController* controller = id_to_item_controller_map_[id]; 696 controller->Activate(source); 697 return; 698 } 699 700 // Create a temporary application launcher item and use it to see if there are 701 // running instances. 702 scoped_ptr<AppShortcutLauncherItemController> app_controller( 703 new AppShortcutLauncherItemController(app_id, this)); 704 if (!app_controller->GetRunningApplications().empty()) 705 app_controller->Activate(source); 706 else 707 LaunchApp(app_id, source, event_flags); 708 } 709 710 extensions::LaunchType ChromeLauncherController::GetLaunchType( 711 ash::LauncherID id) { 712 DCHECK(HasItemController(id)); 713 714 const Extension* extension = GetExtensionForAppID( 715 id_to_item_controller_map_[id]->app_id()); 716 717 // An extension can be unloaded/updated/unavailable at any time. 718 if (!extension) 719 return extensions::LAUNCH_TYPE_DEFAULT; 720 721 return extensions::GetLaunchType( 722 profile_->GetExtensionService()->extension_prefs(), 723 extension); 724 } 725 726 ash::LauncherID ChromeLauncherController::GetLauncherIDForAppID( 727 const std::string& app_id) { 728 for (IDToItemControllerMap::const_iterator i = 729 id_to_item_controller_map_.begin(); 730 i != id_to_item_controller_map_.end(); ++i) { 731 if (i->second->type() == LauncherItemController::TYPE_APP_PANEL) 732 continue; // Don't include panels 733 if (i->second->app_id() == app_id) 734 return i->first; 735 } 736 return 0; 737 } 738 739 const std::string& ChromeLauncherController::GetAppIDForLauncherID( 740 ash::LauncherID id) { 741 CHECK(HasItemController(id)); 742 return id_to_item_controller_map_[id]->app_id(); 743 } 744 745 void ChromeLauncherController::SetAppImage(const std::string& id, 746 const gfx::ImageSkia& image) { 747 // TODO: need to get this working for shortcuts. 748 for (IDToItemControllerMap::const_iterator i = 749 id_to_item_controller_map_.begin(); 750 i != id_to_item_controller_map_.end(); ++i) { 751 LauncherItemController* controller = i->second; 752 if (controller->app_id() != id) 753 continue; 754 if (controller->image_set_by_controller()) 755 continue; 756 int index = model_->ItemIndexByID(i->first); 757 if (index == -1) 758 continue; 759 ash::LauncherItem item = model_->items()[index]; 760 item.image = image; 761 model_->Set(index, item); 762 // It's possible we're waiting on more than one item, so don't break. 763 } 764 } 765 766 void ChromeLauncherController::OnAutoHideBehaviorChanged( 767 aura::Window* root_window, 768 ash::ShelfAutoHideBehavior new_behavior) { 769 SetShelfAutoHideBehaviorPrefs(new_behavior, root_window); 770 } 771 772 void ChromeLauncherController::SetLauncherItemImage( 773 ash::LauncherID launcher_id, 774 const gfx::ImageSkia& image) { 775 int index = model_->ItemIndexByID(launcher_id); 776 if (index == -1) 777 return; 778 ash::LauncherItem item = model_->items()[index]; 779 item.image = image; 780 model_->Set(index, item); 781 } 782 783 bool ChromeLauncherController::CanPin() const { 784 const PrefService::Preference* pref = 785 profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps); 786 return pref && pref->IsUserModifiable(); 787 } 788 789 bool ChromeLauncherController::IsAppPinned(const std::string& app_id) { 790 for (IDToItemControllerMap::const_iterator i = 791 id_to_item_controller_map_.begin(); 792 i != id_to_item_controller_map_.end(); ++i) { 793 if (IsPinned(i->first) && i->second->app_id() == app_id) 794 return true; 795 } 796 return false; 797 } 798 799 bool ChromeLauncherController::IsWindowedAppInLauncher( 800 const std::string& app_id) { 801 int index = model_->ItemIndexByID(GetLauncherIDForAppID(app_id)); 802 if (index < 0) 803 return false; 804 805 ash::LauncherItemType type = model_->items()[index].type; 806 return type == ash::TYPE_WINDOWED_APP; 807 } 808 809 void ChromeLauncherController::PinAppWithID(const std::string& app_id) { 810 if (CanPin()) 811 DoPinAppWithID(app_id); 812 else 813 NOTREACHED(); 814 } 815 816 void ChromeLauncherController::SetLaunchType( 817 ash::LauncherID id, 818 extensions::LaunchType launch_type) { 819 if (!HasItemController(id)) 820 return; 821 822 extensions::SetLaunchType(profile_->GetExtensionService()->extension_prefs(), 823 id_to_item_controller_map_[id]->app_id(), 824 launch_type); 825 } 826 827 void ChromeLauncherController::UnpinAppWithID(const std::string& app_id) { 828 if (CanPin()) 829 DoUnpinAppWithID(app_id); 830 else 831 NOTREACHED(); 832 } 833 834 bool ChromeLauncherController::IsLoggedInAsGuest() { 835 return profile_->IsGuestSession(); 836 } 837 838 void ChromeLauncherController::CreateNewWindow() { 839 // Use the currently active user. 840 chrome::NewEmptyWindow(profile_, chrome::HOST_DESKTOP_TYPE_ASH); 841 } 842 843 void ChromeLauncherController::CreateNewIncognitoWindow() { 844 // Use the currently active user. 845 chrome::NewEmptyWindow(profile_->GetOffTheRecordProfile(), 846 chrome::HOST_DESKTOP_TYPE_ASH); 847 } 848 849 void ChromeLauncherController::PersistPinnedState() { 850 if (ignore_persist_pinned_state_change_) 851 return; 852 // It is a coding error to call PersistPinnedState() if the pinned apps are 853 // not user-editable. The code should check earlier and not perform any 854 // modification actions that trigger persisting the state. 855 if (!CanPin()) { 856 NOTREACHED() << "Can't pin but pinned state being updated"; 857 return; 858 } 859 // Mutating kPinnedLauncherApps is going to notify us and trigger us to 860 // process the change. We don't want that to happen so remove ourselves as a 861 // listener. 862 pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); 863 { 864 ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); 865 updater->Clear(); 866 for (size_t i = 0; i < model_->items().size(); ++i) { 867 if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { 868 ash::LauncherID id = model_->items()[i].id; 869 if (HasItemController(id) && IsPinned(id)) { 870 base::DictionaryValue* app_value = ash::CreateAppDict( 871 id_to_item_controller_map_[id]->app_id()); 872 if (app_value) 873 updater->Append(app_value); 874 } 875 } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) { 876 PersistChromeItemIndex(i); 877 } else if (model_->items()[i].type == ash::TYPE_APP_LIST) { 878 base::DictionaryValue* app_value = ash::CreateAppDict( 879 kAppLauncherIdPlaceholder); 880 if (app_value) 881 updater->Append(app_value); 882 } 883 } 884 } 885 pref_change_registrar_.Add( 886 prefs::kPinnedLauncherApps, 887 base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref, 888 base::Unretained(this))); 889 } 890 891 ash::ShelfModel* ChromeLauncherController::model() { 892 return model_; 893 } 894 895 Profile* ChromeLauncherController::profile() { 896 return profile_; 897 } 898 899 ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior( 900 aura::Window* root_window) const { 901 // Don't show the shelf in app mode. 902 if (chrome::IsRunningInAppMode()) 903 return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN; 904 905 // See comment in |kShelfAlignment| as to why we consider two prefs. 906 const std::string behavior_value( 907 GetPrefForRootWindow(profile_->GetPrefs(), 908 root_window, 909 prefs::kShelfAutoHideBehaviorLocal, 910 prefs::kShelfAutoHideBehavior)); 911 912 // Note: To maintain sync compatibility with old images of chrome/chromeos 913 // the set of values that may be encountered includes the now-extinct 914 // "Default" as well as "Never" and "Always", "Default" should now 915 // be treated as "Never" (http://crbug.com/146773). 916 if (behavior_value == ash::kShelfAutoHideBehaviorAlways) 917 return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; 918 return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER; 919 } 920 921 bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior( 922 aura::Window* root_window) const { 923 return profile_->GetPrefs()-> 924 FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable(); 925 } 926 927 void ChromeLauncherController::ToggleShelfAutoHideBehavior( 928 aura::Window* root_window) { 929 ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) == 930 ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ? 931 ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER : 932 ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; 933 SetShelfAutoHideBehaviorPrefs(behavior, root_window); 934 return; 935 } 936 937 void ChromeLauncherController::RemoveTabFromRunningApp( 938 WebContents* tab, 939 const std::string& app_id) { 940 web_contents_to_app_id_.erase(tab); 941 // BrowserShortcutLauncherItemController::UpdateBrowserItemState() will update 942 // the state when no application is associated with the tab. 943 if (app_id.empty()) 944 return; 945 946 AppIDToWebContentsListMap::iterator i_app_id = 947 app_id_to_web_contents_list_.find(app_id); 948 if (i_app_id != app_id_to_web_contents_list_.end()) { 949 WebContentsList* tab_list = &i_app_id->second; 950 tab_list->remove(tab); 951 ash::LauncherItemStatus status = ash::STATUS_RUNNING; 952 if (tab_list->empty()) { 953 app_id_to_web_contents_list_.erase(i_app_id); 954 status = ash::STATUS_CLOSED; 955 } 956 ash::LauncherID id = GetLauncherIDForAppID(app_id); 957 if (id) 958 SetItemStatus(id, status); 959 } 960 } 961 962 void ChromeLauncherController::UpdateAppState(content::WebContents* contents, 963 AppState app_state) { 964 std::string app_id = app_tab_helper_->GetAppID(contents); 965 966 // Check if the gMail app is loaded and it matches the given content. 967 // This special treatment is needed to address crbug.com/234268. 968 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) 969 app_id = kGmailAppId; 970 971 // Check the old |app_id| for a tab. If the contents has changed we need to 972 // remove it from the previous app. 973 if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) { 974 std::string last_app_id = web_contents_to_app_id_[contents]; 975 if (last_app_id != app_id) 976 RemoveTabFromRunningApp(contents, last_app_id); 977 } 978 979 web_contents_to_app_id_[contents] = app_id; 980 981 if (app_state == APP_STATE_REMOVED) { 982 // The tab has gone away. 983 RemoveTabFromRunningApp(contents, app_id); 984 } else if (!app_id.empty()) { 985 WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]); 986 WebContentsList::const_iterator i_tab = 987 std::find(tab_list.begin(), tab_list.end(), contents); 988 989 if (i_tab == tab_list.end()) 990 tab_list.push_back(contents); 991 992 if (app_state == APP_STATE_INACTIVE || app_state == APP_STATE_ACTIVE) { 993 if (i_tab != tab_list.begin()) { 994 // Going to running state, but wasn't the front tab, indicating that a 995 // new tab has already become active. 996 return; 997 } 998 } 999 1000 if (app_state == APP_STATE_ACTIVE || app_state == APP_STATE_WINDOW_ACTIVE) { 1001 tab_list.remove(contents); 1002 tab_list.push_front(contents); 1003 } 1004 1005 ash::LauncherID id = GetLauncherIDForAppID(app_id); 1006 if (id) { 1007 // If the window is active, mark the app as active. 1008 SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? 1009 ash::STATUS_ACTIVE : ash::STATUS_RUNNING); 1010 } 1011 } 1012 } 1013 1014 ash::LauncherID ChromeLauncherController::GetLauncherIDForWebContents( 1015 content::WebContents* contents) { 1016 DCHECK(contents); 1017 1018 std::string app_id = app_tab_helper_->GetAppID(contents); 1019 1020 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) 1021 app_id = kGmailAppId; 1022 1023 ash::LauncherID id = GetLauncherIDForAppID(app_id); 1024 1025 if (app_id.empty() || !id) { 1026 int browser_index = model_->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT); 1027 return model_->items()[browser_index].id; 1028 } 1029 1030 return id; 1031 } 1032 1033 void ChromeLauncherController::SetRefocusURLPatternForTest(ash::LauncherID id, 1034 const GURL& url) { 1035 DCHECK(HasItemController(id)); 1036 LauncherItemController* controller = id_to_item_controller_map_[id]; 1037 1038 int index = model_->ItemIndexByID(id); 1039 if (index == -1) { 1040 NOTREACHED() << "Invalid launcher id"; 1041 return; 1042 } 1043 1044 ash::LauncherItemType type = model_->items()[index].type; 1045 if (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_WINDOWED_APP) { 1046 AppShortcutLauncherItemController* app_controller = 1047 static_cast<AppShortcutLauncherItemController*>(controller); 1048 app_controller->set_refocus_url(url); 1049 } else { 1050 NOTREACHED() << "Invalid launcher type"; 1051 } 1052 } 1053 1054 const Extension* ChromeLauncherController::GetExtensionForAppID( 1055 const std::string& app_id) const { 1056 // Some unit tests do not have a real extension. 1057 return (profile_->GetExtensionService()) ? 1058 profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL; 1059 } 1060 1061 void ChromeLauncherController::ActivateWindowOrMinimizeIfActive( 1062 ui::BaseWindow* window, 1063 bool allow_minimize) { 1064 // In separated desktop mode we might have to teleport a window back to the 1065 // current user. 1066 if (chrome::MultiUserWindowManager::GetMultiProfileMode() == 1067 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) { 1068 aura::Window* native_window = window->GetNativeWindow(); 1069 const std::string& current_user = 1070 multi_user_util::GetUserIDFromProfile(profile()); 1071 chrome::MultiUserWindowManager* manager = 1072 chrome::MultiUserWindowManager::GetInstance(); 1073 if (!manager->IsWindowOnDesktopOfUser(native_window, current_user)) { 1074 ash::MultiProfileUMA::RecordTeleportAction( 1075 ash::MultiProfileUMA::TELEPORT_WINDOW_RETURN_BY_LAUNCHER); 1076 manager->ShowWindowForUser(native_window, current_user); 1077 window->Activate(); 1078 return; 1079 } 1080 } 1081 1082 if (window->IsActive() && allow_minimize) { 1083 if (CommandLine::ForCurrentProcess()->HasSwitch( 1084 switches::kDisableMinimizeOnSecondLauncherItemClick)) { 1085 AnimateWindow(window->GetNativeWindow(), 1086 views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE); 1087 } else { 1088 window->Minimize(); 1089 } 1090 } else { 1091 window->Show(); 1092 window->Activate(); 1093 } 1094 } 1095 1096 void ChromeLauncherController::OnLauncherCreated(ash::Launcher* launcher) { 1097 launchers_.insert(launcher); 1098 launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this); 1099 } 1100 1101 void ChromeLauncherController::OnLauncherDestroyed(ash::Launcher* launcher) { 1102 launchers_.erase(launcher); 1103 // RemoveObserver is not called here, since by the time this method is called 1104 // Launcher is already in its destructor. 1105 } 1106 1107 void ChromeLauncherController::ShelfItemAdded(int index) { 1108 // The app list launcher can get added to the shelf after we applied the 1109 // preferences. In that case the item might be at the wrong spot. As such we 1110 // call the function again. 1111 if (model_->items()[index].type == ash::TYPE_APP_LIST && 1112 ash::switches::UseAlternateShelfLayout()) 1113 UpdateAppLaunchersFromPref(); 1114 } 1115 1116 void ChromeLauncherController::ShelfItemRemoved(int index, ash::LauncherID id) { 1117 } 1118 1119 void ChromeLauncherController::ShelfItemMoved(int start_index, 1120 int target_index) { 1121 const ash::LauncherItem& item = model_->items()[target_index]; 1122 // We remember the moved item position if it is either pinnable or 1123 // it is the app list with the alternate shelf layout. 1124 if ((HasItemController(item.id) && IsPinned(item.id)) || 1125 (ash::switches::UseAlternateShelfLayout() && 1126 item.type == ash::TYPE_APP_LIST)) 1127 PersistPinnedState(); 1128 } 1129 1130 void ChromeLauncherController::ShelfItemChanged( 1131 int index, 1132 const ash::LauncherItem& old_item) { 1133 } 1134 1135 void ChromeLauncherController::ShelfStatusChanged() { 1136 } 1137 1138 void ChromeLauncherController::ActiveUserChanged( 1139 const std::string& user_email) { 1140 // Coming here the default profile is already switched. All profile specific 1141 // resources get released and the new profile gets attached instead. 1142 ReleaseProfile(); 1143 // When coming here, the active user has already be changed so that we can 1144 // set it as active. 1145 AttachProfile(ProfileManager::GetActiveUserProfile()); 1146 // Update the V1 applications. 1147 browser_status_monitor_->ActiveUserChanged(user_email); 1148 // Switch the running applications to the new user. 1149 shell_window_controller_->ActiveUserChanged(user_email); 1150 // Update the user specific shell properties from the new user profile. 1151 UpdateAppLaunchersFromPref(); 1152 SetShelfAlignmentFromPrefs(); 1153 SetShelfAutoHideBehaviorFromPrefs(); 1154 SetShelfBehaviorsFromPrefs(); 1155 } 1156 1157 void ChromeLauncherController::AdditionalUserAddedToSession(Profile* profile) { 1158 // Switch the running applications to the new user. 1159 shell_window_controller_->AdditionalUserAddedToSession(profile); 1160 } 1161 1162 void ChromeLauncherController::Observe( 1163 int type, 1164 const content::NotificationSource& source, 1165 const content::NotificationDetails& details) { 1166 switch (type) { 1167 case chrome::NOTIFICATION_EXTENSION_LOADED: { 1168 const Extension* extension = 1169 content::Details<const Extension>(details).ptr(); 1170 if (IsAppPinned(extension->id())) { 1171 // Clear and re-fetch to ensure icon is up-to-date. 1172 app_icon_loader_->ClearImage(extension->id()); 1173 app_icon_loader_->FetchImage(extension->id()); 1174 } 1175 1176 UpdateAppLaunchersFromPref(); 1177 break; 1178 } 1179 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 1180 const content::Details<UnloadedExtensionInfo>& unload_info(details); 1181 const Extension* extension = unload_info->extension; 1182 const std::string& id = extension->id(); 1183 // Since we might have windowed apps of this type which might have 1184 // outstanding locks which needs to be removed. 1185 if (GetLauncherIDForAppID(id) && 1186 unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) { 1187 CloseWindowedAppsFromRemovedExtension(id); 1188 } 1189 1190 if (IsAppPinned(id)) { 1191 if (unload_info->reason == UnloadedExtensionInfo::REASON_UNINSTALL) { 1192 DoUnpinAppWithID(id); 1193 app_icon_loader_->ClearImage(id); 1194 } else { 1195 app_icon_loader_->UpdateImage(id); 1196 } 1197 } 1198 break; 1199 } 1200 default: 1201 NOTREACHED() << "Unexpected notification type=" << type; 1202 } 1203 } 1204 1205 void ChromeLauncherController::OnShelfAlignmentChanged( 1206 aura::Window* root_window) { 1207 const char* pref_value = NULL; 1208 switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) { 1209 case ash::SHELF_ALIGNMENT_BOTTOM: 1210 pref_value = ash::kShelfAlignmentBottom; 1211 break; 1212 case ash::SHELF_ALIGNMENT_LEFT: 1213 pref_value = ash::kShelfAlignmentLeft; 1214 break; 1215 case ash::SHELF_ALIGNMENT_RIGHT: 1216 pref_value = ash::kShelfAlignmentRight; 1217 break; 1218 case ash::SHELF_ALIGNMENT_TOP: 1219 pref_value = ash::kShelfAlignmentTop; 1220 } 1221 1222 UpdatePerDisplayPref( 1223 profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value); 1224 1225 if (root_window == ash::Shell::GetPrimaryRootWindow()) { 1226 // See comment in |kShelfAlignment| about why we have two prefs here. 1227 profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value); 1228 profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value); 1229 } 1230 } 1231 1232 void ChromeLauncherController::OnDisplayConfigurationChanging() { 1233 } 1234 1235 void ChromeLauncherController::OnDisplayConfigurationChanged() { 1236 SetShelfBehaviorsFromPrefs(); 1237 } 1238 1239 void ChromeLauncherController::OnIsSyncingChanged() { 1240 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); 1241 MaybePropagatePrefToLocal(prefs, 1242 prefs::kShelfAlignmentLocal, 1243 prefs::kShelfAlignment); 1244 MaybePropagatePrefToLocal(prefs, 1245 prefs::kShelfAutoHideBehaviorLocal, 1246 prefs::kShelfAutoHideBehavior); 1247 } 1248 1249 void ChromeLauncherController::OnAppSyncUIStatusChanged() { 1250 if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING) 1251 model_->SetStatus(ash::ShelfModel::STATUS_LOADING); 1252 else 1253 model_->SetStatus(ash::ShelfModel::STATUS_NORMAL); 1254 } 1255 1256 void ChromeLauncherController::ExtensionEnableFlowFinished() { 1257 LaunchApp(extension_enable_flow_->extension_id(), 1258 ash::LAUNCH_FROM_UNKNOWN, 1259 ui::EF_NONE); 1260 extension_enable_flow_.reset(); 1261 } 1262 1263 void ChromeLauncherController::ExtensionEnableFlowAborted(bool user_initiated) { 1264 extension_enable_flow_.reset(); 1265 } 1266 1267 ChromeLauncherAppMenuItems ChromeLauncherController::GetApplicationList( 1268 const ash::LauncherItem& item, 1269 int event_flags) { 1270 // Make sure that there is a controller associated with the id and that the 1271 // extension itself is a valid application and not a panel. 1272 if (!HasItemController(item.id) || 1273 !GetLauncherIDForAppID(id_to_item_controller_map_[item.id]->app_id())) 1274 return ChromeLauncherAppMenuItems().Pass(); 1275 1276 return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags); 1277 } 1278 1279 std::vector<content::WebContents*> 1280 ChromeLauncherController::GetV1ApplicationsFromAppId(std::string app_id) { 1281 ash::LauncherID id = GetLauncherIDForAppID(app_id); 1282 1283 // If there is no such an item pinned to the launcher, no menu gets created. 1284 if (id) { 1285 LauncherItemController* controller = id_to_item_controller_map_[id]; 1286 DCHECK(controller); 1287 if (controller->type() == LauncherItemController::TYPE_SHORTCUT) 1288 return GetV1ApplicationsFromController(controller); 1289 } 1290 return std::vector<content::WebContents*>(); 1291 } 1292 1293 void ChromeLauncherController::ActivateShellApp(const std::string& app_id, 1294 int index) { 1295 ash::LauncherID id = GetLauncherIDForAppID(app_id); 1296 if (id) { 1297 LauncherItemController* controller = id_to_item_controller_map_[id]; 1298 if (controller->type() == LauncherItemController::TYPE_APP) { 1299 ShellWindowLauncherItemController* shell_window_controller = 1300 static_cast<ShellWindowLauncherItemController*>(controller); 1301 shell_window_controller->ActivateIndexedApp(index); 1302 } 1303 } 1304 } 1305 1306 bool ChromeLauncherController::IsWebContentHandledByApplication( 1307 content::WebContents* web_contents, 1308 const std::string& app_id) { 1309 if ((web_contents_to_app_id_.find(web_contents) != 1310 web_contents_to_app_id_.end()) && 1311 (web_contents_to_app_id_[web_contents] == app_id)) 1312 return true; 1313 return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents)); 1314 } 1315 1316 bool ChromeLauncherController::ContentCanBeHandledByGmailApp( 1317 content::WebContents* web_contents) { 1318 ash::LauncherID id = GetLauncherIDForAppID(kGmailAppId); 1319 if (id) { 1320 const GURL url = web_contents->GetURL(); 1321 // We need to extend the application matching for the gMail app beyond the 1322 // manifest file's specification. This is required because of the namespace 1323 // overlap with the offline app ("/mail/mu/"). 1324 if (!MatchPattern(url.path(), "/mail/mu/*") && 1325 MatchPattern(url.path(), "/mail/*") && 1326 GetExtensionForAppID(kGmailAppId) && 1327 GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url)) 1328 return true; 1329 } 1330 return false; 1331 } 1332 1333 gfx::Image ChromeLauncherController::GetAppListIcon( 1334 content::WebContents* web_contents) const { 1335 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 1336 if (IsIncognito(web_contents)) 1337 return rb.GetImageNamed(IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER); 1338 FaviconTabHelper* favicon_tab_helper = 1339 FaviconTabHelper::FromWebContents(web_contents); 1340 gfx::Image result = favicon_tab_helper->GetFavicon(); 1341 if (result.IsEmpty()) 1342 return rb.GetImageNamed(IDR_DEFAULT_FAVICON); 1343 return result; 1344 } 1345 1346 base::string16 ChromeLauncherController::GetAppListTitle( 1347 content::WebContents* web_contents) const { 1348 base::string16 title = web_contents->GetTitle(); 1349 if (!title.empty()) 1350 return title; 1351 WebContentsToAppIDMap::const_iterator iter = 1352 web_contents_to_app_id_.find(web_contents); 1353 if (iter != web_contents_to_app_id_.end()) { 1354 std::string app_id = iter->second; 1355 const extensions::Extension* extension = GetExtensionForAppID(app_id); 1356 if (extension) 1357 return UTF8ToUTF16(extension->name()); 1358 } 1359 return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); 1360 } 1361 1362 ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItem( 1363 const std::string& app_id, 1364 int index) { 1365 return CreateAppShortcutLauncherItemWithType(app_id, 1366 index, 1367 ash::TYPE_APP_SHORTCUT); 1368 } 1369 1370 void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) { 1371 app_tab_helper_.reset(helper); 1372 } 1373 1374 void ChromeLauncherController::SetAppIconLoaderForTest( 1375 extensions::AppIconLoader* loader) { 1376 app_icon_loader_.reset(loader); 1377 } 1378 1379 const std::string& ChromeLauncherController::GetAppIdFromLauncherIdForTest( 1380 ash::LauncherID id) { 1381 return id_to_item_controller_map_[id]->app_id(); 1382 } 1383 1384 void ChromeLauncherController::SetShelfItemDelegateManagerForTest( 1385 ash::ShelfItemDelegateManager* manager) { 1386 item_delegate_manager_ = manager; 1387 } 1388 1389 ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItemWithType( 1390 const std::string& app_id, 1391 int index, 1392 ash::LauncherItemType launcher_item_type) { 1393 AppShortcutLauncherItemController* controller = 1394 new AppShortcutLauncherItemController(app_id, this); 1395 ash::LauncherID launcher_id = InsertAppLauncherItem( 1396 controller, app_id, ash::STATUS_CLOSED, index, launcher_item_type); 1397 return launcher_id; 1398 } 1399 1400 LauncherItemController* ChromeLauncherController::GetLauncherItemController( 1401 const ash::LauncherID id) { 1402 if (!HasItemController(id)) 1403 return NULL; 1404 return id_to_item_controller_map_[id]; 1405 } 1406 1407 bool ChromeLauncherController::IsBrowserFromActiveUser(Browser* browser) { 1408 // If running multi user mode with separate desktops, we have to check if the 1409 // browser is from the active user. 1410 if (chrome::MultiUserWindowManager::GetMultiProfileMode() != 1411 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_SEPARATED) 1412 return true; 1413 return multi_user_util::IsProfileFromActiveUser(browser->profile()); 1414 } 1415 1416 void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) { 1417 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); 1418 CHECK(iter != id_to_item_controller_map_.end()); 1419 CHECK(iter->second); 1420 app_icon_loader_->ClearImage(iter->second->app_id()); 1421 id_to_item_controller_map_.erase(iter); 1422 int index = model_->ItemIndexByID(id); 1423 // A "browser proxy" is not known to the model and this removal does 1424 // therefore not need to be propagated to the model. 1425 if (index != -1) 1426 model_->RemoveItemAt(index); 1427 } 1428 1429 void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) { 1430 // If there is an item, do nothing and return. 1431 if (IsAppPinned(app_id)) 1432 return; 1433 1434 ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); 1435 if (launcher_id) { 1436 // App item exists, pin it 1437 Pin(launcher_id); 1438 } else { 1439 // Otherwise, create a shortcut item for it. 1440 CreateAppShortcutLauncherItem(app_id, model_->item_count()); 1441 if (CanPin()) 1442 PersistPinnedState(); 1443 } 1444 } 1445 1446 void ChromeLauncherController::DoUnpinAppWithID(const std::string& app_id) { 1447 ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); 1448 if (launcher_id && IsPinned(launcher_id)) 1449 Unpin(launcher_id); 1450 } 1451 1452 int ChromeLauncherController::PinRunningAppInternal( 1453 int index, 1454 ash::LauncherID launcher_id) { 1455 int running_index = model_->ItemIndexByID(launcher_id); 1456 ash::LauncherItem item = model_->items()[running_index]; 1457 DCHECK(item.type == ash::TYPE_WINDOWED_APP || 1458 item.type == ash::TYPE_PLATFORM_APP); 1459 item.type = ash::TYPE_APP_SHORTCUT; 1460 model_->Set(running_index, item); 1461 // The |ShelfModel|'s weight system might reposition the item to a 1462 // new index, so we get the index again. 1463 running_index = model_->ItemIndexByID(launcher_id); 1464 if (running_index < index) 1465 --index; 1466 if (running_index != index) 1467 model_->Move(running_index, index); 1468 return index; 1469 } 1470 1471 void ChromeLauncherController::UnpinRunningAppInternal(int index) { 1472 DCHECK_GE(index, 0); 1473 ash::LauncherItem item = model_->items()[index]; 1474 DCHECK_EQ(item.type, ash::TYPE_APP_SHORTCUT); 1475 item.type = ash::TYPE_WINDOWED_APP; 1476 // A platform app and a windowed app are sharing TYPE_APP_SHORTCUT. As such 1477 // we have to check here what this was before it got a shortcut. 1478 if (HasItemController(item.id) && 1479 id_to_item_controller_map_[item.id]->type() == 1480 LauncherItemController::TYPE_APP) 1481 item.type = ash::TYPE_PLATFORM_APP; 1482 model_->Set(index, item); 1483 } 1484 1485 void ChromeLauncherController::UpdateAppLaunchersFromPref() { 1486 // There are various functions which will trigger a |PersistPinnedState| call 1487 // like a direct call to |DoPinAppWithID|, or an indirect call to the menu 1488 // model which will use weights to re-arrange the icons to new positions. 1489 // Since this function is meant to synchronize the "is state" with the 1490 // "sync state", it makes no sense to store any changes by this function back 1491 // into the pref state. Therefore we tell |persistPinnedState| to ignore any 1492 // invocations while we are running. 1493 base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true); 1494 std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser(); 1495 1496 int index = 0; 1497 int max_index = model_->item_count(); 1498 1499 // When one of the two special items cannot be moved (and we do not know where 1500 // yet), we remember the current location in one of these variables. 1501 int chrome_index = -1; 1502 int app_list_index = -1; 1503 1504 // Walk the model and |pinned_apps| from the pref lockstep, adding and 1505 // removing items as necessary. NB: This code uses plain old indexing instead 1506 // of iterators because of model mutations as part of the loop. 1507 std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin()); 1508 for (; index < max_index && pref_app_id != pinned_apps.end(); ++index) { 1509 // Check if we have an item which we need to handle. 1510 if (*pref_app_id == extension_misc::kChromeAppId || 1511 *pref_app_id == kAppLauncherIdPlaceholder || 1512 IsAppPinned(*pref_app_id)) { 1513 for (; index < max_index; ++index) { 1514 const ash::LauncherItem& item(model_->items()[index]); 1515 bool is_app_list = item.type == ash::TYPE_APP_LIST; 1516 bool is_chrome = item.type == ash::TYPE_BROWSER_SHORTCUT; 1517 if (item.type != ash::TYPE_APP_SHORTCUT && !is_app_list && !is_chrome) 1518 continue; 1519 IDToItemControllerMap::const_iterator entry = 1520 id_to_item_controller_map_.find(item.id); 1521 if ((kAppLauncherIdPlaceholder == *pref_app_id && is_app_list) || 1522 (extension_misc::kChromeAppId == *pref_app_id && is_chrome) || 1523 (entry != id_to_item_controller_map_.end() && 1524 entry->second->app_id() == *pref_app_id)) { 1525 // Check if an item needs to be moved here. 1526 MoveChromeOrApplistToFinalPosition( 1527 is_chrome, is_app_list, index, &chrome_index, &app_list_index); 1528 ++pref_app_id; 1529 break; 1530 } else { 1531 if (is_chrome || is_app_list) { 1532 // We cannot delete any of these shortcuts. As such we remember 1533 // their positions and move them later where they belong. 1534 if (is_chrome) 1535 chrome_index = index; 1536 else 1537 app_list_index = index; 1538 // And skip the item - or exit the loop if end is reached (note that 1539 // in that case we will reduce the index again by one and this only 1540 // compensates for it). 1541 if (index >= max_index - 1) 1542 break; 1543 ++index; 1544 } else { 1545 // Check if this is a platform or a windowed app. 1546 if (item.type == ash::TYPE_APP_SHORTCUT && 1547 (id_to_item_controller_map_[item.id]->locked() || 1548 id_to_item_controller_map_[item.id]->type() == 1549 LauncherItemController::TYPE_APP)) { 1550 // Note: This will not change the amount of items (|max_index|). 1551 // Even changes to the actual |index| due to item weighting 1552 // changes should be fine. 1553 UnpinRunningAppInternal(index); 1554 } else { 1555 LauncherItemClosed(item.id); 1556 --max_index; 1557 } 1558 } 1559 --index; 1560 } 1561 } 1562 // If the item wasn't found, that means id_to_item_controller_map_ 1563 // is out of sync. 1564 DCHECK(index <= max_index); 1565 } else { 1566 // Check if the item was already running but not yet pinned. 1567 ash::LauncherID launcher_id = GetLauncherIDForAppID(*pref_app_id); 1568 if (launcher_id) { 1569 // This app is running but not yet pinned. So pin and move it. 1570 index = PinRunningAppInternal(index, launcher_id); 1571 } else { 1572 // This app wasn't pinned before, insert a new entry. 1573 launcher_id = CreateAppShortcutLauncherItem(*pref_app_id, index); 1574 ++max_index; 1575 index = model_->ItemIndexByID(launcher_id); 1576 } 1577 ++pref_app_id; 1578 } 1579 } 1580 1581 // Remove any trailing existing items. 1582 while (index < model_->item_count()) { 1583 const ash::LauncherItem& item(model_->items()[index]); 1584 if (item.type == ash::TYPE_APP_SHORTCUT) { 1585 if (id_to_item_controller_map_[item.id]->locked() || 1586 id_to_item_controller_map_[item.id]->type() == 1587 LauncherItemController::TYPE_APP) 1588 UnpinRunningAppInternal(index); 1589 else 1590 LauncherItemClosed(item.id); 1591 } else { 1592 if (item.type == ash::TYPE_BROWSER_SHORTCUT) 1593 chrome_index = index; 1594 else if (item.type == ash::TYPE_APP_LIST) 1595 app_list_index = index; 1596 ++index; 1597 } 1598 } 1599 1600 // Append unprocessed items from the pref to the end of the model. 1601 for (; pref_app_id != pinned_apps.end(); ++pref_app_id) { 1602 // All items but the chrome and / or app list shortcut needs to be added. 1603 bool is_chrome = *pref_app_id == extension_misc::kChromeAppId; 1604 bool is_app_list = *pref_app_id == kAppLauncherIdPlaceholder; 1605 // Coming here we know the next item which can be finalized, either the 1606 // chrome item or the app launcher. The final position is the end of the 1607 // list. The menu model will make sure that the item is grouped according 1608 // to its weight (which we do not know here). 1609 if (!is_chrome && !is_app_list) { 1610 DoPinAppWithID(*pref_app_id); 1611 int target_index = FindInsertionPoint(false); 1612 ash::LauncherID id = GetLauncherIDForAppID(*pref_app_id); 1613 int source_index = model_->ItemIndexByID(id); 1614 if (source_index != target_index) 1615 model_->Move(source_index, target_index); 1616 1617 // Needed for the old layout - the weight might force it to be lower in 1618 // rank. 1619 if (app_list_index != -1 && target_index <= app_list_index) 1620 ++app_list_index; 1621 } else { 1622 int target_index = FindInsertionPoint(is_app_list); 1623 MoveChromeOrApplistToFinalPosition( 1624 is_chrome, is_app_list, target_index, &chrome_index, &app_list_index); 1625 } 1626 } 1627 } 1628 1629 void ChromeLauncherController::SetShelfAutoHideBehaviorPrefs( 1630 ash::ShelfAutoHideBehavior behavior, 1631 aura::Window* root_window) { 1632 const char* value = NULL; 1633 switch (behavior) { 1634 case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: 1635 value = ash::kShelfAutoHideBehaviorAlways; 1636 break; 1637 case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER: 1638 value = ash::kShelfAutoHideBehaviorNever; 1639 break; 1640 case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN: 1641 // This one should not be a valid preference option for now. We only want 1642 // to completely hide it when we run app mode. 1643 NOTREACHED(); 1644 return; 1645 } 1646 1647 UpdatePerDisplayPref( 1648 profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value); 1649 1650 if (root_window == ash::Shell::GetPrimaryRootWindow()) { 1651 // See comment in |kShelfAlignment| about why we have two prefs here. 1652 profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value); 1653 profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value); 1654 } 1655 } 1656 1657 void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() { 1658 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 1659 1660 for (aura::Window::Windows::const_iterator iter = root_windows.begin(); 1661 iter != root_windows.end(); ++iter) { 1662 ash::Shell::GetInstance()->SetShelfAutoHideBehavior( 1663 GetShelfAutoHideBehavior(*iter), *iter); 1664 } 1665 } 1666 1667 void ChromeLauncherController::SetShelfAlignmentFromPrefs() { 1668 if (!ash::ShelfWidget::ShelfAlignmentAllowed()) 1669 return; 1670 1671 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 1672 1673 for (aura::Window::Windows::const_iterator iter = root_windows.begin(); 1674 iter != root_windows.end(); ++iter) { 1675 // See comment in |kShelfAlignment| as to why we consider two prefs. 1676 const std::string alignment_value( 1677 GetPrefForRootWindow(profile_->GetPrefs(), 1678 *iter, 1679 prefs::kShelfAlignmentLocal, 1680 prefs::kShelfAlignment)); 1681 ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; 1682 if (alignment_value == ash::kShelfAlignmentLeft) 1683 alignment = ash::SHELF_ALIGNMENT_LEFT; 1684 else if (alignment_value == ash::kShelfAlignmentRight) 1685 alignment = ash::SHELF_ALIGNMENT_RIGHT; 1686 else if (alignment_value == ash::kShelfAlignmentTop) 1687 alignment = ash::SHELF_ALIGNMENT_TOP; 1688 ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter); 1689 } 1690 } 1691 1692 void ChromeLauncherController::SetShelfBehaviorsFromPrefs() { 1693 SetShelfAutoHideBehaviorFromPrefs(); 1694 SetShelfAlignmentFromPrefs(); 1695 } 1696 1697 WebContents* ChromeLauncherController::GetLastActiveWebContents( 1698 const std::string& app_id) { 1699 AppIDToWebContentsListMap::iterator i = 1700 app_id_to_web_contents_list_.find(app_id); 1701 if (i == app_id_to_web_contents_list_.end()) 1702 return NULL; 1703 1704 // There are many crash records (crbug.com/341250) which indicate that the 1705 // app_id_to_web_contents_list_ contains deleted content entries - so there 1706 // must be a way that the content does not get properly updated. To fix 1707 // M33 and M34 we filter out the invalid items here, but this should be 1708 // addressed by a later patch correctly. Looking at the code however, the 1709 // real culprit is possibly BrowserStatusMonitor::UpdateAppItemState which 1710 // does not call "UpdateAppState(.., APP_STATE_REMOVED)" because due to a 1711 // Browser::SwapTabContent operation it isn't able to get the browser. I 1712 // think that the real patch is to call anyway when APP_STATE_REMOVED is 1713 // requested, but for a backport that seems risky. 1714 WebContentsList* list = &i->second; 1715 while (!list->empty()) { 1716 WebContents* contents = *list->begin(); 1717 if (chrome::FindBrowserWithWebContents(contents)) 1718 return contents; 1719 list->erase(list->begin()); 1720 // This might not be necessary, but since we do not know why the lists 1721 // diverged we also erase it since it cannot be correct either. 1722 web_contents_to_app_id_.erase(contents); 1723 } 1724 app_id_to_web_contents_list_.erase(app_id); 1725 return NULL; 1726 } 1727 1728 ash::LauncherID ChromeLauncherController::InsertAppLauncherItem( 1729 LauncherItemController* controller, 1730 const std::string& app_id, 1731 ash::LauncherItemStatus status, 1732 int index, 1733 ash::LauncherItemType launcher_item_type) { 1734 ash::LauncherID id = model_->next_id(); 1735 CHECK(!HasItemController(id)); 1736 CHECK(controller); 1737 id_to_item_controller_map_[id] = controller; 1738 controller->set_launcher_id(id); 1739 1740 ash::LauncherItem item; 1741 item.type = launcher_item_type; 1742 item.image = extensions::IconsInfo::GetDefaultAppIcon(); 1743 1744 WebContents* active_tab = GetLastActiveWebContents(app_id); 1745 if (active_tab) { 1746 Browser* browser = chrome::FindBrowserWithWebContents(active_tab); 1747 DCHECK(browser); 1748 if (browser->window()->IsActive()) 1749 status = ash::STATUS_ACTIVE; 1750 else 1751 status = ash::STATUS_RUNNING; 1752 } 1753 item.status = status; 1754 1755 model_->AddAt(index, item); 1756 1757 app_icon_loader_->FetchImage(app_id); 1758 1759 SetShelfItemDelegate(id, controller); 1760 1761 return id; 1762 } 1763 1764 bool ChromeLauncherController::HasItemController(ash::LauncherID id) const { 1765 return id_to_item_controller_map_.find(id) != 1766 id_to_item_controller_map_.end(); 1767 } 1768 1769 std::vector<content::WebContents*> 1770 ChromeLauncherController::GetV1ApplicationsFromController( 1771 LauncherItemController* controller) { 1772 DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT); 1773 AppShortcutLauncherItemController* app_controller = 1774 static_cast<AppShortcutLauncherItemController*>(controller); 1775 return app_controller->GetRunningApplications(); 1776 } 1777 1778 BrowserShortcutLauncherItemController* 1779 ChromeLauncherController::GetBrowserShortcutLauncherItemController() { 1780 for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); 1781 i != id_to_item_controller_map_.end(); ++i) { 1782 int index = model_->ItemIndexByID(i->first); 1783 const ash::LauncherItem& item = model_->items()[index]; 1784 if (item.type == ash::TYPE_BROWSER_SHORTCUT) 1785 return static_cast<BrowserShortcutLauncherItemController*>(i->second); 1786 } 1787 // Create a LauncherItemController for the Browser shortcut if it does not 1788 // exist yet. 1789 ash::LauncherID id = CreateBrowserShortcutLauncherItem(); 1790 DCHECK(id_to_item_controller_map_[id]); 1791 return static_cast<BrowserShortcutLauncherItemController*>( 1792 id_to_item_controller_map_[id]); 1793 } 1794 1795 ash::LauncherID ChromeLauncherController::CreateBrowserShortcutLauncherItem() { 1796 ash::LauncherItem browser_shortcut; 1797 browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; 1798 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 1799 browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); 1800 ash::LauncherID id = model_->next_id(); 1801 size_t index = GetChromeIconIndexForCreation(); 1802 model_->AddAt(index, browser_shortcut); 1803 id_to_item_controller_map_[id] = 1804 new BrowserShortcutLauncherItemController(this); 1805 id_to_item_controller_map_[id]->set_launcher_id(id); 1806 // ShelfItemDelegateManager owns BrowserShortcutLauncherItemController. 1807 SetShelfItemDelegate(id, id_to_item_controller_map_[id]); 1808 return id; 1809 } 1810 1811 void ChromeLauncherController::PersistChromeItemIndex(int index) { 1812 profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index); 1813 } 1814 1815 int ChromeLauncherController::GetChromeIconIndexFromPref() const { 1816 size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex); 1817 const base::ListValue* pinned_apps_pref = 1818 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); 1819 return std::max(static_cast<size_t>(0), 1820 std::min(pinned_apps_pref->GetSize(), index)); 1821 } 1822 1823 void ChromeLauncherController::MoveChromeOrApplistToFinalPosition( 1824 bool is_chrome, 1825 bool is_app_list, 1826 int target_index, 1827 int* chrome_index, 1828 int* app_list_index) { 1829 if (is_chrome && *chrome_index != -1) { 1830 model_->Move(*chrome_index, target_index); 1831 if (*app_list_index != -1 && 1832 *chrome_index < *app_list_index && 1833 target_index > *app_list_index) 1834 --(*app_list_index); 1835 *chrome_index = -1; 1836 } else if (is_app_list && *app_list_index != -1) { 1837 model_->Move(*app_list_index, target_index); 1838 if (*chrome_index != -1 && 1839 *app_list_index < *chrome_index && 1840 target_index > *chrome_index) 1841 --(*chrome_index); 1842 *app_list_index = -1; 1843 } 1844 } 1845 1846 int ChromeLauncherController::FindInsertionPoint(bool is_app_list) { 1847 bool alternate = ash::switches::UseAlternateShelfLayout(); 1848 // Keeping this change small to backport to M33&32 (see crbug.com/329597). 1849 // TODO(skuhne): With the removal of the legacy shelf layout we should remove 1850 // the ability to move the app list item since this was never used. We should 1851 // instead ask the ShelfModel::ValidateInsertionIndex or similir for an index. 1852 if (is_app_list && alternate) 1853 return 0; 1854 1855 for (int i = model_->item_count() - 1; i > 0; --i) { 1856 ash::LauncherItemType type = model_->items()[i].type; 1857 if (type == ash::TYPE_APP_SHORTCUT || 1858 ((is_app_list || alternate) && type == ash::TYPE_APP_LIST) || 1859 type == ash::TYPE_BROWSER_SHORTCUT || 1860 type == ash::TYPE_WINDOWED_APP) 1861 return i; 1862 } 1863 return 0; 1864 } 1865 1866 int ChromeLauncherController::GetChromeIconIndexForCreation() { 1867 // We get the list of pinned apps as they currently would get pinned. 1868 // Within this list the chrome icon will be the correct location. 1869 std::vector<std::string> pinned_apps = GetListOfPinnedAppsAndBrowser(); 1870 1871 std::vector<std::string>::iterator it = 1872 std::find(pinned_apps.begin(), 1873 pinned_apps.end(), 1874 std::string(extension_misc::kChromeAppId)); 1875 DCHECK(it != pinned_apps.end()); 1876 int index = it - pinned_apps.begin(); 1877 1878 // We should do here a comparison between the is state and the "want to be" 1879 // state since some apps might be able to pin but are not yet. Instead - for 1880 // the time being we clamp against the amount of known items and wait for the 1881 // next |UpdateAppLaunchersFromPref()| call to correct it - it will come since 1882 // the pinning will be done then. 1883 return std::min(model_->item_count(), index); 1884 } 1885 1886 std::vector<std::string> 1887 ChromeLauncherController::GetListOfPinnedAppsAndBrowser() { 1888 // Adding the app list item to the list of items requires that the ID is not 1889 // a valid and known ID for the extension system. The ID was constructed that 1890 // way - but just to make sure... 1891 DCHECK(!app_tab_helper_->IsValidIDForCurrentUser(kAppLauncherIdPlaceholder)); 1892 1893 std::vector<std::string> pinned_apps; 1894 1895 // Get the new incarnation of the list. 1896 const base::ListValue* pinned_apps_pref = 1897 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); 1898 1899 // Keep track of the addition of the chrome and the app list icon. 1900 bool chrome_icon_added = false; 1901 bool app_list_icon_added = false; 1902 size_t chrome_icon_index = GetChromeIconIndexFromPref(); 1903 1904 // See if the chrome string is already in the pinned list and remove it if 1905 // needed. 1906 base::Value* chrome_app = ash::CreateAppDict(extension_misc::kChromeAppId); 1907 if (chrome_app) { 1908 chrome_icon_added = pinned_apps_pref->Find(*chrome_app) != 1909 pinned_apps_pref->end(); 1910 delete chrome_app; 1911 } 1912 1913 for (size_t index = 0; index < pinned_apps_pref->GetSize(); ++index) { 1914 // We need to position the chrome icon relative to it's place in the pinned 1915 // preference list - even if an item of that list isn't shown yet. 1916 if (index == chrome_icon_index && !chrome_icon_added) { 1917 pinned_apps.push_back(extension_misc::kChromeAppId); 1918 chrome_icon_added = true; 1919 } 1920 const DictionaryValue* app = NULL; 1921 std::string app_id; 1922 if (pinned_apps_pref->GetDictionary(index, &app) && 1923 app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) && 1924 (std::find(pinned_apps.begin(), pinned_apps.end(), app_id) == 1925 pinned_apps.end())) { 1926 if (app_id == extension_misc::kChromeAppId) { 1927 chrome_icon_added = true; 1928 pinned_apps.push_back(extension_misc::kChromeAppId); 1929 } else if (app_id == kAppLauncherIdPlaceholder) { 1930 app_list_icon_added = true; 1931 pinned_apps.push_back(kAppLauncherIdPlaceholder); 1932 } else if (app_tab_helper_->IsValidIDForCurrentUser(app_id)) { 1933 // Note: In multi profile scenarios we only want to show pinnable apps 1934 // here which is correct. Running applications from the other users will 1935 // continue to run. So no need for multi profile modifications. 1936 pinned_apps.push_back(app_id); 1937 } 1938 } 1939 } 1940 1941 // If not added yet, the chrome item will be the last item in the list. 1942 if (!chrome_icon_added) 1943 pinned_apps.push_back(extension_misc::kChromeAppId); 1944 1945 // If not added yet, add the app list item either at the end or at the 1946 // beginning - depending on the shelf layout. 1947 if (!app_list_icon_added) { 1948 if (ash::switches::UseAlternateShelfLayout()) 1949 pinned_apps.insert(pinned_apps.begin(), kAppLauncherIdPlaceholder); 1950 else 1951 pinned_apps.push_back(kAppLauncherIdPlaceholder); 1952 } 1953 return pinned_apps; 1954 } 1955 1956 bool ChromeLauncherController::IsIncognito( 1957 const content::WebContents* web_contents) const { 1958 const Profile* profile = 1959 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 1960 return profile->IsOffTheRecord() && !profile->IsGuestSession(); 1961 } 1962 1963 void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension( 1964 const std::string& app_id) { 1965 // This function cannot rely on the controller's enumeration functionality 1966 // since the extension has already be unloaded. 1967 const BrowserList* ash_browser_list = 1968 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); 1969 std::vector<Browser*> browser_to_close; 1970 for (BrowserList::const_reverse_iterator 1971 it = ash_browser_list->begin_last_active(); 1972 it != ash_browser_list->end_last_active(); ++it) { 1973 Browser* browser = *it; 1974 if (!browser->is_type_tabbed() && 1975 browser->is_type_popup() && 1976 browser->is_app() && 1977 app_id == web_app::GetExtensionIdFromApplicationName( 1978 browser->app_name())) { 1979 browser_to_close.push_back(browser); 1980 } 1981 } 1982 while (!browser_to_close.empty()) { 1983 TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model(); 1984 tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE); 1985 browser_to_close.pop_back(); 1986 } 1987 } 1988 1989 void ChromeLauncherController::SetShelfItemDelegate( 1990 ash::LauncherID id, 1991 ash::ShelfItemDelegate* item_delegate) { 1992 DCHECK_GT(id, 0); 1993 DCHECK(item_delegate); 1994 DCHECK(item_delegate_manager_); 1995 item_delegate_manager_->SetShelfItemDelegate( 1996 id, scoped_ptr<ash::ShelfItemDelegate>(item_delegate).Pass()); 1997 } 1998 1999 void ChromeLauncherController::AttachProfile(Profile* profile) { 2000 profile_ = profile; 2001 // Either add the profile to the list of known profiles and make it the active 2002 // one for some functions of AppTabHelper or create a new one. 2003 if (!app_tab_helper_.get()) 2004 app_tab_helper_.reset(new LauncherAppTabHelper(profile_)); 2005 else 2006 app_tab_helper_->SetCurrentUser(profile_); 2007 // TODO(skuhne): The AppIconLoaderImpl has the same problem. Each loaded 2008 // image is associated with a profile (it's loader requires the profile). 2009 // Since icon size changes are possible, the icon could be requested to be 2010 // reloaded. However - having it not multi profile aware would cause problems 2011 // if the icon cache gets deleted upon user switch. 2012 app_icon_loader_.reset(new extensions::AppIconLoaderImpl( 2013 profile_, extension_misc::EXTENSION_ICON_SMALL, this)); 2014 2015 pref_change_registrar_.Init(profile_->GetPrefs()); 2016 pref_change_registrar_.Add( 2017 prefs::kPinnedLauncherApps, 2018 base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref, 2019 base::Unretained(this))); 2020 pref_change_registrar_.Add( 2021 prefs::kShelfAlignmentLocal, 2022 base::Bind(&ChromeLauncherController::SetShelfAlignmentFromPrefs, 2023 base::Unretained(this))); 2024 pref_change_registrar_.Add( 2025 prefs::kShelfAutoHideBehaviorLocal, 2026 base::Bind(&ChromeLauncherController:: 2027 SetShelfAutoHideBehaviorFromPrefs, 2028 base::Unretained(this))); 2029 pref_change_registrar_.Add( 2030 prefs::kShelfPreferences, 2031 base::Bind(&ChromeLauncherController::SetShelfBehaviorsFromPrefs, 2032 base::Unretained(this))); 2033 } 2034 2035 void ChromeLauncherController::ReleaseProfile() { 2036 if (app_sync_ui_state_) 2037 app_sync_ui_state_->RemoveObserver(this); 2038 2039 PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this); 2040 2041 pref_change_registrar_.RemoveAll(); 2042 } 2043