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