| // Copyright (c) 2019 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/apps/app_service/arc_icon_once_loader.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_icon.h" |
| #include "chrome/common/chrome_features.h" |
| |
| namespace apps { |
| |
| constexpr size_t kMaxSimultaneousIconRequests = 250; |
| |
| // A part of an ArcIconOnceLoader, for a specific size_in_dip and |
| // icon_type. This two-level structure (an ArcIconOnceLoader contains |
| // multiple SizeSpecificLoader instances) is needed because each ArcAppIcon is |
| // for a specific size_in_dip and type. |
| class ArcIconOnceLoader::SizeSpecificLoader : public ArcAppIcon::Observer { |
| public: |
| SizeSpecificLoader(Profile* profile, |
| int32_t size_in_dip, |
| apps::mojom::IconType icon_type, |
| ArcIconOnceLoader& host); |
| ~SizeSpecificLoader() override; |
| |
| void LoadIcon(const std::string& app_id, |
| base::OnceCallback<void(ArcAppIcon*)> callback); |
| void Remove(const std::string& app_id); |
| void Reload(const std::string& app_id, ui::ScaleFactor scale_factor); |
| |
| // ArcAppIcon::Observer overrides. |
| void OnIconUpdated(ArcAppIcon* icon) override; |
| void OnIconFailed(ArcAppIcon* icon) override; |
| |
| private: |
| Profile* const profile_; |
| const int32_t size_in_dip_; |
| const apps::mojom::IconType icon_type_; |
| ArcIconOnceLoader& host_; |
| |
| // Maps App IDs to their icon loaders (for a specific size_in_dip and |
| // icon_compression). |
| std::map<std::string, std::unique_ptr<ArcAppIcon>> icons_; |
| |
| // Maps App IDs to callbacks to run when an icon is completely loaded. |
| std::multimap<std::string, base::OnceCallback<void(ArcAppIcon*)>> callbacks_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SizeSpecificLoader); |
| }; |
| |
| ArcIconOnceLoader::SizeSpecificLoader::SizeSpecificLoader( |
| Profile* profile, |
| int32_t size_in_dip, |
| apps::mojom::IconType icon_type, |
| ArcIconOnceLoader& host) |
| : profile_(profile), |
| size_in_dip_(size_in_dip), |
| icon_type_(icon_type), |
| host_(host) {} |
| |
| ArcIconOnceLoader::SizeSpecificLoader::~SizeSpecificLoader() { |
| for (auto& kv_pair : callbacks_) { |
| std::move(kv_pair.second).Run(nullptr); |
| } |
| } |
| |
| void ArcIconOnceLoader::SizeSpecificLoader::LoadIcon( |
| const std::string& app_id, |
| base::OnceCallback<void(ArcAppIcon*)> callback) { |
| auto iter = icons_.find(app_id); |
| if ((iter != icons_.end()) && |
| iter->second->EverySupportedScaleFactorIsLoaded()) { |
| std::move(callback).Run(iter->second.get()); |
| return; |
| } |
| |
| callbacks_.insert(std::make_pair(app_id, std::move(callback))); |
| |
| if (iter != icons_.end()) { |
| return; |
| } |
| ArcAppIcon::IconType icon_type; |
| switch (icon_type_) { |
| case apps::mojom::IconType::kUnknown: |
| case apps::mojom::IconType::kUncompressed: |
| icon_type = |
| base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon) |
| ? ArcAppIcon::IconType::kAdaptive |
| : ArcAppIcon::IconType::kUncompressed; |
| break; |
| case apps::mojom::IconType::kCompressed: |
| icon_type = |
| base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon) |
| ? ArcAppIcon::IconType::kAdaptive |
| : ArcAppIcon::IconType::kCompressed; |
| break; |
| case apps::mojom::IconType::kStandard: |
| icon_type = ArcAppIcon::IconType::kAdaptive; |
| break; |
| } |
| |
| auto arc_app_icon = host_.arc_app_icon_factory()->CreateArcAppIcon( |
| profile_, app_id, size_in_dip_, this, icon_type); |
| iter = icons_.insert(std::make_pair(app_id, std::move(arc_app_icon))).first; |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| host_.MaybeStartIconRequest(iter->second.get(), |
| ui::ScaleFactor::NUM_SCALE_FACTORS); |
| return; |
| } |
| iter->second->LoadSupportedScaleFactors(); |
| } |
| |
| void ArcIconOnceLoader::SizeSpecificLoader::Remove(const std::string& app_id) { |
| auto iter = icons_.find(app_id); |
| if (iter != icons_.end()) { |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| host_.RemoveArcAppIcon(iter->second.get()); |
| } |
| icons_.erase(iter); |
| } |
| } |
| |
| void ArcIconOnceLoader::SizeSpecificLoader::Reload( |
| const std::string& app_id, |
| ui::ScaleFactor scale_factor) { |
| auto iter = icons_.find(app_id); |
| if (iter != icons_.end()) { |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| host_.MaybeStartIconRequest(iter->second.get(), scale_factor); |
| return; |
| } |
| iter->second->LoadForScaleFactor(scale_factor); |
| } |
| } |
| |
| void ArcIconOnceLoader::SizeSpecificLoader::OnIconUpdated(ArcAppIcon* icon) { |
| if (!icon) { |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| host_.RemoveArcAppIcon(icon); |
| } |
| |
| if (!icon->EverySupportedScaleFactorIsLoaded()) { |
| return; |
| } |
| |
| auto range = callbacks_.equal_range(icon->app_id()); |
| auto count = std::distance(range.first, range.second); |
| if (count <= 0) { |
| return; |
| } |
| |
| // Optimize / simplify the common case. |
| if (count == 1) { |
| base::OnceCallback<void(ArcAppIcon*)> callback = |
| std::move(range.first->second); |
| callbacks_.erase(range.first, range.second); |
| std::move(callback).Run(icon); |
| return; |
| } |
| |
| // Run every callback in |range|. This is subtle, because an arbitrary |
| // callback could invoke further methods on |this|, which could mutate |
| // |callbacks_|, invalidating |range|'s iterators. |
| // |
| // Thus, we first gather the callbacks, then erase the |range|, then run the |
| // callbacks. |
| std::vector<base::OnceCallback<void(ArcAppIcon*)>> callbacks_to_run; |
| callbacks_to_run.reserve(count); |
| for (auto iter = range.first; iter != range.second; ++iter) { |
| callbacks_to_run.push_back(std::move(iter->second)); |
| } |
| callbacks_.erase(range.first, range.second); |
| for (auto& callback : callbacks_to_run) { |
| std::move(callback).Run(icon); |
| } |
| } |
| |
| void ArcIconOnceLoader::SizeSpecificLoader::OnIconFailed(ArcAppIcon* icon) { |
| OnIconUpdated(icon); |
| } |
| |
| ArcIconOnceLoader::ArcIconOnceLoader(Profile* profile) |
| : profile_(profile), stop_observing_called_(false) { |
| ArcAppListPrefs::Get(profile)->AddObserver(this); |
| arc_app_icon_factory_ = std::make_unique<arc::ArcAppIconFactory>(); |
| } |
| |
| ArcIconOnceLoader::~ArcIconOnceLoader() { |
| // Check that somebody called StopObserving. We can't call StopObserving |
| // here in the destructor, because we need a ArcAppListPrefs* prefs, and for |
| // tests, the prefs pointer for a profile can change over time (e.g. by |
| // ArcAppListPrefsFactory::RecreateServiceInstanceForTesting). |
| // |
| // See also ArcApps::Shutdown. |
| DCHECK(stop_observing_called_); |
| } |
| |
| void ArcIconOnceLoader::StopObserving(ArcAppListPrefs* prefs) { |
| stop_observing_called_ = true; |
| if (prefs) { |
| prefs->RemoveObserver(this); |
| } |
| } |
| |
| void ArcIconOnceLoader::LoadIcon( |
| const std::string& app_id, |
| int32_t size_in_dip, |
| apps::mojom::IconType icon_type, |
| base::OnceCallback<void(ArcAppIcon*)> callback) { |
| auto key = std::make_pair(size_in_dip, icon_type); |
| auto iter = size_specific_loaders_.find(key); |
| if (iter == size_specific_loaders_.end()) { |
| iter = size_specific_loaders_ |
| .insert(std::make_pair( |
| key, std::make_unique<SizeSpecificLoader>( |
| profile_, size_in_dip, icon_type, *this))) |
| .first; |
| } |
| iter->second->LoadIcon(app_id, std::move(callback)); |
| } |
| |
| void ArcIconOnceLoader::OnAppRemoved(const std::string& app_id) { |
| for (auto& iter : size_specific_loaders_) { |
| iter.second->Remove(app_id); |
| } |
| } |
| |
| void ArcIconOnceLoader::OnAppIconUpdated( |
| const std::string& app_id, |
| const ArcAppIconDescriptor& descriptor) { |
| for (int i = static_cast<int>(apps::mojom::IconType::kUncompressed); |
| i <= static_cast<int>(apps::mojom::IconType::kStandard); ++i) { |
| auto iter = size_specific_loaders_.find(std::make_pair( |
| descriptor.dip_size, static_cast<apps::mojom::IconType>(i))); |
| if (iter != size_specific_loaders_.end()) { |
| iter->second->Reload(app_id, descriptor.scale_factor); |
| } |
| } |
| } |
| |
| void ArcIconOnceLoader::SetArcAppIconFactoryForTesting( |
| std::unique_ptr<arc::ArcAppIconFactory> arc_app_icon_factory) { |
| arc_app_icon_factory_ = std::move(arc_app_icon_factory); |
| } |
| |
| void ArcIconOnceLoader::MaybeStartIconRequest(ArcAppIcon* arc_app_icon, |
| ui::ScaleFactor scale_factor) { |
| DCHECK(arc_app_icon); |
| if (in_flight_requests_.size() < kMaxSimultaneousIconRequests) { |
| in_flight_requests_.insert(arc_app_icon); |
| if (scale_factor == ui::ScaleFactor::NUM_SCALE_FACTORS) { |
| arc_app_icon->LoadSupportedScaleFactors(); |
| } else { |
| arc_app_icon->LoadForScaleFactor(scale_factor); |
| } |
| return; |
| } |
| |
| pending_requests_[arc_app_icon].insert(scale_factor); |
| } |
| |
| void ArcIconOnceLoader::RemoveArcAppIcon(ArcAppIcon* arc_app_icon) { |
| DCHECK(arc_app_icon); |
| |
| in_flight_requests_.erase(arc_app_icon); |
| pending_requests_.erase(arc_app_icon); |
| |
| MaybeLoadPendingIconRequest(); |
| } |
| |
| void ArcIconOnceLoader::MaybeLoadPendingIconRequest() { |
| while (!pending_requests_.empty() && |
| in_flight_requests_.size() < kMaxSimultaneousIconRequests) { |
| auto it = pending_requests_.begin(); |
| |
| ArcAppIcon* arc_app_icon = it->first; |
| DCHECK(arc_app_icon); |
| |
| std::set<ui::ScaleFactor>& scale_factors = it->second; |
| DCHECK(!scale_factors.empty()); |
| |
| // Handle all pending icon loading requests for |arc_app_icon|. |
| for (auto scale_factor : scale_factors) { |
| if (scale_factor == ui::ScaleFactor::NUM_SCALE_FACTORS) { |
| arc_app_icon->LoadSupportedScaleFactors(); |
| } else { |
| arc_app_icon->LoadForScaleFactor(scale_factor); |
| } |
| } |
| in_flight_requests_.insert(arc_app_icon); |
| |
| pending_requests_.erase(it); |
| } |
| } |
| |
| } // namespace apps |