| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| |
| #include "ash/public/cpp/app_types.h" |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "ash/public/cpp/multi_user_window_manager.h" |
| #include "ash/public/cpp/shelf_item.h" |
| #include "ash/public/cpp/shelf_model.h" |
| #include "ash/public/cpp/shelf_prefs.h" |
| #include "ash/public/cpp/window_animation_types.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/chromeos/arc/arc_util.h" |
| #include "chrome/browser/chromeos/crostini/crostini_util.h" |
| #include "chrome/browser/extensions/chrome_app_icon_loader.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/prefs/pref_service_syncable_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/app_list/app_list_client_impl.h" |
| #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_icon_loader.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" |
| #include "chrome/browser/ui/app_list/crostini/crostini_app_icon_loader.h" |
| #include "chrome/browser/ui/app_list/internal_app/internal_app_icon_loader.h" |
| #include "chrome/browser/ui/app_list/md_icon_normalizer.h" |
| #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h" |
| #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h" |
| #include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h" |
| #include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h" |
| #include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h" |
| #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" |
| #include "chrome/browser/ui/ash/launcher/browser_status_monitor.h" |
| #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h" |
| #include "chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.h" |
| #include "chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.h" |
| #include "chrome/browser/ui/ash/launcher/launcher_arc_app_updater.h" |
| #include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h" |
| #include "chrome/browser/ui/ash/launcher/launcher_crostini_app_updater.h" |
| #include "chrome/browser/ui/ash/launcher/launcher_extension_app_updater.h" |
| #include "chrome/browser/ui/ash/launcher/multi_profile_app_window_launcher_controller.h" |
| #include "chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.h" |
| #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h" |
| #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" |
| #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h" |
| #include "chrome/browser/ui/ash/session_controller_client_impl.h" |
| #include "chrome/browser/ui/ash/tablet_mode_client.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/web_applications/components/web_app_helpers.h" |
| #include "chrome/browser/web_applications/system_web_app_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/chrome_unscaled_resources.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "components/account_id/account_id.h" |
| #include "components/arc/arc_prefs.h" |
| #include "components/arc/arc_util.h" |
| #include "components/favicon/content/content_favicon_driver.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "extensions/common/extension.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/resources/grit/ui_resources.h" |
| |
| using extension_misc::kChromeAppId; |
| using extension_misc::kGmailAppId; |
| |
| namespace { |
| |
| // Calls ItemSelected with |source|, default arguments, and no callback. |
| void SelectItemWithSource(ash::ShelfItemDelegate* delegate, |
| ash::ShelfLaunchSource source, |
| int64_t display_id) { |
| delegate->ItemSelected(nullptr, display_id, source, base::DoNothing()); |
| } |
| |
| // Returns true if the given |item| has a pinned shelf item type. |
| bool ItemTypeIsPinned(const ash::ShelfItem& item) { |
| return item.type == ash::TYPE_PINNED_APP || |
| item.type == ash::TYPE_BROWSER_SHORTCUT; |
| } |
| |
| // Returns the app_id of the crostini app that can handle the given web content. |
| // Returns the empty string if crostini does not recognise the contents. This is |
| // used to prevent crbug.com/855662. |
| // TODO(crbug.com/846546): Remove this function when the crostini terminal is |
| // less hacky |
| std::string GetCrostiniAppIdFromContents(content::WebContents* web_contents) { |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
| base::Optional<std::string> app_id_opt = |
| crostini::CrostiniAppIdFromAppName(browser->app_name()); |
| return app_id_opt.value_or(""); |
| } |
| |
| } // namespace |
| |
| // A class to get events from ChromeOS when a user gets changed or added. |
| class ChromeLauncherControllerUserSwitchObserver |
| : public user_manager::UserManager::UserSessionStateObserver { |
| public: |
| ChromeLauncherControllerUserSwitchObserver( |
| ChromeLauncherController* controller) |
| : controller_(controller) { |
| DCHECK(user_manager::UserManager::IsInitialized()); |
| user_manager::UserManager::Get()->AddSessionStateObserver(this); |
| } |
| ~ChromeLauncherControllerUserSwitchObserver() override { |
| user_manager::UserManager::Get()->RemoveSessionStateObserver(this); |
| } |
| |
| // user_manager::UserManager::UserSessionStateObserver overrides: |
| void UserAddedToSession(const user_manager::User* added_user) override; |
| |
| // ChromeLauncherControllerUserSwitchObserver: |
| void OnUserProfileReadyToSwitch(Profile* profile); |
| |
| private: |
| // Add a user to the session. |
| void AddUser(Profile* profile); |
| |
| // The owning ChromeLauncherController. |
| ChromeLauncherController* controller_; |
| |
| // Users which were just added to the system, but which profiles were not yet |
| // (fully) loaded. |
| std::set<std::string> added_user_ids_waiting_for_profiles_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerUserSwitchObserver); |
| }; |
| |
| void ChromeLauncherControllerUserSwitchObserver::UserAddedToSession( |
| const user_manager::User* active_user) { |
| Profile* profile = |
| multi_user_util::GetProfileFromAccountId(active_user->GetAccountId()); |
| // If we do not have a profile yet, we postpone forwarding the notification |
| // until it is loaded. |
| if (!profile) { |
| added_user_ids_waiting_for_profiles_.insert( |
| active_user->GetAccountId().GetUserEmail()); |
| } else { |
| AddUser(profile); |
| } |
| } |
| |
| void ChromeLauncherControllerUserSwitchObserver::OnUserProfileReadyToSwitch( |
| Profile* profile) { |
| if (!added_user_ids_waiting_for_profiles_.empty()) { |
| // Check if the profile is from a user which was on the waiting list. |
| // TODO(alemate): added_user_ids_waiting_for_profiles_ should be |
| // a set<AccountId> |
| std::string user_id = |
| multi_user_util::GetAccountIdFromProfile(profile).GetUserEmail(); |
| std::set<std::string>::iterator it = |
| std::find(added_user_ids_waiting_for_profiles_.begin(), |
| added_user_ids_waiting_for_profiles_.end(), user_id); |
| if (it != added_user_ids_waiting_for_profiles_.end()) { |
| added_user_ids_waiting_for_profiles_.erase(it); |
| AddUser(profile->GetOriginalProfile()); |
| } |
| } |
| } |
| |
| void ChromeLauncherControllerUserSwitchObserver::AddUser(Profile* profile) { |
| MultiUserWindowManagerHelper::GetInstance()->AddUser(profile); |
| controller_->AdditionalUserAddedToSession(profile->GetOriginalProfile()); |
| } |
| |
| // static |
| ChromeLauncherController* ChromeLauncherController::instance_ = nullptr; |
| |
| ChromeLauncherController::ChromeLauncherController(Profile* profile, |
| ash::ShelfModel* model) |
| : model_(model), weak_ptr_factory_(this) { |
| DCHECK(!instance_); |
| instance_ = this; |
| |
| DCHECK(model_); |
| |
| if (!web_app::SystemWebAppManager::IsEnabled()) { |
| settings_window_observer_ = std::make_unique<SettingsWindowObserver>(); |
| discover_window_observer_ = std::make_unique<DiscoverWindowObserver>(); |
| } |
| |
| if (!profile) { |
| // If no profile was passed, we take the currently active profile and use it |
| // as the owner of the current desktop. |
| // Use the original profile as on chromeos we may get a temporary off the |
| // record profile, unless in guest session (where off the record profile is |
| // the right one). |
| profile = ProfileManager::GetActiveUserProfile(); |
| if (!profile->IsGuestSession() && !profile->IsSystemProfile()) |
| profile = profile->GetOriginalProfile(); |
| } |
| |
| // All profile relevant settings get bound to the current profile. |
| AttachProfile(profile); |
| DCHECK_EQ(profile, profile_); |
| model_->AddObserver(this); |
| |
| shelf_spinner_controller_.reset(new ShelfSpinnerController(this)); |
| |
| // Create either the real window manager or a stub. |
| MultiUserWindowManagerHelper::CreateInstance(); |
| |
| // On Chrome OS using multi profile we want to switch the content of the shelf |
| // with a user change. Note that for unit tests the instance can be NULL. |
| if (SessionControllerClientImpl::IsMultiProfileAvailable()) { |
| user_switch_observer_.reset( |
| new ChromeLauncherControllerUserSwitchObserver(this)); |
| } |
| |
| std::unique_ptr<AppWindowLauncherController> extension_app_window_controller; |
| // Create our v1/v2 application / browser monitors which will inform the |
| // launcher of status changes. |
| if (SessionControllerClientImpl::IsMultiProfileAvailable()) { |
| // If running in separated destkop mode, we create the multi profile version |
| // of status monitor. |
| browser_status_monitor_.reset(new MultiProfileBrowserStatusMonitor(this)); |
| browser_status_monitor_->Initialize(); |
| extension_app_window_controller.reset( |
| new MultiProfileAppWindowLauncherController(this)); |
| } else { |
| // Create our v1/v2 application / browser monitors which will inform the |
| // launcher of status changes. |
| browser_status_monitor_.reset(new BrowserStatusMonitor(this)); |
| browser_status_monitor_->Initialize(); |
| extension_app_window_controller.reset( |
| new ExtensionAppWindowLauncherController(this)); |
| } |
| app_window_controllers_.push_back(std::move(extension_app_window_controller)); |
| |
| auto arc_app_window_controller = |
| std::make_unique<ArcAppWindowLauncherController>(this); |
| arc_app_window_controller_ = arc_app_window_controller.get(); |
| app_window_controllers_.push_back(std::move(arc_app_window_controller)); |
| |
| if (crostini::IsCrostiniUIAllowedForProfile(profile)) { |
| std::unique_ptr<CrostiniAppWindowShelfController> crostini_controller = |
| std::make_unique<CrostiniAppWindowShelfController>(this); |
| crostini_app_window_shelf_controller_ = crostini_controller.get(); |
| app_window_controllers_.emplace_back(std::move(crostini_controller)); |
| } |
| app_window_controllers_.push_back( |
| std::make_unique<InternalAppWindowShelfController>(this)); |
| } |
| |
| ChromeLauncherController::~ChromeLauncherController() { |
| // Reset the BrowserStatusMonitor as it has a weak pointer to this. |
| browser_status_monitor_.reset(); |
| |
| // Reset the app window controllers here since it has a weak pointer to this. |
| arc_app_window_controller_ = nullptr; |
| app_window_controllers_.clear(); |
| |
| // Destroy the ShelfSpinnerController before clearing delegates. |
| shelf_spinner_controller_.reset(); |
| |
| // Destroy local shelf item delegates; some subclasses have complex cleanup. |
| model_->DestroyItemDelegates(); |
| |
| model_->RemoveObserver(this); |
| |
| // Release all profile dependent resources. |
| ReleaseProfile(); |
| |
| // Get rid of the multi user window manager instance. |
| MultiUserWindowManagerHelper::DeleteInstance(); |
| |
| if (instance_ == this) |
| instance_ = nullptr; |
| } |
| |
| void ChromeLauncherController::Init() { |
| CreateBrowserShortcutLauncherItem(); |
| UpdateAppLaunchersFromSync(); |
| } |
| |
| ash::ShelfID ChromeLauncherController::CreateAppLauncherItem( |
| std::unique_ptr<ash::ShelfItemDelegate> item_delegate, |
| ash::ShelfItemStatus status, |
| const base::string16& title) { |
| return InsertAppLauncherItem(std::move(item_delegate), status, |
| model_->item_count(), ash::TYPE_APP, title); |
| } |
| |
| const ash::ShelfItem* ChromeLauncherController::GetItem( |
| const ash::ShelfID& id) const { |
| const int index = model_->ItemIndexByID(id); |
| if (index >= 0 && index < model_->item_count()) |
| return &model_->items()[index]; |
| return nullptr; |
| } |
| |
| void ChromeLauncherController::SetItemType(const ash::ShelfID& id, |
| ash::ShelfItemType type) { |
| const ash::ShelfItem* item = GetItem(id); |
| if (item && item->type != type) { |
| ash::ShelfItem new_item = *item; |
| new_item.type = type; |
| model_->Set(model_->ItemIndexByID(id), new_item); |
| } |
| } |
| |
| void ChromeLauncherController::SetItemStatus(const ash::ShelfID& id, |
| ash::ShelfItemStatus status) { |
| const ash::ShelfItem* item = GetItem(id); |
| if (item && item->status != status) { |
| ash::ShelfItem new_item = *item; |
| new_item.status = status; |
| model_->Set(model_->ItemIndexByID(id), new_item); |
| } |
| } |
| |
| void ChromeLauncherController::SetItemTitle(const ash::ShelfID& id, |
| const base::string16& title) { |
| const ash::ShelfItem* item = GetItem(id); |
| if (item && item->title != title) { |
| ash::ShelfItem new_item = *item; |
| new_item.title = title; |
| model_->Set(model_->ItemIndexByID(id), new_item); |
| } |
| } |
| |
| void ChromeLauncherController::CloseLauncherItem(const ash::ShelfID& id) { |
| CHECK(!id.IsNull()); |
| if (IsPinned(id)) { |
| // Create a new shortcut delegate. |
| SetItemStatus(id, ash::STATUS_CLOSED); |
| model_->SetShelfItemDelegate(id, |
| AppShortcutLauncherItemController::Create(id)); |
| } else { |
| RemoveShelfItem(id); |
| } |
| } |
| |
| void ChromeLauncherController::UnpinShelfItemInternal(const ash::ShelfID& id) { |
| const ash::ShelfItem* item = GetItem(id); |
| if (item && item->status != ash::STATUS_CLOSED) |
| UnpinRunningAppInternal(model_->ItemIndexByID(id)); |
| else |
| RemoveShelfItem(id); |
| } |
| |
| void ChromeLauncherController::SetItemStatusOrRemove( |
| const ash::ShelfID& id, |
| ash::ShelfItemStatus status) { |
| if (!IsPinned(id) && status == ash::STATUS_CLOSED) |
| RemoveShelfItem(id); |
| else |
| SetItemStatus(id, status); |
| } |
| |
| bool ChromeLauncherController::IsPinned(const ash::ShelfID& id) { |
| const ash::ShelfItem* item = GetItem(id); |
| return item && ItemTypeIsPinned(*item); |
| } |
| |
| void ChromeLauncherController::SetV1AppStatus(const std::string& app_id, |
| ash::ShelfItemStatus status) { |
| ash::ShelfID id(app_id); |
| const ash::ShelfItem* item = GetItem(id); |
| if (item) { |
| SetItemStatusOrRemove(id, status); |
| } else if (status != ash::STATUS_CLOSED && !app_id.empty()) { |
| InsertAppLauncherItem( |
| AppShortcutLauncherItemController::Create(ash::ShelfID(app_id)), status, |
| model_->item_count(), ash::TYPE_APP); |
| } |
| } |
| |
| void ChromeLauncherController::Close(const ash::ShelfID& id) { |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); |
| if (!delegate) |
| return; // May happen if menu closed. |
| delegate->Close(); |
| } |
| |
| bool ChromeLauncherController::IsOpen(const ash::ShelfID& id) { |
| const ash::ShelfItem* item = GetItem(id); |
| return item && item->status != ash::STATUS_CLOSED; |
| } |
| |
| bool ChromeLauncherController::IsPlatformApp(const ash::ShelfID& id) { |
| const extensions::Extension* extension = |
| GetExtensionForAppID(id.app_id, profile()); |
| // An extension can be synced / updated at any time and therefore not be |
| // available. |
| return extension ? extension->is_platform_app() : false; |
| } |
| |
| void ChromeLauncherController::LaunchApp(const ash::ShelfID& id, |
| ash::ShelfLaunchSource source, |
| int event_flags, |
| int64_t display_id) { |
| launcher_controller_helper_->LaunchApp(id, source, event_flags, display_id); |
| } |
| |
| void ChromeLauncherController::ActivateApp(const std::string& app_id, |
| ash::ShelfLaunchSource source, |
| int event_flags, |
| int64_t display_id) { |
| // If there is an existing delegate for this app, select it. |
| const ash::ShelfID shelf_id(app_id); |
| if (auto* delegate = model_->GetShelfItemDelegate(shelf_id)) { |
| SelectItemWithSource(delegate, source, display_id); |
| return; |
| } |
| |
| std::unique_ptr<AppShortcutLauncherItemController> item_delegate = |
| AppShortcutLauncherItemController::Create(shelf_id); |
| if (!item_delegate->GetRunningApplications().empty()) { |
| SelectItemWithSource(item_delegate.get(), source, display_id); |
| } else { |
| LaunchApp(shelf_id, source, event_flags, display_id); |
| } |
| } |
| |
| void ChromeLauncherController::SetLauncherItemImage( |
| const ash::ShelfID& shelf_id, |
| const gfx::ImageSkia& image) { |
| DCHECK(!image.isNull()); |
| if (const auto* item = GetItem(shelf_id)) { |
| ash::ShelfItem new_item = *item; |
| new_item.image = image; |
| model_->Set(model_->ItemIndexByID(shelf_id), new_item); |
| } |
| } |
| |
| void ChromeLauncherController::UpdateLauncherItemImage( |
| const std::string& app_id) { |
| if (auto* icon_loader = GetAppIconLoaderForApp(app_id)) |
| icon_loader->UpdateImage(app_id); |
| } |
| |
| void ChromeLauncherController::UpdateAppState(content::WebContents* contents, |
| bool remove) { |
| ash::ShelfID shelf_id(launcher_controller_helper_->GetAppID(contents)); |
| |
| // Check if the gMail app is loaded and it matches the given content. |
| // This special treatment is needed to address crbug.com/234268. |
| if (shelf_id.IsNull() && ContentCanBeHandledByGmailApp(contents)) |
| shelf_id = ash::ShelfID(kGmailAppId); |
| |
| // If the tab changed apps, remove its association with the previous app item. |
| if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) { |
| ash::ShelfID old_id(web_contents_to_app_id_[contents]); |
| if (old_id != shelf_id && GetItem(old_id) != nullptr) { |
| // Since GetAppState() will use |web_contents_to_app_id_| we remove |
| // the connection before calling it. |
| web_contents_to_app_id_.erase(contents); |
| SetItemStatusOrRemove(old_id, GetAppState(old_id.app_id)); |
| } |
| } |
| |
| if (remove) |
| web_contents_to_app_id_.erase(contents); |
| else |
| web_contents_to_app_id_[contents] = shelf_id.app_id; |
| |
| SetItemStatusOrRemove(shelf_id, GetAppState(shelf_id.app_id)); |
| } |
| |
| ash::ShelfID ChromeLauncherController::GetShelfIDForWebContents( |
| content::WebContents* contents) { |
| std::string app_id = launcher_controller_helper_->GetAppID(contents); |
| if (app_id.empty() && crostini::IsCrostiniEnabled(profile())) |
| app_id = GetCrostiniAppIdFromContents(contents); |
| if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) |
| app_id = kGmailAppId; |
| |
| // If there is no dedicated app item, use the browser shortcut item. |
| const ash::ShelfItem* item = GetItem(ash::ShelfID(app_id)); |
| return item ? item->id : ash::ShelfID(kChromeAppId); |
| } |
| |
| void ChromeLauncherController::SetRefocusURLPatternForTest( |
| const ash::ShelfID& id, |
| const GURL& url) { |
| const ash::ShelfItem* item = GetItem(id); |
| if (item && !IsPlatformApp(id) && |
| (item->type == ash::TYPE_PINNED_APP || item->type == ash::TYPE_APP)) { |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); |
| AppShortcutLauncherItemController* item_controller = |
| static_cast<AppShortcutLauncherItemController*>(delegate); |
| item_controller->set_refocus_url(url); |
| } else { |
| NOTREACHED() << "Invalid launcher item or type"; |
| } |
| } |
| |
| ash::ShelfAction ChromeLauncherController::ActivateWindowOrMinimizeIfActive( |
| ui::BaseWindow* window, |
| bool allow_minimize) { |
| // We might have to teleport a window back to the current user. |
| aura::Window* native_window = window->GetNativeWindow(); |
| const AccountId& current_account_id = |
| multi_user_util::GetAccountIdFromProfile(profile()); |
| if (!MultiUserWindowManagerHelper::GetInstance()->IsWindowOnDesktopOfUser( |
| native_window, current_account_id)) { |
| MultiUserWindowManagerHelper::GetWindowManager()->ShowWindowForUser( |
| native_window, current_account_id); |
| window->Activate(); |
| return ash::SHELF_ACTION_WINDOW_ACTIVATED; |
| } |
| |
| AppListClientImpl* app_list_client = AppListClientImpl::GetInstance(); |
| if (window->IsActive() && allow_minimize && |
| !(app_list_client && app_list_client->app_list_target_visibility())) { |
| window->Minimize(); |
| return ash::SHELF_ACTION_WINDOW_MINIMIZED; |
| } |
| |
| if (TabletModeClient::Get() && |
| TabletModeClient::Get()->tablet_mode_enabled()) { |
| // Run slide down animation to show the window. |
| wm::SetWindowVisibilityAnimationType( |
| native_window, ash::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN); |
| } |
| |
| window->Show(); |
| window->Activate(); |
| return ash::SHELF_ACTION_WINDOW_ACTIVATED; |
| } |
| |
| void ChromeLauncherController::ActiveUserChanged(const AccountId& account_id) { |
| // Store the order of running applications for the user which gets inactive. |
| RememberUnpinnedRunningApplicationOrder(); |
| // Coming here the default profile is already switched. All profile specific |
| // resources get released and the new profile gets attached instead. |
| ReleaseProfile(); |
| // When coming here, the active user has already be changed so that we can |
| // set it as active. |
| AttachProfile(ProfileManager::GetActiveUserProfile()); |
| // Update the V1 applications. |
| browser_status_monitor_->ActiveUserChanged(account_id.GetUserEmail()); |
| // Save/restore spinners belonging to the old/new user. Must be called before |
| // notifying the AppWindowControllers, as some of them assume spinners owned |
| // by the new user have already been added to the shelf. |
| shelf_spinner_controller_->ActiveUserChanged(account_id); |
| // Switch the running applications to the new user. |
| for (auto& controller : app_window_controllers_) |
| controller->ActiveUserChanged(account_id.GetUserEmail()); |
| // Update the user specific shell properties from the new user profile. |
| // Shelf preferences are loaded in ChromeLauncherController::AttachProfile. |
| UpdateAppLaunchersFromSync(); |
| |
| // Restore the order of running, but unpinned applications for the activated |
| // user. |
| RestoreUnpinnedRunningApplicationOrder(account_id.GetUserEmail()); |
| } |
| |
| void ChromeLauncherController::AdditionalUserAddedToSession(Profile* profile) { |
| // Switch the running applications to the new user. |
| for (auto& controller : app_window_controllers_) |
| controller->AdditionalUserAddedToSession(profile); |
| } |
| |
| ash::ShelfItemDelegate::AppMenuItems |
| ChromeLauncherController::GetAppMenuItemsForTesting( |
| const ash::ShelfItem& item) { |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item.id); |
| return delegate ? delegate->GetAppMenuItems(ui::EF_NONE) |
| : ash::ShelfItemDelegate::AppMenuItems(); |
| } |
| |
| std::vector<content::WebContents*> |
| ChromeLauncherController::GetV1ApplicationsFromAppId( |
| const std::string& app_id) { |
| // Use the app's shelf item to find that app's windows. |
| const ash::ShelfItem* item = GetItem(ash::ShelfID(app_id)); |
| if (!item) |
| return std::vector<content::WebContents*>(); |
| |
| // This should only be called for apps. |
| DCHECK(item->type == ash::TYPE_APP || item->type == ash::TYPE_PINNED_APP); |
| |
| return AppShortcutLauncherItemController::GetRunningApplications(app_id); |
| } |
| |
| std::vector<aura::Window*> ChromeLauncherController::GetArcWindows() { |
| std::vector<aura::Window*> windows = |
| arc_app_window_controller_->GetObservedWindows(); |
| std::vector<aura::Window*> arc_windows; |
| std::copy_if(windows.begin(), windows.end(), |
| std::inserter(arc_windows, arc_windows.end()), |
| [](aura::Window* w) { return arc::IsArcAppWindow(w); }); |
| return arc_windows; |
| } |
| |
| void ChromeLauncherController::ActivateShellApp(const std::string& app_id, |
| int window_index) { |
| const ash::ShelfItem* item = GetItem(ash::ShelfID(app_id)); |
| if (item && |
| (item->type == ash::TYPE_APP || item->type == ash::TYPE_PINNED_APP)) { |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item->id); |
| AppWindowLauncherItemController* item_controller = |
| delegate->AsAppWindowLauncherItemController(); |
| item_controller->ActivateIndexedApp(window_index); |
| } |
| } |
| |
| bool ChromeLauncherController::IsWebContentHandledByApplication( |
| content::WebContents* web_contents, |
| const std::string& app_id) { |
| if ((web_contents_to_app_id_.find(web_contents) != |
| web_contents_to_app_id_.end()) && |
| (web_contents_to_app_id_[web_contents] == app_id)) |
| return true; |
| return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents)); |
| } |
| |
| bool ChromeLauncherController::ContentCanBeHandledByGmailApp( |
| content::WebContents* web_contents) { |
| if (GetItem(ash::ShelfID(kGmailAppId)) != nullptr) { |
| const GURL url = web_contents->GetURL(); |
| // We need to extend the application matching for the gMail app beyond the |
| // manifest file's specification. This is required because of the namespace |
| // overlap with the offline app ("/mail/mu/"). |
| if (!base::MatchPattern(url.path(), "/mail/mu/*") && |
| base::MatchPattern(url.path(), "/mail/*") && |
| GetExtensionForAppID(kGmailAppId, profile()) && |
| GetExtensionForAppID(kGmailAppId, profile())->OverlapsWithOrigin(url)) |
| return true; |
| } |
| return false; |
| } |
| |
| gfx::Image ChromeLauncherController::GetAppListIcon( |
| content::WebContents* web_contents) const { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| if (IsIncognito(web_contents)) |
| return rb.GetImageNamed(IDR_ASH_SHELF_LIST_INCOGNITO_BROWSER); |
| favicon::FaviconDriver* favicon_driver = |
| favicon::ContentFaviconDriver::FromWebContents(web_contents); |
| gfx::Image result = favicon_driver->GetFavicon(); |
| if (result.IsEmpty()) |
| return rb.GetImageNamed(IDR_DEFAULT_FAVICON); |
| return result; |
| } |
| |
| base::string16 ChromeLauncherController::GetAppListTitle( |
| content::WebContents* web_contents) const { |
| base::string16 title = web_contents->GetTitle(); |
| if (!title.empty()) |
| return title; |
| WebContentsToAppIDMap::const_iterator iter = |
| web_contents_to_app_id_.find(web_contents); |
| if (iter != web_contents_to_app_id_.end()) { |
| std::string app_id = iter->second; |
| const extensions::Extension* extension = |
| GetExtensionForAppID(app_id, profile()); |
| if (extension) |
| return base::UTF8ToUTF16(extension->name()); |
| } |
| return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); |
| } |
| |
| BrowserShortcutLauncherItemController* |
| ChromeLauncherController::GetBrowserShortcutLauncherItemController() { |
| ash::ShelfID id(kChromeAppId); |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); |
| DCHECK(delegate) << "There should be always be a browser shortcut item."; |
| return static_cast<BrowserShortcutLauncherItemController*>(delegate); |
| } |
| |
| void ChromeLauncherController::OnUserProfileReadyToSwitch(Profile* profile) { |
| if (user_switch_observer_.get()) |
| user_switch_observer_->OnUserProfileReadyToSwitch(profile); |
| } |
| |
| ShelfSpinnerController* ChromeLauncherController::GetShelfSpinnerController() { |
| return shelf_spinner_controller_.get(); |
| } |
| |
| ChromeLauncherController::ScopedPinSyncDisabler |
| ChromeLauncherController::GetScopedPinSyncDisabler() { |
| // Only one temporary disabler should not exist at a time. |
| DCHECK(should_sync_pin_changes_); |
| return std::make_unique<base::AutoReset<bool>>(&should_sync_pin_changes_, |
| false); |
| } |
| |
| void ChromeLauncherController::SetLauncherControllerHelperForTest( |
| std::unique_ptr<LauncherControllerHelper> helper) { |
| launcher_controller_helper_ = std::move(helper); |
| } |
| |
| void ChromeLauncherController::SetAppIconLoadersForTest( |
| std::vector<std::unique_ptr<AppIconLoader>>& loaders) { |
| app_icon_loaders_.clear(); |
| for (auto& loader : loaders) |
| app_icon_loaders_.push_back(std::move(loader)); |
| } |
| |
| void ChromeLauncherController::SetProfileForTest(Profile* profile) { |
| profile_ = profile; |
| } |
| |
| void ChromeLauncherController::PinAppWithID(const std::string& app_id) { |
| model_->PinAppWithID(app_id); |
| } |
| |
| bool ChromeLauncherController::IsAppPinned(const std::string& app_id) { |
| return model_->IsAppPinned(app_id); |
| } |
| |
| void ChromeLauncherController::UnpinAppWithID(const std::string& app_id) { |
| model_->UnpinAppWithID(app_id); |
| } |
| |
| AppIconLoader* ChromeLauncherController::GetAppIconLoaderForApp( |
| const std::string& app_id) { |
| for (const auto& app_icon_loader : app_icon_loaders_) { |
| if (app_icon_loader->CanLoadImageForApp(app_id)) |
| return app_icon_loader.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // LauncherAppUpdater::Delegate: |
| |
| void ChromeLauncherController::OnAppInstalled( |
| content::BrowserContext* browser_context, |
| const std::string& app_id) { |
| if (IsAppPinned(app_id)) { |
| // Clear and re-fetch to ensure icon is up-to-date. |
| AppIconLoader* app_icon_loader = GetAppIconLoaderForApp(app_id); |
| if (app_icon_loader) { |
| app_icon_loader->ClearImage(app_id); |
| app_icon_loader->FetchImage(app_id); |
| } |
| } |
| |
| UpdateAppLaunchersFromSync(); |
| } |
| |
| void ChromeLauncherController::OnAppUninstalledPrepared( |
| content::BrowserContext* browser_context, |
| const std::string& app_id) { |
| // Since we might have windowed apps of this type which might have |
| // outstanding locks which needs to be removed. |
| const Profile* profile = Profile::FromBrowserContext(browser_context); |
| ash::ShelfID shelf_id(app_id); |
| if (GetItem(shelf_id) != nullptr) |
| CloseWindowedAppsFromRemovedExtension(app_id, profile); |
| |
| // Some apps may be removed locally. Unpin the item without removing the pin |
| // position from profile preferences. When needed, it is automatically deleted |
| // on app list model update. |
| if (IsAppPinned(app_id) && profile == this->profile()) |
| UnpinShelfItemInternal(shelf_id); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // AppIconLoaderDelegate: |
| |
| void ChromeLauncherController::OnAppImageUpdated(const std::string& app_id, |
| const gfx::ImageSkia& image) { |
| // TODO: need to get this working for shortcuts. |
| for (int index = 0; index < model_->item_count(); ++index) { |
| ash::ShelfItem item = model_->items()[index]; |
| ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item.id); |
| if (!delegate || delegate->image_set_by_controller() || |
| item.id.app_id != app_id) { |
| continue; |
| } |
| item.image = image; |
| shelf_spinner_controller_->MaybeApplySpinningEffect(app_id, &item.image); |
| model_->Set(index, item); |
| // It's possible we're waiting on more than one item, so don't break. |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ChromeLauncherController private: |
| |
| ash::ShelfID ChromeLauncherController::CreateAppShortcutLauncherItem( |
| const ash::ShelfID& shelf_id, |
| int index) { |
| return CreateAppShortcutLauncherItem(shelf_id, index, base::string16()); |
| } |
| |
| ash::ShelfID ChromeLauncherController::CreateAppShortcutLauncherItem( |
| const ash::ShelfID& shelf_id, |
| int index, |
| const base::string16& title) { |
| return InsertAppLauncherItem( |
| AppShortcutLauncherItemController::Create(shelf_id), ash::STATUS_CLOSED, |
| index, ash::TYPE_PINNED_APP, title); |
| } |
| |
| void ChromeLauncherController::RememberUnpinnedRunningApplicationOrder() { |
| RunningAppListIds list; |
| for (int i = 0; i < model_->item_count(); i++) { |
| if (model_->items()[i].type == ash::TYPE_APP) |
| list.push_back(model_->items()[i].id.app_id); |
| } |
| const std::string user_email = |
| multi_user_util::GetAccountIdFromProfile(profile()).GetUserEmail(); |
| last_used_running_application_order_[user_email] = list; |
| } |
| |
| void ChromeLauncherController::RestoreUnpinnedRunningApplicationOrder( |
| const std::string& user_id) { |
| const RunningAppListIdMap::iterator app_id_list = |
| last_used_running_application_order_.find(user_id); |
| if (app_id_list == last_used_running_application_order_.end()) |
| return; |
| |
| // Find the first insertion point for running applications. |
| int running_index = model_->FirstRunningAppIndex(); |
| for (const std::string& app_id : app_id_list->second) { |
| const ash::ShelfItem* item = GetItem(ash::ShelfID(app_id)); |
| if (item && item->type == ash::TYPE_APP) { |
| int app_index = model_->ItemIndexByID(item->id); |
| DCHECK_GE(app_index, 0); |
| if (running_index != app_index) |
| model_->Move(running_index, app_index); |
| running_index++; |
| } |
| } |
| } |
| |
| void ChromeLauncherController::RemoveShelfItem(const ash::ShelfID& id) { |
| const int index = model_->ItemIndexByID(id); |
| if (index >= 0 && index < model_->item_count()) |
| model_->RemoveItemAt(index); |
| } |
| |
| void ChromeLauncherController::PinRunningAppInternal( |
| int index, |
| const ash::ShelfID& shelf_id) { |
| DCHECK_EQ(GetItem(shelf_id)->type, ash::TYPE_APP); |
| SetItemType(shelf_id, ash::TYPE_PINNED_APP); |
| int running_index = model_->ItemIndexByID(shelf_id); |
| if (running_index < index) |
| --index; |
| if (running_index != index) |
| model_->Move(running_index, index); |
| } |
| |
| void ChromeLauncherController::UnpinRunningAppInternal(int index) { |
| DCHECK(index >= 0 && index < model_->item_count()); |
| const ash::ShelfItem& item = model_->items()[index]; |
| DCHECK_EQ(item.type, ash::TYPE_PINNED_APP); |
| SetItemType(item.id, ash::TYPE_APP); |
| } |
| |
| void ChromeLauncherController::SyncPinPosition(const ash::ShelfID& shelf_id) { |
| DCHECK(should_sync_pin_changes_); |
| DCHECK(!shelf_id.IsNull()); |
| |
| const int max_index = model_->item_count(); |
| const int index = model_->ItemIndexByID(shelf_id); |
| DCHECK_GE(index, 0); |
| |
| ash::ShelfID shelf_id_before; |
| std::vector<ash::ShelfID> shelf_ids_after; |
| |
| for (int i = index - 1; i >= 0; --i) { |
| shelf_id_before = model_->items()[i].id; |
| if (IsPinned(shelf_id_before)) |
| break; |
| } |
| |
| for (int i = index + 1; i < max_index; ++i) { |
| const ash::ShelfID& shelf_id_after = model_->items()[i].id; |
| if (IsPinned(shelf_id_after)) |
| shelf_ids_after.push_back(shelf_id_after); |
| } |
| |
| SetPinPosition(profile(), shelf_id, shelf_id_before, shelf_ids_after); |
| } |
| |
| void ChromeLauncherController::OnSyncModelUpdated() { |
| UpdateAppLaunchersFromSync(); |
| } |
| |
| void ChromeLauncherController::OnIsSyncingChanged() { |
| UpdateAppLaunchersFromSync(); |
| |
| // Initialize the local prefs if this is the first time sync has occurred. |
| if (!PrefServiceSyncableFromProfile(profile())->IsSyncing()) |
| return; |
| InitLocalPref(profile()->GetPrefs(), ash::prefs::kShelfAlignmentLocal, |
| ash::prefs::kShelfAlignment); |
| InitLocalPref(profile()->GetPrefs(), ash::prefs::kShelfAutoHideBehaviorLocal, |
| ash::prefs::kShelfAutoHideBehavior); |
| } |
| |
| void ChromeLauncherController::ScheduleUpdateAppLaunchersFromSync() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ChromeLauncherController::UpdateAppLaunchersFromSync, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ChromeLauncherController::UpdateAppLaunchersFromSync() { |
| // Do not sync pin changes during this function to avoid cyclical updates. |
| // This function makes the shelf model reflect synced prefs, and should not |
| // cyclically trigger sync changes (eg. ShelfItemAdded calls SyncPinPosition). |
| ScopedPinSyncDisabler scoped_pin_sync_disabler = GetScopedPinSyncDisabler(); |
| |
| const std::vector<ash::ShelfID> pinned_apps = |
| GetPinnedAppsFromSync(launcher_controller_helper_.get()); |
| |
| int index = 0; |
| |
| // Apply pins in two steps. At the first step, go through the list of apps to |
| // pin, move existing pin to current position specified by |index| or create |
| // the new pin at that position. |
| for (const auto& pref_shelf_id : pinned_apps) { |
| // Update apps icon if applicable. |
| OnAppUpdated(profile(), pref_shelf_id.app_id); |
| |
| // Find existing pin or app from the right of current |index|. |
| int app_index = index; |
| for (; app_index < model_->item_count(); ++app_index) { |
| const ash::ShelfItem& item = model_->items()[app_index]; |
| if (item.id == pref_shelf_id) |
| break; |
| } |
| if (app_index < model_->item_count()) { |
| // Found existing pin or running app. |
| const ash::ShelfItem item = model_->items()[app_index]; |
| if (ItemTypeIsPinned(item)) { |
| // Just move to required position or keep it inplace. |
| model_->Move(app_index, index); |
| } else { |
| PinRunningAppInternal(index, item.id); |
| } |
| DCHECK_EQ(model_->ItemIndexByID(item.id), index); |
| } else { |
| // This is fresh pin. Create new one. |
| DCHECK_NE(pref_shelf_id.app_id, kChromeAppId); |
| CreateAppShortcutLauncherItem(pref_shelf_id, index); |
| } |
| ++index; |
| } |
| |
| // At second step remove any pin to the right from the current index. |
| while (index < model_->item_count()) { |
| const ash::ShelfItem& item = model_->items()[index]; |
| if (item.type == ash::TYPE_PINNED_APP) |
| UnpinShelfItemInternal(item.id); |
| else |
| ++index; |
| } |
| |
| UpdatePolicyPinnedAppsFromPrefs(); |
| } |
| |
| void ChromeLauncherController::UpdatePolicyPinnedAppsFromPrefs() { |
| for (int index = 0; index < model_->item_count(); index++) { |
| ash::ShelfItem item = model_->items()[index]; |
| const bool pinned_by_policy = |
| GetPinnableForAppID(item.id.app_id, profile()) == |
| AppListControllerDelegate::PIN_FIXED; |
| if (item.pinned_by_policy != pinned_by_policy) { |
| item.pinned_by_policy = pinned_by_policy; |
| model_->Set(index, item); |
| } |
| } |
| } |
| |
| ash::ShelfItemStatus ChromeLauncherController::GetAppState( |
| const std::string& app_id) { |
| for (auto& it : web_contents_to_app_id_) { |
| if (it.second == app_id) { |
| Browser* browser = chrome::FindBrowserWithWebContents(it.first); |
| // Usually there should never be an item in our |web_contents_to_app_id_| |
| // list which got deleted already. However - in some situations e.g. |
| // Browser::SwapTabContent there is temporarily no associated browser. |
| // TODO(jamescook): This test may not be necessary anymore. |
| if (!browser) |
| continue; |
| return ash::STATUS_RUNNING; |
| } |
| } |
| return ash::STATUS_CLOSED; |
| } |
| |
| ash::ShelfID ChromeLauncherController::InsertAppLauncherItem( |
| std::unique_ptr<ash::ShelfItemDelegate> item_delegate, |
| ash::ShelfItemStatus status, |
| int index, |
| ash::ShelfItemType shelf_item_type, |
| const base::string16& title) { |
| CHECK(item_delegate); |
| CHECK(!GetItem(item_delegate->shelf_id())); |
| ash::ShelfItem item; |
| item.status = status; |
| item.type = shelf_item_type; |
| item.id = item_delegate->shelf_id(); |
| item.title = title; |
| // Set the delegate first to avoid constructing one in ShelfItemAdded. |
| model_->SetShelfItemDelegate(item.id, std::move(item_delegate)); |
| model_->AddAt(index, item); |
| return item.id; |
| } |
| |
| void ChromeLauncherController::CreateBrowserShortcutLauncherItem() { |
| // Do not sync the pin position of the browser shortcut item yet; its initial |
| // position before prefs have loaded is unimportant and the sync service may |
| // not yet be initialized. |
| ScopedPinSyncDisabler scoped_pin_sync_disabler = GetScopedPinSyncDisabler(); |
| |
| ash::ShelfItem browser_shortcut; |
| browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; |
| browser_shortcut.id = ash::ShelfID(kChromeAppId); |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_CHROME_APP_ICON_192); |
| browser_shortcut.title = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); |
| std::unique_ptr<BrowserShortcutLauncherItemController> item_delegate = |
| std::make_unique<BrowserShortcutLauncherItemController>(model_); |
| BrowserShortcutLauncherItemController* item_controller = item_delegate.get(); |
| // Set the delegate first to avoid constructing another one in ShelfItemAdded. |
| model_->SetShelfItemDelegate(browser_shortcut.id, std::move(item_delegate)); |
| // Add the item towards the start of the shelf, it will be ordered by weight. |
| model_->AddAt(0, browser_shortcut); |
| item_controller->UpdateBrowserItemState(); |
| } |
| |
| bool ChromeLauncherController::IsIncognito( |
| content::WebContents* web_contents) const { |
| const Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| return profile->IsOffTheRecord() && !profile->IsGuestSession() && |
| !profile->IsSystemProfile(); |
| } |
| |
| int ChromeLauncherController::FindInsertionPoint() { |
| for (int i = model_->item_count() - 1; i > 0; --i) { |
| if (ItemTypeIsPinned(model_->items()[i])) |
| return i; |
| } |
| return 0; |
| } |
| |
| void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension( |
| const std::string& app_id, |
| const Profile* profile) { |
| // This function cannot rely on the controller's enumeration functionality |
| // since the extension has already been unloaded. |
| const BrowserList* browser_list = BrowserList::GetInstance(); |
| std::vector<Browser*> browser_to_close; |
| for (BrowserList::const_reverse_iterator it = |
| browser_list->begin_last_active(); |
| it != browser_list->end_last_active(); ++it) { |
| Browser* browser = *it; |
| if (!browser->is_type_tabbed() && browser->is_type_popup() && |
| browser->is_app() && |
| app_id == web_app::GetAppIdFromApplicationName(browser->app_name()) && |
| profile == browser->profile()) { |
| browser_to_close.push_back(browser); |
| } |
| } |
| while (!browser_to_close.empty()) { |
| TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model(); |
| if (!tab_strip->empty()) |
| tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE); |
| browser_to_close.pop_back(); |
| } |
| } |
| |
| void ChromeLauncherController::AttachProfile(Profile* profile_to_attach) { |
| profile_ = profile_to_attach; |
| // Either add the profile to the list of known profiles and make it the active |
| // one for some functions of LauncherControllerHelper or create a new one. |
| if (!launcher_controller_helper_.get()) { |
| launcher_controller_helper_ = |
| std::make_unique<LauncherControllerHelper>(profile_); |
| } else { |
| launcher_controller_helper_->set_profile(profile_); |
| } |
| |
| // TODO(skuhne): The AppIconLoaderImpl has the same problem. Each loaded |
| // image is associated with a profile (its loader requires the profile). |
| // Since icon size changes are possible, the icon could be requested to be |
| // reloaded. However - having it not multi profile aware would cause problems |
| // if the icon cache gets deleted upon user switch. |
| std::unique_ptr<AppIconLoader> chrome_app_icon_loader = |
| std::make_unique<extensions::ChromeAppIconLoader>( |
| profile_, extension_misc::EXTENSION_ICON_MEDIUM, |
| base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd), this); |
| app_icon_loaders_.push_back(std::move(chrome_app_icon_loader)); |
| |
| if (arc::IsArcAllowedForProfile(profile_)) { |
| std::unique_ptr<AppIconLoader> arc_app_icon_loader = |
| std::make_unique<ArcAppIconLoader>( |
| profile_, extension_misc::EXTENSION_ICON_MEDIUM, this); |
| app_icon_loaders_.push_back(std::move(arc_app_icon_loader)); |
| } |
| |
| std::unique_ptr<AppIconLoader> internal_app_icon_loader = |
| std::make_unique<InternalAppIconLoader>( |
| profile_, extension_misc::EXTENSION_ICON_MEDIUM, this); |
| app_icon_loaders_.push_back(std::move(internal_app_icon_loader)); |
| |
| if (crostini::IsCrostiniUIAllowedForProfile(profile_)) { |
| std::unique_ptr<AppIconLoader> crostini_app_icon_loader = |
| std::make_unique<CrostiniAppIconLoader>( |
| profile_, extension_misc::EXTENSION_ICON_MEDIUM, this); |
| app_icon_loaders_.push_back(std::move(crostini_app_icon_loader)); |
| } |
| |
| pref_change_registrar_.Init(profile()->GetPrefs()); |
| pref_change_registrar_.Add( |
| prefs::kPolicyPinnedLauncherApps, |
| base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromSync, |
| base::Unretained(this))); |
| // Handling of prefs::kArcEnabled change should be called deferred to avoid |
| // race condition when OnAppUninstalledPrepared for ARC apps is called after |
| // UpdateAppLaunchersFromSync. |
| pref_change_registrar_.Add( |
| arc::prefs::kArcEnabled, |
| base::Bind(&ChromeLauncherController::ScheduleUpdateAppLaunchersFromSync, |
| base::Unretained(this))); |
| |
| std::unique_ptr<LauncherAppUpdater> extension_app_updater( |
| new LauncherExtensionAppUpdater(this, profile())); |
| app_updaters_.push_back(std::move(extension_app_updater)); |
| |
| if (arc::IsArcAllowedForProfile(profile())) { |
| std::unique_ptr<LauncherAppUpdater> arc_app_updater( |
| new LauncherArcAppUpdater(this, profile())); |
| app_updaters_.push_back(std::move(arc_app_updater)); |
| } |
| |
| if (crostini::IsCrostiniUIAllowedForProfile(profile())) { |
| std::unique_ptr<LauncherAppUpdater> crostini_app_updater( |
| new LauncherCrostiniAppUpdater(this, profile())); |
| app_updaters_.push_back(std::move(crostini_app_updater)); |
| } |
| |
| app_list::AppListSyncableService* app_list_syncable_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile()); |
| if (app_list_syncable_service) |
| app_list_syncable_service->AddObserverAndStart(this); |
| |
| PrefServiceSyncableFromProfile(profile())->AddObserver(this); |
| } |
| |
| void ChromeLauncherController::ReleaseProfile() { |
| app_updaters_.clear(); |
| |
| pref_change_registrar_.RemoveAll(); |
| |
| app_list::AppListSyncableService* app_list_syncable_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile()); |
| if (app_list_syncable_service) |
| app_list_syncable_service->RemoveObserver(this); |
| |
| PrefServiceSyncableFromProfile(profile())->RemoveObserver(this); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ash::ShelfModelObserver: |
| |
| void ChromeLauncherController::ShelfItemAdded(int index) { |
| ash::ShelfID id = model_->items()[index].id; |
| // Construct a ShelfItemDelegate for the item if one does not yet exist. |
| // The delegate must be set before FetchImage() so that shelf item icon is |
| // set properly when FetchImage() calls OnAppImageUpdated() synchronously. |
| if (!model_->GetShelfItemDelegate(id)) { |
| model_->SetShelfItemDelegate(id, |
| AppShortcutLauncherItemController::Create(id)); |
| } |
| |
| // Fetch the app icon, this may synchronously update the item's image. |
| AppIconLoader* app_icon_loader = GetAppIconLoaderForApp(id.app_id); |
| if (app_icon_loader) |
| app_icon_loader->FetchImage(id.app_id); |
| |
| // Update the item with any other missing Chrome-specific info. |
| // Construct |item| after FetchImage, which might synchronously load an image. |
| ash::ShelfItem item = model_->items()[index]; |
| if (item.type == ash::TYPE_APP || item.type == ash::TYPE_PINNED_APP) { |
| bool needs_update = false; |
| if (item.title.empty()) { |
| needs_update = true; |
| item.title = LauncherControllerHelper::GetAppTitle(profile(), id.app_id); |
| } |
| ash::ShelfItemStatus status = GetAppState(id.app_id); |
| if (status != item.status && status != ash::STATUS_CLOSED) { |
| needs_update = true; |
| item.status = status; |
| } |
| if (needs_update) |
| model_->Set(index, item); |
| } |
| |
| // Update the pin position preference as needed. |
| if (ItemTypeIsPinned(item) && should_sync_pin_changes_) |
| SyncPinPosition(item.id); |
| } |
| |
| void ChromeLauncherController::ShelfItemRemoved( |
| int index, |
| const ash::ShelfItem& old_item) { |
| // Remove the pin position from preferences as needed. |
| if (ItemTypeIsPinned(old_item) && should_sync_pin_changes_) |
| RemovePinPosition(profile(), old_item.id); |
| if (auto* app_icon_loader = GetAppIconLoaderForApp(old_item.id.app_id)) |
| app_icon_loader->ClearImage(old_item.id.app_id); |
| } |
| |
| void ChromeLauncherController::ShelfItemMoved(int start_index, |
| int target_index) { |
| // Update the pin position preference as needed. |
| const ash::ShelfItem& item = model_->items()[target_index]; |
| if (ItemTypeIsPinned(item) && should_sync_pin_changes_) |
| SyncPinPosition(item.id); |
| } |
| |
| void ChromeLauncherController::ShelfItemChanged( |
| int index, |
| const ash::ShelfItem& old_item) { |
| if (!should_sync_pin_changes_) |
| return; |
| |
| // Add or remove the pin position from preferences as needed. |
| const ash::ShelfItem& item = model_->items()[index]; |
| if (!ItemTypeIsPinned(old_item) && ItemTypeIsPinned(item)) |
| SyncPinPosition(item.id); |
| else if (ItemTypeIsPinned(old_item) && !ItemTypeIsPinned(item)) |
| RemovePinPosition(profile(), old_item.id); |
| } |
| |