blob: 11849675b7542e70d05c1e79084e8aabb5a18779 [file] [log] [blame]
// Copyright 2018 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/app_list/search/arc/arc_app_reinstall_search_provider.h"
#include <algorithm>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "ash/public/cpp/app_list/app_list_config.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "chrome/browser/ui/app_list/search/common/url_icon_source.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_service_manager.h"
#include "extensions/grit/extensions_browser_resources.h"
namespace {
// Seconds in between refreshes;
constexpr int64_t kRefreshSeconds = 1800;
constexpr char kAppListLatency[] = "Apps.AppListRecommendedResponse.Latency";
constexpr char kAppListCounts[] = "Apps.AppListRecommendedResponse.Count";
void RecordUmaResponseParseResult(arc::mojom::AppReinstallState result) {
UMA_HISTOGRAM_ENUMERATION("Apps.AppListRecommendedResponse", result);
}
std::string LimitIconSizeWithFife(const std::string& icon_url,
int icon_dimension) {
// We append a suffix to icon url
DCHECK_GT(icon_dimension, 0);
return base::StrCat({icon_url, "=s", base::NumberToString(icon_dimension)});
}
} // namespace
namespace app_list {
ArcAppReinstallSearchProvider::ArcAppReinstallSearchProvider(
Profile* profile,
unsigned int max_result_count)
: profile_(profile),
max_result_count_(max_result_count),
icon_dimension_(
app_list::AppListConfig::instance().GetPreferredIconDimension(
ash::SearchResultDisplayType::kRecommendation)),
app_fetch_timer_(std::make_unique<base::RepeatingTimer>()),
weak_ptr_factory_(this) {
DCHECK(profile_ != nullptr);
ArcAppListPrefs::Get(profile_)->AddObserver(this);
MaybeUpdateFetching();
}
ArcAppReinstallSearchProvider::~ArcAppReinstallSearchProvider() {
ArcAppListPrefs::Get(profile_)->RemoveObserver(this);
}
void ArcAppReinstallSearchProvider::BeginRepeatingFetch() {
// If already running, do not re-start.
if (app_fetch_timer_->IsRunning())
return;
app_fetch_timer_->Start(FROM_HERE,
base::TimeDelta::FromSeconds(kRefreshSeconds), this,
&ArcAppReinstallSearchProvider::StartFetch);
StartFetch();
}
void ArcAppReinstallSearchProvider::StopRepeatingFetch() {
weak_ptr_factory_.InvalidateWeakPtrs();
app_fetch_timer_->AbandonAndStop();
loaded_value_.clear();
icon_urls_.clear();
loading_icon_urls_.clear();
UpdateResults();
}
void ArcAppReinstallSearchProvider::Start(const base::string16& query) {
if (query_is_empty_ == query.empty())
return;
query_is_empty_ = query.empty();
UpdateResults();
}
void ArcAppReinstallSearchProvider::StartFetch() {
arc::mojom::AppInstance* app_instance =
arc::ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
GetAppReinstallCandidates)
: nullptr;
if (app_instance == nullptr)
return;
app_instance->GetAppReinstallCandidates(base::BindOnce(
&ArcAppReinstallSearchProvider::OnGetAppReinstallCandidates,
weak_ptr_factory_.GetWeakPtr(), base::Time::Now()));
}
void ArcAppReinstallSearchProvider::OnGetAppReinstallCandidates(
base::Time start_time,
arc::mojom::AppReinstallState state,
std::vector<arc::mojom::AppReinstallCandidatePtr> results) {
RecordUmaResponseParseResult(state);
DCHECK_NE(start_time, base::Time::UnixEpoch());
UMA_HISTOGRAM_TIMES(kAppListLatency, base::Time::Now() - start_time);
UMA_HISTOGRAM_COUNTS_100(kAppListCounts, results.size());
if (state != arc::mojom::AppReinstallState::REQUEST_SUCCESS) {
LOG(ERROR) << "Failed to get reinstall candidates: " << state;
return;
}
loaded_value_.clear();
for (const auto& candidate : results) {
// only keep candidates with icons.
if (candidate->icon_url != base::nullopt) {
loaded_value_.push_back(candidate.Clone());
}
}
UpdateResults();
}
void ArcAppReinstallSearchProvider::UpdateResults() {
// We clear results if there are none from the server, or the user has entered
// a non-zero query.
if (loaded_value_.empty() || !query_is_empty_) {
if (loaded_value_.empty())
icon_urls_.clear();
ClearResults();
return;
}
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
DCHECK(prefs != nullptr);
std::vector<std::unique_ptr<ChromeSearchResult>> new_results;
std::unordered_set<std::string> used_icon_urls;
// Lock over the whole list.
for (size_t i = 0, processed = 0;
i < loaded_value_.size() && processed < max_result_count_; ++i) {
// Any packages that are installing or installed and not in sync with the
// server are removed with IsUnknownPackage.
if (!prefs->IsUnknownPackage(loaded_value_[i]->package_name))
continue;
processed++;
// From this point, we believe that this item should be in the result list.
// We try to find this icon, and if it is not available, we load it.
const std::string& icon_url = loaded_value_[i]->icon_url.value();
// All the icons we are showing.
used_icon_urls.insert(icon_url);
const auto icon_it = icon_urls_.find(icon_url);
const auto loading_icon_it = loading_icon_urls_.find(icon_url);
if (icon_it == icon_urls_.end() &&
loading_icon_it == loading_icon_urls_.end()) {
// this icon is not loaded, nor is it in the loading set. Add it.
loading_icon_urls_[icon_url] = gfx::ImageSkia(
std::make_unique<app_list::UrlIconSource>(
base::BindRepeating(&ArcAppReinstallSearchProvider::OnIconLoaded,
weak_ptr_factory_.GetWeakPtr(), icon_url),
profile_, GURL(LimitIconSizeWithFife(icon_url, icon_dimension_)),
icon_dimension_, IDR_APP_DEFAULT_ICON),
gfx::Size(icon_dimension_, icon_dimension_));
loading_icon_urls_[icon_url].GetRepresentation(1.0f);
} else if (icon_it != icon_urls_.end()) {
// Icon is loaded, add it to the results.
new_results.emplace_back(std::make_unique<ArcAppReinstallAppResult>(
loaded_value_[i], icon_it->second));
}
}
// Remove unused icons.
std::unordered_set<std::string> unused_icon_urls;
for (const auto& it : icon_urls_) {
if (used_icon_urls.find(it.first) == used_icon_urls.end()) {
// This url is used, remove.
unused_icon_urls.insert(it.first);
}
}
for (const std::string& url : unused_icon_urls) {
icon_urls_.erase(url);
loading_icon_urls_.erase(url);
}
// Now we are ready with new_results. do we actually need to replace things on
// screen?
if (!ResultsIdentical(results(), new_results)) {
SwapResults(&new_results);
}
}
void ArcAppReinstallSearchProvider::MaybeUpdateFetching() {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(arc::kPlayStoreAppId);
if (app_info && app_info->ready)
BeginRepeatingFetch();
else
StopRepeatingFetch();
}
void ArcAppReinstallSearchProvider::OnAppRegistered(
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
OnAppStatesChanged(app_id, app_info);
}
void ArcAppReinstallSearchProvider::OnAppStatesChanged(
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (app_id == arc::kPlayStoreAppId)
MaybeUpdateFetching();
}
void ArcAppReinstallSearchProvider::OnAppRemoved(const std::string& app_id) {
if (app_id == arc::kPlayStoreAppId)
MaybeUpdateFetching();
}
void ArcAppReinstallSearchProvider::OnInstallationStarted(
const std::string& package_name) {
UpdateResults();
}
void ArcAppReinstallSearchProvider::OnInstallationFinished(
const std::string& package_name,
bool success) {
UpdateResults();
}
void ArcAppReinstallSearchProvider::OnPackageInstalled(
const arc::mojom::ArcPackageInfo& package_info) {
UpdateResults();
}
void ArcAppReinstallSearchProvider::OnPackageRemoved(
const std::string& package_name,
bool uninstalled) {
UpdateResults();
}
void ArcAppReinstallSearchProvider::SetTimerForTesting(
std::unique_ptr<base::RepeatingTimer> timer) {
app_fetch_timer_ = std::move(timer);
}
// For icon load callback, in OnGetAppReinstallCandidates
void ArcAppReinstallSearchProvider::OnIconLoaded(const std::string& icon_url) {
auto skia_ptr = loading_icon_urls_.find(icon_url);
DCHECK(skia_ptr != loading_icon_urls_.end());
if (skia_ptr == loading_icon_urls_.end()) {
return;
}
const std::vector<gfx::ImageSkiaRep> image_reps =
skia_ptr->second.image_reps();
for (const gfx::ImageSkiaRep& rep : image_reps)
skia_ptr->second.RemoveRepresentation(rep.scale());
DCHECK_LE(skia_ptr->second.width(), icon_dimension_);
// ImageSkia is now ready to serve, move to the done list and update the
// screen.
icon_urls_[icon_url] = skia_ptr->second;
loading_icon_urls_.erase(icon_url);
UpdateResults();
}
bool ArcAppReinstallSearchProvider::ResultsIdentical(
const std::vector<std::unique_ptr<ChromeSearchResult>>& old_results,
const std::vector<std::unique_ptr<ChromeSearchResult>>& new_results) {
if (old_results.size() != new_results.size()) {
return false;
}
for (size_t i = 0; i < old_results.size(); ++i) {
const ChromeSearchResult& old_result = *(old_results[i]);
const ChromeSearchResult& new_result = *(new_results[i]);
if (!old_result.icon().BackedBySameObjectAs(new_result.icon())) {
return false;
}
if (old_result.title() != new_result.title()) {
return false;
}
if (old_result.id() != new_result.id()) {
return false;
}
if (old_result.relevance() != new_result.relevance()) {
return false;
}
if (old_result.rating() != new_result.rating()) {
return false;
}
}
return true;
}
} // namespace app_list