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