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