chromium/chrome/browser/ash/app_list/search/files/zero_state_file_provider.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/app_list/search/files/zero_state_file_provider.h"

#include <optional>
#include <string>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_list/search/files/file_result.h"
#include "chrome/browser/ash/app_list/search/files/justifications.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
#include "chrome/browser/ash/file_suggest/file_suggest_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_service.h"

using file_manager::file_tasks::FileTasksObserver;

namespace app_list {
namespace {

constexpr char kSchema[] = "zero_state_file://";
constexpr size_t kMaxLocalFiles = 10u;

// Screenshots are identified as files that match ScreenshotXXX.png in the
// Downloads folder.
bool IsScreenshot(const base::FilePath& path,
                  const base::FilePath& downloads_path) {
  return path.DirName() == downloads_path && path.Extension() == ".png" &&
         path.BaseName().value().rfind("Screenshot", 0) == 0;
}

bool IsDriveDisabled(Profile* profile) {
  return profile->GetPrefs()->GetBoolean(drive::prefs::kDisableDrive);
}

}  // namespace

ZeroStateFileProvider::ZeroStateFileProvider(Profile* profile)
    : SearchProvider(SearchCategory::kFiles),
      profile_(profile),
      thumbnail_loader_(profile),
      file_suggest_service_(
          ash::FileSuggestKeyedServiceFactory::GetInstance()->GetService(
              profile)),
      downloads_path_(
          file_manager::util::GetDownloadsFolderForProfile(profile)) {
  DCHECK(profile_);
  file_suggest_service_observation_.Observe(file_suggest_service_);
}

ZeroStateFileProvider::~ZeroStateFileProvider() = default;

ash::AppListSearchResultType ZeroStateFileProvider::ResultType() const {
  return ash::AppListSearchResultType::kZeroStateFile;
}

void ZeroStateFileProvider::StartZeroState() {
  query_start_time_ = base::TimeTicks::Now();

  // Despite this being for zero-state _local_ files only, we disable all
  // results in the Continue section if Drive is disabled.
  if (IsDriveDisabled(profile_)) {
    return;
  }

  file_suggest_service_->GetSuggestFileData(
      ash::FileSuggestionType::kLocalFile,
      base::BindOnce(&ZeroStateFileProvider::OnSuggestFileDataFetched,
                     weak_factory_.GetWeakPtr()));
}

void ZeroStateFileProvider::StopZeroState() {
  // Cancel any pending zero state callbacks.
  weak_factory_.InvalidateWeakPtrs();
}

void ZeroStateFileProvider::OnSuggestFileDataFetched(
    const std::optional<std::vector<ash::FileSuggestData>>& suggest_results) {
  if (suggest_results)
    SetSearchResults(*suggest_results);
}

void ZeroStateFileProvider::SetSearchResults(
    const std::vector<ash::FileSuggestData>& results) {
  const bool timestamp_based_score =
      ash::features::UseMixedFileLauncherContinueSection();
  const base::TimeDelta max_recency = ash::GetMaxFileSuggestionRecency();

  // Use valid results for search results.
  SearchProvider::Results new_results;
  for (size_t i = 0; i < std::min(results.size(), kMaxLocalFiles); ++i) {
    const auto& filepath = results[i].file_path;
    if (!IsScreenshot(filepath, downloads_path_)) {
      DCHECK(results[i].score.has_value());

      const double score = timestamp_based_score ? ash::ToTimestampBasedScore(
                                                       results[i], max_recency)
                                                 : *results[i].score;
      auto result = std::make_unique<FileResult>(
          results[i].id, filepath, results[i].prediction_reason,
          ash::AppListSearchResultType::kZeroStateFile,
          ash::SearchResultDisplayType::kContinue, score, std::u16string(),
          FileResult::Type::kFile, profile_, /*thumbnail_loader=*/nullptr);
      if (results[i].modified_time) {
        result->SetContinueFileSuggestionType(
            ash::ContinueFileSuggestionType::kModifiedByCurrentUserFile);
      } else if (results[i].viewed_time) {
        result->SetContinueFileSuggestionType(
            ash::ContinueFileSuggestionType::kViewedFile);
      }
      new_results.push_back(std::move(result));
    }
  }

  if (app_list_features::IsForceShowContinueSectionEnabled())
    AppendFakeSearchResults(&new_results);

  UMA_HISTOGRAM_TIMES("Apps.AppList.ZeroStateFileProvider.Latency",
                      base::TimeTicks::Now() - query_start_time_);
  SwapResults(&new_results);
}

void ZeroStateFileProvider::AppendFakeSearchResults(Results* results) {
  constexpr int kTotalFakeFiles = 3;
  for (int i = 0; i < kTotalFakeFiles; ++i) {
    const base::FilePath path(base::FilePath(FILE_PATH_LITERAL(
        base::StrCat({"Fake-file-", base::NumberToString(i), ".png"}))));
    results->emplace_back(std::make_unique<FileResult>(
        /*id=*/kSchema + path.value(), path, u"-",
        ash::AppListSearchResultType::kZeroStateFile,
        ash::SearchResultDisplayType::kContinue, 0.1f, std::u16string(),
        FileResult::Type::kFile, profile_, /*thumbnail_loader=*/nullptr));
  }
}

void ZeroStateFileProvider::OnFileSuggestionUpdated(
    ash::FileSuggestionType type) {
  if (type == ash::FileSuggestionType::kLocalFile) {
    StartZeroState();
  }
}

}  // namespace app_list