| // Copyright 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/ui/app_list/search/zero_state_file_provider.h" |
| |
| #include <string> |
| |
| #include "ash/public/cpp/app_list/app_list_features.h" |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/post_task.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/search/file_chip_result.h" |
| #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h" |
| #include "chrome/browser/ui/app_list/search/zero_state_file_result.h" |
| |
| using file_manager::file_tasks::FileTasksObserver; |
| |
| namespace app_list { |
| namespace { |
| |
| constexpr int kMaxLocalFiles = 10; |
| |
| // Given the output of RecurrenceRanker::RankTopN, partition files by whether |
| // they exist or not on disk. Returns a pair of vectors: <valid, invalid>. |
| internal::ValidAndInvalidResults ValidateFiles( |
| const std::vector<std::pair<std::string, float>>& ranker_results) { |
| internal::ScoredResults valid; |
| internal::Results invalid; |
| for (const auto& path_score : ranker_results) { |
| // We use FilePath::FromUTF8Unsafe to decode the filepath string. As per its |
| // documentation, this is a safe use of the function because |
| // ZeroStateFileProvider is only used on ChromeOS, for which |
| // filepaths are UTF8. |
| const auto& path = base::FilePath::FromUTF8Unsafe(path_score.first); |
| if (base::PathExists(path)) |
| valid.emplace_back(path, path_score.second); |
| else |
| invalid.emplace_back(path); |
| } |
| return {valid, invalid}; |
| } |
| |
| } // namespace |
| |
| ZeroStateFileProvider::ZeroStateFileProvider(Profile* profile) |
| : profile_(profile) { |
| DCHECK(profile_); |
| task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::BEST_EFFORT, base::MayBlock(), |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); |
| |
| auto* notifier = |
| file_manager::file_tasks::FileTasksNotifier::GetForProfile(profile_); |
| |
| UMA_HISTOGRAM_BOOLEAN( |
| "Apps.AppList.ZeroStateFileProvider.NotifierCreationSuccess", |
| notifier != nullptr); |
| |
| if (notifier) { |
| file_tasks_observer_.Add(notifier); |
| |
| RecurrenceRankerConfigProto config; |
| config.set_min_seconds_between_saves(120u); |
| config.set_condition_limit(1u); |
| config.set_condition_decay(0.5f); |
| config.set_target_limit(200); |
| config.set_target_decay(0.9f); |
| config.mutable_predictor()->mutable_default_predictor(); |
| files_ranker_ = std::make_unique<RecurrenceRanker>( |
| "ZeroStateLocalFiles", |
| profile->GetPath().AppendASCII("zero_state_local_files.pb"), config, |
| chromeos::ProfileHelper::IsEphemeralUserProfile(profile)); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| app_list_features::kEnableLauncherSearchNormalization)) { |
| normalizer_.emplace("zero_state_file_provider", profile); |
| } |
| } |
| |
| ZeroStateFileProvider::~ZeroStateFileProvider() = default; |
| |
| ash::AppListSearchResultType ZeroStateFileProvider::ResultType() { |
| return ash::AppListSearchResultType::kZeroStateFile; |
| } |
| |
| void ZeroStateFileProvider::Start(const base::string16& query) { |
| query_start_time_ = base::TimeTicks::Now(); |
| ClearResultsSilently(); |
| if (!files_ranker_ || !query.empty()) |
| return; |
| |
| base::PostTaskAndReplyWithResult( |
| task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ValidateFiles, files_ranker_->RankTopN(kMaxLocalFiles)), |
| base::BindOnce(&ZeroStateFileProvider::SetSearchResults, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ZeroStateFileProvider::SetSearchResults( |
| const internal::ValidAndInvalidResults& results) { |
| // Delete invalid results from the model. |
| for (const auto& path : results.second) |
| files_ranker_->RemoveTarget(path.value()); |
| |
| // Use valid results for search results. |
| SearchProvider::Results new_results; |
| for (const auto& filepath_score : results.first) { |
| new_results.emplace_back(std::make_unique<ZeroStateFileResult>( |
| filepath_score.first, filepath_score.second, profile_)); |
| // Add suggestion chip file results |
| if (app_list_features::IsSuggestedFilesEnabled()) { |
| new_results.emplace_back(std::make_unique<FileChipResult>( |
| filepath_score.first, filepath_score.second, profile_)); |
| } |
| } |
| |
| if (normalizer_.has_value()) { |
| normalizer_->Record(new_results); |
| normalizer_->NormalizeResults(&new_results); |
| } |
| |
| UMA_HISTOGRAM_TIMES("Apps.AppList.ZeroStateFileProvider.Latency", |
| base::TimeTicks::Now() - query_start_time_); |
| SwapResults(&new_results); |
| } |
| |
| void ZeroStateFileProvider::OnFilesOpened( |
| const std::vector<FileOpenEvent>& file_opens) { |
| if (!files_ranker_) |
| return; |
| |
| // The DriveQuickAccessProvider handles Drive files, so recording them here |
| // would be redundant. Filter them out by checking the file resides within the |
| // user's cryptohome. |
| const auto& profile_path = profile_->GetPath(); |
| for (const auto& file_open : file_opens) { |
| if (profile_path.AppendRelativePath(file_open.path, nullptr)) |
| files_ranker_->Record(file_open.path.value()); |
| } |
| } |
| |
| } // namespace app_list |