910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/drive/service/fake_drive_service.h"

#include <stddef.h>

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_string_value_serializer.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/file_system_core_util.h"
#include "crypto/obsolete/md5.h"
#include "google_apis/common/test_util.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/drive/drive_common_callbacks.h"
#include "net/base/url_util.h"

using google_apis::AboutResource;
using google_apis::AboutResourceCallback;
using google_apis::ApiErrorCode;
using google_apis::AuthStatusCallback;
using google_apis::CancelCallbackOnce;
using google_apis::ChangeList;
using google_apis::ChangeListCallback;
using google_apis::ChangeListOnceCallback;
using google_apis::ChangeResource;
using google_apis::DownloadActionCallback;
using google_apis::DRIVE_FILE_ERROR;
using google_apis::EntryActionCallback;
using google_apis::FileList;
using google_apis::FileListCallback;
using google_apis::FileResource;
using google_apis::FileResourceCallback;
using google_apis::GetContentCallback;
using google_apis::HTTP_BAD_REQUEST;
using google_apis::HTTP_CREATED;
using google_apis::HTTP_FORBIDDEN;
using google_apis::HTTP_NO_CONTENT;
using google_apis::HTTP_NOT_FOUND;
using google_apis::HTTP_PRECONDITION;
using google_apis::HTTP_RESUME_INCOMPLETE;
using google_apis::HTTP_SUCCESS;
using google_apis::InitiateUploadCallback;
using google_apis::NO_CONNECTION;
using google_apis::OTHER_ERROR;
using google_apis::ParentReference;
using google_apis::ProgressCallback;
using google_apis::StartPageToken;
using google_apis::TeamDriveList;
using google_apis::TeamDriveListCallback;
using google_apis::TeamDriveResource;
using google_apis::UploadRangeResponse;
using google_apis::drive::UploadRangeCallback;
namespace test_util = google_apis::test_util;

namespace drive {
namespace {

// Returns true if the entry matches with the search query.
// Supports queries consist of following format.
// - Phrases quoted by double/single quotes
// - AND search for multiple words/phrases segmented by space
// - Limited attribute search.  Only "title:" is supported.
bool EntryMatchWithQuery(const ChangeResource& entry,
                         const std::string& query) {
  base::StringTokenizer tokenizer(query, " ");
  tokenizer.set_quote_chars("\"'");
  while (tokenizer.GetNext()) {
    std::string key, value;
    const std::string& token = tokenizer.token();
    if (token.find(':') == std::string::npos) {
      base::TrimString(token, "\"'", &value);
    } else {
      base::StringTokenizer key_value(token, ":");
      key_value.set_quote_chars("\"'");
      if (!key_value.GetNext())
        return false;
      key = key_value.token();
      if (!key_value.GetNext())
        return false;
      base::TrimString(key_value.token(), "\"'", &value);
    }

    // TODO(peria): Deal with other attributes than title.
    if (!key.empty() && key != "title")
      return false;
    // Search query in the title.
    if (!entry.file() || entry.file()->title().find(value) == std::string::npos)
      return false;
  }
  return true;
}

std::string GetMd5Checksum(std::string_view input) {
  return base::HexEncodeLower(
      crypto::obsolete::Md5::HashForTesting(base::as_byte_span(input)));
}

void ScheduleUploadRangeCallback(UploadRangeCallback callback,
                                 int64_t start_position,
                                 int64_t end_position,
                                 ApiErrorCode error,
                                 std::unique_ptr<FileResource> entry) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback),
                     UploadRangeResponse(error, start_position, end_position),
                     std::move(entry)));
}

void FileListCallbackAdapter(FileListCallback callback,
                             ApiErrorCode error,
                             std::unique_ptr<ChangeList> change_list) {
  std::unique_ptr<FileList> file_list;
  if (!change_list) {
    std::move(callback).Run(error, std::move(file_list));
    return;
  }

  file_list = std::make_unique<FileList>();
  file_list->set_next_link(change_list->next_link());
  for (size_t i = 0; i < change_list->items().size(); ++i) {
    const ChangeResource& entry = *change_list->items()[i];
    if (entry.type() == ChangeResource::FILE && entry.file())
      file_list->mutable_items()->push_back(
          std::make_unique<FileResource>(*entry.file()));
  }
  std::move(callback).Run(error, std::move(file_list));
}

bool UserHasWriteAccess(google_apis::drive::PermissionRole user_permission) {
  switch (user_permission) {
    case google_apis::drive::PERMISSION_ROLE_OWNER:
    case google_apis::drive::PERMISSION_ROLE_WRITER:
      return true;
    case google_apis::drive::PERMISSION_ROLE_READER:
    case google_apis::drive::PERMISSION_ROLE_COMMENTER:
      break;
  }
  return false;
}

void CallFileResourceCallback(FileResourceCallback callback,
                              const UploadRangeResponse& response,
                              std::unique_ptr<FileResource> entry) {
  std::move(callback).Run(response.code, std::move(entry));
}

struct CallResumeUpload {
  CallResumeUpload() = default;
  ~CallResumeUpload() = default;

  void Run(ApiErrorCode code, const GURL& upload_url) {
    if (service) {
      service->ResumeUpload(
          upload_url,
          /* start position */ 0,
          /* end position */ content_length, content_length, content_type,
          local_file_path,
          base::BindOnce(&CallFileResourceCallback, std::move(callback)),
          progress_callback);
    }
  }

  base::WeakPtr<FakeDriveService> service;
  int64_t content_length;
  std::string content_type;
  base::FilePath local_file_path;
  FileResourceCallback callback;
  ProgressCallback progress_callback;
};

std::string GetTeamDriveId(const google_apis::ChangeResource& change_resource) {
  std::string team_drive_id;
  switch (change_resource.type()) {
    case ChangeResource::FILE:
      team_drive_id = change_resource.file()->team_drive_id();
      break;
    case ChangeResource::TEAM_DRIVE:
      team_drive_id = change_resource.team_drive_id();
      break;
    case ChangeResource::UNKNOWN:
      NOTREACHED();
  }
  return team_drive_id;
}

}  // namespace

struct FakeDriveService::EntryInfo {
  EntryInfo()
      : user_permission(google_apis::drive::PERMISSION_ROLE_OWNER),
        visibility(google_apis::drive::FILE_VISIBILITY_DEFAULT) {}

  google_apis::ChangeResource change_resource;
  GURL share_url;
  std::string content_data;

  // Behaves in the same way as "userPermission" described in
  // https://developers.google.com/drive/v2/reference/files
  google_apis::drive::PermissionRole user_permission;

  google_apis::drive::FileVisibility visibility;
};

struct FakeDriveService::UploadSession {
  std::string content_type;
  int64_t content_length;
  std::string parent_resource_id;
  std::string resource_id;
  std::string etag;
  std::string title;

  int64_t uploaded_size;

  UploadSession() : content_length(0), uploaded_size(0) {}

  UploadSession(std::string content_type,
                int64_t content_length,
                std::string parent_resource_id,
                std::string resource_id,
                std::string etag,
                std::string title)
      : content_type(content_type),
        content_length(content_length),
        parent_resource_id(parent_resource_id),
        resource_id(resource_id),
        etag(etag),
        title(title),
        uploaded_size(0) {}
};

FakeDriveService::FakeDriveService()
    : about_resource_(new AboutResource),
      start_page_token_(new StartPageToken),
      date_seq_(0),
      next_upload_sequence_number_(0),
      largest_changestamp_(654321),
      default_max_results_(0),
      resource_id_count_(0),
      team_drive_list_load_count_(0),
      file_list_load_count_(0),
      change_list_load_count_(0),
      directory_load_count_(0),
      about_resource_load_count_(0),
      blocked_file_list_load_count_(0),
      start_page_token_load_count_(0),
      offline_(false),
      never_return_all_file_list_(false) {
  UpdateLatestChangelistId(largest_changestamp_, std::string());
  about_resource_->set_quota_bytes_total(9876543210);
  about_resource_->set_quota_bytes_used_aggregate(6789012345);
  about_resource_->set_root_folder_id(GetRootResourceId());
}

FakeDriveService::~FakeDriveService() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void FakeDriveService::AddTeamDrive(const std::string& id,
                                    const std::string& name) {
  AddTeamDrive(id, name, "");
}

void FakeDriveService::AddTeamDrive(const std::string& id,
                                    const std::string& name,
                                    const std::string& start_page_token) {
  DCHECK(entries_.find(id) == entries_.end());
  auto team_drive = std::make_unique<TeamDriveResource>();
  team_drive->set_id(id);
  team_drive->set_name(name);
  team_drive_value_.push_back(std::move(team_drive));

  team_drive_start_page_tokens_[id] = std::make_unique<StartPageToken>();
  team_drive_start_page_tokens_[id]->set_start_page_token(start_page_token);

  const EntryInfo* new_entry = AddNewTeamDriveEntry(id, name);
  DCHECK(new_entry);
}

void FakeDriveService::SetQuotaValue(int64_t used, int64_t total) {
  DCHECK(thread_checker_.CalledOnValidThread());

  about_resource_->set_quota_bytes_used_aggregate(used);
  about_resource_->set_quota_bytes_total(total);
}

void FakeDriveService::Initialize(const CoreAccountId& account_id) {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
  DCHECK(thread_checker_.CalledOnValidThread());
}

bool FakeDriveService::CanSendRequest() const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return true;
}

bool FakeDriveService::HasAccessToken() const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return true;
}

void FakeDriveService::RequestAccessToken(AuthStatusCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);
  std::move(callback).Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
}

bool FakeDriveService::HasRefreshToken() const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return true;
}

void FakeDriveService::ClearAccessToken() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void FakeDriveService::ClearRefreshToken() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

std::string FakeDriveService::GetRootResourceId() const {
  return "fake_root";
}

void FakeDriveService::GetTeamDriveListInternal(
    int start_offset,
    int max_results,
    int* load_counter,
    google_apis::TeamDriveListCallback callback) {
  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<TeamDriveList>()));
    return;
  }
  if (load_counter)
    ++*load_counter;

  // If max_results is zero then we want to return the entire list.
  if (max_results <= 0) {
    max_results = team_drive_value_.size();
  }

  auto result = std::make_unique<TeamDriveList>();
  size_t next_start_offset = start_offset + max_results;
  if (next_start_offset < team_drive_value_.size()) {
    // Embed next start offset to next page token to be read in
    // GetRemainingTeamDriveList next time.
    result->set_next_page_token(base::NumberToString(next_start_offset));
  }
  for (size_t i = start_offset;
       i < std::min(next_start_offset, team_drive_value_.size()); ++i) {
    std::unique_ptr<TeamDriveResource> team_drive(new TeamDriveResource);
    team_drive->set_id(team_drive_value_[i]->id());
    team_drive->set_name(team_drive_value_[i]->name());
    team_drive->set_capabilities(team_drive_value_[i]->capabilities());
    result->mutable_items()->push_back(std::move(team_drive));
  }
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), HTTP_SUCCESS, std::move(result)));
}

CancelCallbackOnce FakeDriveService::GetAllTeamDriveList(
    TeamDriveListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  GetTeamDriveListInternal(0, default_max_results_,
                           &team_drive_list_load_count_, std::move(callback));

  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetAllFileList(
    const std::string& team_drive_id,
    FileListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (never_return_all_file_list_) {
    ++blocked_file_list_load_count_;
    return CancelCallbackOnce();
  }

  GetChangeListInternal(
      0,              // start changestamp
      std::string(),  // empty search query
      std::string(),  // no directory resource id,
      team_drive_id,
      0,  // start offset
      default_max_results_, &file_list_load_count_,
      base::BindOnce(&FileListCallbackAdapter, std::move(callback)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetFileListInDirectory(
    const std::string& directory_resource_id,
    FileListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!directory_resource_id.empty());

  GetChangeListInternal(
      0,              // start changestamp
      std::string(),  // empty search query
      directory_resource_id,
      std::string(),  // empty team drive id.
      0,              // start offset
      default_max_results_, &directory_load_count_,
      base::BindOnce(&FileListCallbackAdapter, std::move(callback)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::Search(const std::string& search_query,
                                            FileListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!search_query.empty());

  GetChangeListInternal(
      0,  // start changestamp
      search_query,
      std::string(),  // no directory resource id,
      std::string(),  // empty team drive id.
      0,              // start offset
      default_max_results_, nullptr,
      base::BindOnce(&FileListCallbackAdapter, std::move(callback)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::SearchByTitle(
    const std::string& title,
    const std::string& directory_resource_id,
    FileListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!title.empty());

  // Note: the search implementation here doesn't support quotation unescape,
  // so don't escape here.
  GetChangeListInternal(
      0,  // start changestamp
      base::StringPrintf("title:'%s'", title.c_str()), directory_resource_id,
      std::string(),  // empty team drive id.
      0,              // start offset
      default_max_results_, nullptr,
      base::BindOnce(&FileListCallbackAdapter, std::move(callback)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetChangeList(
    int64_t start_changestamp,
    ChangeListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  GetChangeListInternal(start_changestamp,
                        std::string(),  // empty search query
                        std::string(),  // no directory resource id,
                        std::string(),  // empty team drive id.
                        0,              // start offset
                        default_max_results_, &change_list_load_count_,
                        std::move(callback));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetChangeListByToken(
    const std::string& team_drive_id,
    const std::string& start_page_token,
    ChangeListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  int64_t changestamp = 0;
  CHECK(base::StringToInt64(start_page_token, &changestamp));

  GetChangeListInternal(changestamp,
                        std::string(),  // empty search query
                        std::string(),  // no directory resource id,
                        team_drive_id,
                        0,  // start offset
                        default_max_results_, &change_list_load_count_,
                        std::move(callback));

  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetRemainingChangeList(
    const GURL& next_link,
    ChangeListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!next_link.is_empty());
  DCHECK(callback);

  // "changestamp", "q", "parent" and "start-offset" are parameters to
  // implement "paging" of the result on FakeDriveService.
  // The URL should be the one filled in GetChangeListInternal of the
  // previous method invocation, so it should start with "http://localhost/?".
  // See also GetChangeListInternal.
  DCHECK_EQ(next_link.GetHost(), "localhost");
  DCHECK_EQ(next_link.GetPath(), "/");

  int64_t start_changestamp = 0;
  std::string search_query;
  std::string directory_resource_id;
  std::string team_drive_id;
  int start_offset = 0;
  int max_results = default_max_results_;
  base::StringPairs parameters;
  if (base::SplitStringIntoKeyValuePairs(next_link.GetQuery(), '=', '&',
                                         &parameters)) {
    for (const auto& param : parameters) {
      if (param.first == "changestamp") {
        base::StringToInt64(param.second, &start_changestamp);
      } else if (param.first == "q") {
        search_query = base::UnescapeBinaryURLComponent(param.second);
      } else if (param.first == "parent") {
        directory_resource_id = base::UnescapeBinaryURLComponent(param.second);
      } else if (param.first == "team-drive-id") {
        team_drive_id = base::UnescapeBinaryURLComponent(param.second);
      } else if (param.first == "start-offset") {
        base::StringToInt(param.second, &start_offset);
      } else if (param.first == "max-results") {
        base::StringToInt(param.second, &max_results);
      }
    }
  }

  GetChangeListInternal(start_changestamp, search_query, directory_resource_id,
                        team_drive_id, start_offset, max_results, nullptr,
                        std::move(callback));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetRemainingTeamDriveList(
    const std::string& page_token,
    TeamDriveListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!page_token.empty());
  DCHECK(callback);

  // Next offset index to page token is embedded in the token.
  size_t start_offset;
  bool parse_success = base::StringToSizeT(page_token, &start_offset);
  DCHECK(parse_success);
  GetTeamDriveListInternal(start_offset, default_max_results_, nullptr,
                           std::move(callback));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetRemainingFileList(
    const GURL& next_link,
    FileListCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!next_link.is_empty());

  return GetRemainingChangeList(
      next_link, base::BindOnce(&FileListCallbackAdapter, std::move(callback)));
}

CancelCallbackOnce FakeDriveService::GetFileResource(
    const std::string& resource_id,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (entry && entry->change_resource.file()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                  std::make_unique<FileResource>(
                                      *entry->change_resource.file())));
    return CancelCallbackOnce();
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                std::unique_ptr<FileResource>()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetAboutResource(
    AboutResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    std::unique_ptr<AboutResource> null;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), NO_CONNECTION, std::move(null)));
    return CancelCallbackOnce();
  }

  ++about_resource_load_count_;
  std::unique_ptr<AboutResource> about_resource(
      new AboutResource(*about_resource_));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::move(about_resource)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetStartPageToken(
    const std::string& team_drive_id,
    google_apis::StartPageTokenCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    std::unique_ptr<StartPageToken> null;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), NO_CONNECTION, std::move(null)));
    return CancelCallbackOnce();
  }

  std::unique_ptr<StartPageToken> start_page_token;
  if (team_drive_id.empty()) {
    start_page_token = std::make_unique<StartPageToken>(*start_page_token_);
  } else {
    auto it = team_drive_start_page_tokens_.find(team_drive_id);
    CHECK(it != team_drive_start_page_tokens_.end());
    start_page_token = std::make_unique<StartPageToken>(*(it->second));
  }
  ++start_page_token_load_count_;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::move(start_page_token)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::DeleteResource(
    const std::string& resource_id,
    const std::string& etag,
    EntryActionCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  if (change->is_deleted()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  const FileResource* file = change->file();
  if (!etag.empty() && etag != file->etag()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_PRECONDITION));
    return CancelCallbackOnce();
  }

  if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_FORBIDDEN));
    return CancelCallbackOnce();
  }

  change->set_deleted(true);
  AddNewChangestamp(change, file->team_drive_id());
  change->set_file(std::unique_ptr<FileResource>());
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_NO_CONTENT));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::TrashResource(
    const std::string& resource_id,
    EntryActionCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  FileResource* file = change->mutable_file();
  if (change->is_deleted() || file->labels().is_trashed()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_FORBIDDEN));
    return CancelCallbackOnce();
  }

  file->mutable_labels()->set_trashed(true);
  AddNewChangestamp(change, file->team_drive_id());
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::DownloadFile(
    const base::FilePath& local_cache_path,
    const std::string& resource_id,
    DownloadActionCallback download_action_callback,
    const GetContentCallback& get_content_callback,
    ProgressCallback progress_callback) {
  base::ScopedAllowBlocking allow_blocking;
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!download_action_callback.is_null());

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(download_action_callback),
                                  NO_CONNECTION, base::FilePath()));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry || entry->change_resource.file()->IsHostedDocument()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(download_action_callback),
                                  HTTP_NOT_FOUND, base::FilePath()));
    return CancelCallbackOnce();
  }

  const FileResource* file = entry->change_resource.file();
  const std::string& content_data = entry->content_data;
  int64_t file_size = file->file_size();
  DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());

  if (!get_content_callback.is_null()) {
    const int64_t kBlockSize = 5;
    for (int64_t i = 0; i < file_size; i += kBlockSize) {
      const int64_t size = std::min(kBlockSize, file_size - i);
      std::unique_ptr<std::string> content_for_callback(
          new std::string(content_data.substr(i, size)));
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(get_content_callback, HTTP_SUCCESS,
                                    std::move(content_for_callback), i == 0));
    }
  }

  if (!test_util::WriteStringToFile(local_cache_path, content_data)) {
    // Failed to write the content.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(download_action_callback),
                                  DRIVE_FILE_ERROR, base::FilePath()));
    return CancelCallbackOnce();
  }

  if (!progress_callback.is_null()) {
    // See also the comment in ResumeUpload(). For testing that clients
    // can handle the case progress_callback is called multiple times,
    // here we invoke the callback twice.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(progress_callback, file_size / 2, file_size));
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(progress_callback, file_size, file_size));
  }
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(download_action_callback),
                                HTTP_SUCCESS, local_cache_path));
  return google_apis::CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::CopyResource(
    const std::string& resource_id,
    const std::string& in_parent_resource_id,
    const std::string& new_title,
    const base::Time& last_modified,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  const std::string& parent_resource_id = in_parent_resource_id.empty()
                                              ? GetRootResourceId()
                                              : in_parent_resource_id;

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  // Make a copy and set the new resource ID and the new title.
  std::unique_ptr<EntryInfo> copied_entry(new EntryInfo);
  copied_entry->content_data = entry->content_data;
  copied_entry->share_url = entry->share_url;
  copied_entry->change_resource.set_type(ChangeResource::FILE);
  copied_entry->change_resource.set_file(
      std::make_unique<FileResource>(*entry->change_resource.file()));

  ChangeResource* new_change = &copied_entry->change_resource;
  FileResource* new_file = new_change->mutable_file();
  const std::string new_resource_id = GetNewResourceId();
  new_change->set_file_id(new_resource_id);
  new_file->set_file_id(new_resource_id);
  new_file->set_title(new_title);

  ParentReference parent;
  parent.set_file_id(parent_resource_id);
  std::vector<ParentReference> parents;
  parents.push_back(parent);
  *new_file->mutable_parents() = parents;

  // Set the team drive for the new entry to the parent.
  if (entries_.count(parent_resource_id) > 0) {
    const ChangeResource& change =
        entries_[parent_resource_id]->change_resource;
    new_file->set_team_drive_id(GetTeamDriveId(change));
  }

  if (!last_modified.is_null()) {
    new_file->set_modified_date(last_modified);
    new_file->set_modified_by_me_date(last_modified);
  } else {
    auto now = base::Time::Now();
    new_file->set_modified_date(now);
    new_file->set_modified_by_me_date(now);
  }

  AddNewChangestamp(new_change, new_file->team_drive_id());
  UpdateETag(new_file);

  // Add the new entry to the map.
  entries_[new_resource_id] = std::move(copied_entry);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::make_unique<FileResource>(*new_file)));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::UpdateResource(
    const std::string& resource_id,
    const std::string& parent_resource_id,
    const std::string& new_title,
    const base::Time& last_modified,
    const base::Time& last_viewed_by_me,
    const google_apis::drive::Properties& properties,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  if (!UserHasWriteAccess(entry->user_permission)) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_FORBIDDEN,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  FileResource* file = change->mutable_file();

  if (!new_title.empty())
    file->set_title(new_title);

  // Set parent if necessary.
  if (!parent_resource_id.empty()) {
    ParentReference parent;
    parent.set_file_id(parent_resource_id);

    std::vector<ParentReference> parents;
    parents.push_back(parent);
    *file->mutable_parents() = parents;
  }

  if (!last_modified.is_null()) {
    file->set_modified_date(last_modified);
    file->set_modified_by_me_date(last_modified);
  }

  if (!last_viewed_by_me.is_null())
    file->set_last_viewed_by_me_date(last_viewed_by_me);

  AddNewChangestamp(change, file->team_drive_id());
  UpdateETag(file);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::make_unique<FileResource>(*file)));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::AddResourceToDirectory(
    const std::string& parent_resource_id,
    const std::string& resource_id,
    EntryActionCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  // On the real Drive server, resources do not necessary shape a tree
  // structure. That is, each resource can have multiple parent.
  // We mimic the behavior here; AddResourceToDirectoy just adds
  // one more parent, not overwriting old ones.
  ParentReference parent;
  parent.set_file_id(parent_resource_id);
  change->mutable_file()->mutable_parents()->push_back(parent);

  AddNewChangestamp(change, change->file()->team_drive_id());
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::RemoveResourceFromDirectory(
    const std::string& parent_resource_id,
    const std::string& resource_id,
    EntryActionCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  FileResource* file = change->mutable_file();
  std::vector<ParentReference>* parents = file->mutable_parents();
  for (size_t i = 0; i < parents->size(); ++i) {
    if ((*parents)[i].file_id() == parent_resource_id) {
      parents->erase(parents->begin() + i);
      AddNewChangestamp(change, file->team_drive_id());
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(callback), HTTP_NO_CONTENT));
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                    weak_ptr_factory_.GetWeakPtr()));
      return CancelCallbackOnce();
    }
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::AddNewDirectory(
    const std::string& parent_resource_id,
    const std::string& directory_title,
    const AddNewDirectoryOptions& options,
    FileResourceCallback callback) {
  return AddNewDirectoryWithResourceId(
      "", parent_resource_id.empty() ? GetRootResourceId() : parent_resource_id,
      directory_title, options, std::move(callback));
}

CancelCallbackOnce FakeDriveService::InitiateUploadNewFile(
    const std::string& content_type,
    int64_t content_length,
    const std::string& parent_resource_id,
    const std::string& title,
    const UploadNewFileOptions& options,
    InitiateUploadCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!callback.is_null());

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION, GURL()));
    return CancelCallbackOnce();
  }

  if (parent_resource_id != GetRootResourceId() &&
      !entries_.count(parent_resource_id)) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND, GURL()));
    return CancelCallbackOnce();
  }

  GURL session_url = GetNewUploadSessionUrl();
  upload_sessions_[session_url] =
      UploadSession(content_type, content_length, parent_resource_id,
                    "",  // resource_id
                    "",  // etag
                    title);

  if (title == "never-sync.txt") {
    return CancelCallbackOnce();
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), HTTP_SUCCESS, session_url));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::InitiateUploadExistingFile(
    const std::string& content_type,
    int64_t content_length,
    const std::string& resource_id,
    const UploadExistingFileOptions& options,
    InitiateUploadCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!callback.is_null());

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION, GURL()));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND, GURL()));
    return CancelCallbackOnce();
  }

  if (!UserHasWriteAccess(entry->user_permission)) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_FORBIDDEN, GURL()));
    return CancelCallbackOnce();
  }

  FileResource* file = entry->change_resource.mutable_file();
  if (!options.etag.empty() && options.etag != file->etag()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), HTTP_PRECONDITION, GURL()));
    return CancelCallbackOnce();
  }
  // TODO(hashimoto): Update |file|'s metadata with |options|.

  GURL session_url = GetNewUploadSessionUrl();
  upload_sessions_[session_url] =
      UploadSession(content_type, content_length,
                    "",  // parent_resource_id
                    resource_id, file->etag(), "" /* title */);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), HTTP_SUCCESS, session_url));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::GetUploadStatus(
    const GURL& upload_url,
    int64_t content_length,
    UploadRangeCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::ResumeUpload(
    const GURL& upload_url,
    int64_t start_position,
    int64_t end_position,
    int64_t content_length,
    const std::string& content_type,
    const base::FilePath& local_file_path,
    UploadRangeCallback callback,
    ProgressCallback progress_callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  FileResourceCallback completion_callback =
      base::BindOnce(&ScheduleUploadRangeCallback, std::move(callback),
                     start_position, end_position);

  if (offline_) {
    std::move(completion_callback)
        .Run(NO_CONNECTION, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  if (!upload_sessions_.count(upload_url)) {
    std::move(completion_callback)
        .Run(HTTP_NOT_FOUND, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  UploadSession* session = &upload_sessions_[upload_url];

  // Chunks are required to be sent in such a ways that they fill from the start
  // of the not-yet-uploaded part with no gaps nor overlaps.
  if (session->uploaded_size != start_position) {
    std::move(completion_callback)
        .Run(HTTP_BAD_REQUEST, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  if (!progress_callback.is_null()) {
    // In the real GDataWapi/Drive DriveService, progress is reported in
    // nondeterministic timing. In this fake implementation, we choose to call
    // it twice per one ResumeUpload. This is for making sure that client code
    // works fine even if the callback is invoked more than once; it is the
    // crucial difference of the progress callback from others.
    // Note that progress is notified in the relative offset in each chunk.
    const int64_t chunk_size = end_position - start_position;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(progress_callback, chunk_size / 2, chunk_size));
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(progress_callback, chunk_size, chunk_size));
  }

  if (content_length != end_position) {
    session->uploaded_size = end_position;
    std::move(completion_callback)
        .Run(HTTP_RESUME_INCOMPLETE, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  std::string content_data;
  {
    base::ScopedAllowBlockingForTesting allow_io;
    if (!base::ReadFileToString(local_file_path, &content_data)) {
      session->uploaded_size = end_position;
      std::move(completion_callback)
          .Run(DRIVE_FILE_ERROR, std::unique_ptr<FileResource>());
      return CancelCallbackOnce();
    }
  }
  session->uploaded_size = end_position;

  // |resource_id| is empty if the upload is for new file.
  if (session->resource_id.empty()) {
    DCHECK(!session->parent_resource_id.empty());
    DCHECK(!session->title.empty());
    const EntryInfo* new_entry =
        AddNewEntry("",  // auto generate resource id.
                    session->content_type, content_data,
                    session->parent_resource_id, session->title,
                    false);  // shared_with_me
    if (!new_entry) {
      std::move(completion_callback)
          .Run(HTTP_NOT_FOUND, std::unique_ptr<FileResource>());
      return CancelCallbackOnce();
    }

    std::move(completion_callback)
        .Run(HTTP_CREATED, std::make_unique<FileResource>(
                               *new_entry->change_resource.file()));
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                  weak_ptr_factory_.GetWeakPtr()));
    return CancelCallbackOnce();
  }

  EntryInfo* entry = FindEntryByResourceId(session->resource_id);
  if (!entry) {
    std::move(completion_callback)
        .Run(HTTP_NOT_FOUND, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  ChangeResource* change = &entry->change_resource;
  FileResource* file = change->mutable_file();
  if (file->etag().empty() || session->etag != file->etag()) {
    std::move(completion_callback)
        .Run(HTTP_PRECONDITION, std::unique_ptr<FileResource>());
    return CancelCallbackOnce();
  }

  file->set_md5_checksum(GetMd5Checksum(content_data));
  entry->content_data = content_data;
  file->set_file_size(end_position);
  AddNewChangestamp(change, file->team_drive_id());
  UpdateETag(file);

  std::move(completion_callback)
      .Run(HTTP_SUCCESS, std::make_unique<FileResource>(*file));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::MultipartUploadNewFile(
    const std::string& content_type,
    std::optional<std::string_view> converted_mime_type,
    int64_t content_length,
    const std::string& parent_resource_id,
    const std::string& title,
    const base::FilePath& local_file_path,
    const UploadNewFileOptions& options,
    FileResourceCallback callback,
    ProgressCallback progress_callback) {
  std::string destination_mime_type(converted_mime_type.value_or(content_type));

  CallResumeUpload* const call_resume_upload = new CallResumeUpload();
  call_resume_upload->service = weak_ptr_factory_.GetWeakPtr();
  call_resume_upload->content_type = destination_mime_type;
  call_resume_upload->content_length = content_length;
  call_resume_upload->local_file_path = local_file_path;
  call_resume_upload->callback = std::move(callback);
  call_resume_upload->progress_callback = progress_callback;
  InitiateUploadNewFile(
      destination_mime_type, content_length, parent_resource_id, title, options,
      base::BindOnce(&CallResumeUpload::Run, base::Owned(call_resume_upload)));
  return CancelCallbackOnce();
}

CancelCallbackOnce FakeDriveService::MultipartUploadExistingFile(
    const std::string& content_type,
    int64_t content_length,
    const std::string& resource_id,
    const base::FilePath& local_file_path,
    const UploadExistingFileOptions& options,
    FileResourceCallback callback,
    ProgressCallback progress_callback) {
  CallResumeUpload* const call_resume_upload = new CallResumeUpload();
  call_resume_upload->service = weak_ptr_factory_.GetWeakPtr();
  call_resume_upload->content_type = content_type;
  call_resume_upload->content_length = content_length;
  call_resume_upload->local_file_path = local_file_path;
  call_resume_upload->callback = std::move(callback);
  call_resume_upload->progress_callback = progress_callback;
  InitiateUploadExistingFile(
      content_type, content_length, resource_id, options,
      base::BindOnce(&CallResumeUpload::Run, base::Owned(call_resume_upload)));
  return CancelCallbackOnce();
}

void FakeDriveService::AddNewFile(const std::string& content_type,
                                  const std::string& content_data,
                                  const std::string& parent_resource_id,
                                  const std::string& title,
                                  bool shared_with_me,
                                  FileResourceCallback callback) {
  AddNewFileWithResourceId("", content_type, content_data, parent_resource_id,
                           title, shared_with_me, std::move(callback));
}

void FakeDriveService::AddNewFileWithResourceId(
    const std::string& resource_id,
    const std::string& content_type,
    const std::string& content_data,
    const std::string& parent_resource_id,
    const std::string& title,
    bool shared_with_me,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  const EntryInfo* new_entry =
      AddNewEntry(resource_id, content_type, content_data, parent_resource_id,
                  title, shared_with_me);
  if (!new_entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_CREATED,
                                std::make_unique<FileResource>(
                                    *new_entry->change_resource.file())));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
}

CancelCallbackOnce FakeDriveService::AddNewDirectoryWithResourceId(
    const std::string& resource_id,
    const std::string& parent_resource_id,
    const std::string& directory_title,
    const AddNewDirectoryOptions& options,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  const EntryInfo* new_entry =
      AddNewEntry(resource_id, util::kDriveFolderMimeType,
                  "",  // content_data
                  parent_resource_id, directory_title,
                  false);  // shared_with_me
  if (!new_entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return CancelCallbackOnce();
  }

  const google_apis::ApiErrorCode result = SetFileVisibility(
      new_entry->change_resource.file_id(), options.visibility);
  DCHECK_EQ(HTTP_SUCCESS, result);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_CREATED,
                                std::make_unique<FileResource>(
                                    *new_entry->change_resource.file())));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeDriveService::NotifyObservers,
                                weak_ptr_factory_.GetWeakPtr()));
  return CancelCallbackOnce();
}

void FakeDriveService::SetLastModifiedTime(const std::string& resource_id,
                                           const base::Time& last_modified_time,
                                           FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  ChangeResource* change = &entry->change_resource;
  FileResource* file = change->mutable_file();
  file->set_modified_date(last_modified_time);
  file->set_modified_by_me_date(last_modified_time);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::make_unique<FileResource>(*file)));
}

void FakeDriveService::SetFileCapabilities(
    const std::string& resource_id,
    const google_apis::FileResourceCapabilities& capabilities,
    FileResourceCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), HTTP_NOT_FOUND,
                                  std::unique_ptr<FileResource>()));
    return;
  }

  ChangeResource& change = entry->change_resource;
  FileResource* file = change.mutable_file();
  file->set_capabilities(capabilities);

  AddNewChangestamp(&change, file->team_drive_id());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::make_unique<FileResource>(*file)));
}

bool FakeDriveService::SetTeamDriveCapabilities(
    const std::string& team_drive_id,
    const google_apis::TeamDriveCapabilities& capabilities) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (offline_) {
    return false;
  }

  EntryInfo* entry = FindEntryByResourceId(team_drive_id);
  if (!entry) {
    return false;
  }

  for (size_t i = 0; i < team_drive_value_.size(); ++i) {
    std::unique_ptr<TeamDriveResource> team_drive(new TeamDriveResource);
    if (team_drive_value_[i]->id() == team_drive_id) {
      team_drive_value_[i]->set_capabilities(capabilities);
    }
  }

  ChangeResource& change = entry->change_resource;
  DCHECK_EQ(ChangeResource::TEAM_DRIVE, change.type());
  TeamDriveResource* team_drive = change.mutable_team_drive();
  team_drive->set_capabilities(capabilities);

  // Changes to Team Drives are added to the default changelist.
  AddNewChangestamp(&change, std::string());
  return true;
}

google_apis::ApiErrorCode FakeDriveService::SetUserPermission(
    const std::string& resource_id,
    google_apis::drive::PermissionRole user_permission) {
  DCHECK(thread_checker_.CalledOnValidThread());

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry)
    return HTTP_NOT_FOUND;

  entry->user_permission = user_permission;
  return HTTP_SUCCESS;
}

google_apis::ApiErrorCode FakeDriveService::SetFileVisibility(
    const std::string& resource_id,
    google_apis::drive::FileVisibility visibility) {
  DCHECK(thread_checker_.CalledOnValidThread());

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry)
    return HTTP_NOT_FOUND;

  entry->visibility = visibility;
  return HTTP_SUCCESS;
}

google_apis::ApiErrorCode FakeDriveService::GetFileVisibility(
    const std::string& resource_id,
    google_apis::drive::FileVisibility* visibility) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(visibility);

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry)
    return HTTP_NOT_FOUND;

  *visibility = entry->visibility;
  return HTTP_SUCCESS;
}

google_apis::ApiErrorCode FakeDriveService::SetFileAsSharedWithMe(
    const std::string& resource_id) {
  DCHECK(thread_checker_.CalledOnValidThread());

  EntryInfo* entry = FindEntryByResourceId(resource_id);
  if (!entry)
    return HTTP_NOT_FOUND;

  entry->change_resource.mutable_file()->set_shared_with_me_date(
      base::Time::Now());
  return HTTP_SUCCESS;
}

void FakeDriveService::AddChangeObserver(ChangeObserver* change_observer) {
  change_observers_.AddObserver(change_observer);
}

void FakeDriveService::RemoveChangeObserver(ChangeObserver* change_observer) {
  change_observers_.RemoveObserver(change_observer);
}

FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
    const std::string& resource_id) {
  DCHECK(thread_checker_.CalledOnValidThread());

  auto it = entries_.find(resource_id);
  if (it == entries_.end()) {
    return nullptr;
  }

  // Deleted entries don't have FileResource.
  if (it->second->change_resource.type() != ChangeResource::TEAM_DRIVE &&
      !it->second->change_resource.file()) {
    return nullptr;
  }

  return it->second.get();
}

std::string FakeDriveService::GetNewResourceId() {
  DCHECK(thread_checker_.CalledOnValidThread());

  ++resource_id_count_;
  return base::StringPrintf("resource_id_%d", resource_id_count_);
}

void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
  file->set_etag("etag_" + start_page_token_->start_page_token());
}

void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change,
                                         const std::string& team_drive_id) {
  ++largest_changestamp_;
  UpdateLatestChangelistId(largest_changestamp_, team_drive_id);
  change->set_change_id(largest_changestamp_);
}

const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
    const std::string& given_resource_id,
    const std::string& content_type,
    const std::string& content_data,
    const std::string& parent_resource_id,
    const std::string& title,
    bool shared_with_me) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (!parent_resource_id.empty() &&
      parent_resource_id != GetRootResourceId() &&
      !entries_.count(parent_resource_id)) {
    return nullptr;
  }

  // Extract the team drive id from the parent, if there is one.
  std::string team_drive_id;
  if (!parent_resource_id.empty() && entries_.count(parent_resource_id) > 0) {
    const ChangeResource& resource =
        entries_[parent_resource_id]->change_resource;
    team_drive_id = GetTeamDriveId(resource);
  }

  const std::string resource_id =
      given_resource_id.empty() ? GetNewResourceId() : given_resource_id;
  if (entries_.count(resource_id))
    return nullptr;
  GURL upload_url = GURL("https://xxx/upload/" + resource_id);

  std::unique_ptr<EntryInfo> new_entry = std::make_unique<EntryInfo>();

  ChangeResource* new_change = &new_entry->change_resource;
  new_change->set_type(ChangeResource::FILE);
  new_change->set_team_drive_id(team_drive_id);

  FileResource* new_file = new FileResource;
  new_change->set_file(base::WrapUnique(new_file));

  // Set the resource ID and the title
  new_change->set_file_id(resource_id);
  new_file->set_file_id(resource_id);
  new_file->set_title(title);
  // Set the contents, size and MD5 for a file.
  if (content_type != util::kDriveFolderMimeType &&
      !util::IsKnownHostedDocumentMimeType(content_type)) {
    new_entry->content_data = content_data;
    new_file->set_file_size(content_data.size());
    new_file->set_md5_checksum(GetMd5Checksum(content_data));
  }

  if (shared_with_me) {
    // Set current time to mark the file as shared_with_me.
    new_file->set_shared_with_me_date(base::Time::Now());
  }

  std::string escaped_resource_id = base::EscapePath(resource_id);

  // Set mime type.
  new_file->set_mime_type(content_type);

  // Set the team drive id.
  new_file->set_team_drive_id(team_drive_id);

  // Set alternate link.
  if (content_type == util::kGoogleDocumentMimeType) {
    new_file->set_alternate_link(
        GURL("https://document_alternate_link/" + title));
  } else if (content_type == util::kDriveFolderMimeType) {
    new_file->set_alternate_link(
        GURL("https://folder_alternate_link/" + title));
  } else {
    new_file->set_alternate_link(GURL("https://file_alternate_link/" + title));
  }

  // Set parents.
  if (!parent_resource_id.empty()) {
    ParentReference parent;
    parent.set_file_id(parent_resource_id);
    std::vector<ParentReference> parents;
    parents.push_back(parent);
    *new_file->mutable_parents() = parents;
  }

  AddNewChangestamp(new_change, team_drive_id);
  UpdateETag(new_file);

  new_file->set_created_date(base::Time() + base::Milliseconds(++date_seq_));
  new_file->set_modified_by_me_date(base::Time() +
                                    base::Milliseconds(++date_seq_));
  new_file->set_modified_date(base::Time() + base::Milliseconds(++date_seq_));

  EntryInfo* raw_new_entry = new_entry.get();
  entries_[resource_id] = std::move(new_entry);
  return raw_new_entry;
}

const FakeDriveService::EntryInfo* FakeDriveService::AddNewTeamDriveEntry(
    const std::string& team_drive_id,
    const std::string& team_drive_name) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // Check we do not already have this entry.
  if (entries_.count(team_drive_id) > 0) {
    return nullptr;
  }

  std::unique_ptr<EntryInfo> new_entry = std::make_unique<EntryInfo>();

  ChangeResource& change = new_entry->change_resource;
  change.set_type(ChangeResource::TEAM_DRIVE);
  change.set_team_drive_id(team_drive_id);

  std::unique_ptr<TeamDriveResource> team_drive =
      std::make_unique<TeamDriveResource>();
  team_drive->set_id(team_drive_id);
  team_drive->set_name(team_drive_name);
  change.set_team_drive(std::move(team_drive));

  AddNewChangestamp(&change, std::string());

  change.set_modification_date(base::Time() + base::Milliseconds(++date_seq_));

  EntryInfo* raw_new_entry = new_entry.get();
  entries_[team_drive_id] = std::move(new_entry);
  return raw_new_entry;
}

void FakeDriveService::GetChangeListInternal(
    int64_t start_changestamp,
    const std::string& search_query,
    const std::string& directory_resource_id,
    const std::string& team_drive_id,
    int start_offset,
    int max_results,
    int* load_counter,
    ChangeListOnceCallback callback) {
  if (offline_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), NO_CONNECTION,
                                  std::unique_ptr<ChangeList>()));
    return;
  }

  // Filter out entries per parameters like |directory_resource_id| and
  // |search_query|.
  std::vector<std::unique_ptr<ChangeResource>> entries;
  int num_entries_matched = 0;
  for (auto it = entries_.begin(); it != entries_.end(); ++it) {
    const ChangeResource& entry = it->second->change_resource;
    bool should_exclude = false;

    // If |directory_resource_id| is set, exclude the entry if it's not in
    // the target directory.
    if (!directory_resource_id.empty()) {
      // Get the parent resource ID of the entry.
      std::string parent_resource_id;
      if (entry.type() == ChangeResource::FILE && entry.file() &&
          !entry.file()->parents().empty())
        parent_resource_id = entry.file()->parents()[0].file_id();

      if (directory_resource_id != parent_resource_id)
        should_exclude = true;
    }

    // If |search_query| is set, exclude the entry if it does not contain the
    // search query in the title.
    if (!should_exclude && !search_query.empty() &&
        !EntryMatchWithQuery(entry, search_query)) {
      should_exclude = true;
    }

    // If the team drive does not match, then exclude the entry.
    switch (entry.type()) {
      case ChangeResource::FILE:
        if (entry.file() && entry.file()->team_drive_id() != team_drive_id) {
          should_exclude = true;
        }
        break;
      case ChangeResource::TEAM_DRIVE:
        // Only include TeamDrive change resources in the default change list.
        if (!team_drive_id.empty()) {
          should_exclude = true;
        }
        break;
      case ChangeResource::UNKNOWN:
        NOTREACHED();
    }

    // If |start_changestamp| is set, exclude the entry if the
    // changestamp is older than |largest_changestamp|.
    // See https://developers.google.com/google-apps/documents-list/
    // #retrieving_all_changes_since_a_given_changestamp
    if (start_changestamp > 0 && entry.change_id() < start_changestamp)
      should_exclude = true;

    // If the caller requests other list than change list by specifying
    // zero-|start_changestamp|, exclude deleted entry from the result.
    const bool deleted = entry.is_deleted() ||
                         (entry.type() == ChangeResource::FILE &&
                          entry.file() && entry.file()->labels().is_trashed());
    if (!start_changestamp && deleted)
      should_exclude = true;

    // The entry matched the criteria for inclusion.
    if (!should_exclude)
      ++num_entries_matched;

    // If |start_offset| is set, exclude the entry if the entry is before the
    // start index. <= instead of < as |num_entries_matched| was
    // already incremented.
    if (start_offset > 0 && num_entries_matched <= start_offset)
      should_exclude = true;

    if (!should_exclude) {
      std::unique_ptr<ChangeResource> entry_copied(new ChangeResource);
      entry_copied->set_type(entry.type());
      entry_copied->set_change_id(entry.change_id());
      entry_copied->set_deleted(entry.is_deleted());
      if (entry.type() == ChangeResource::FILE) {
        entry_copied->set_file_id(entry.file_id());
        if (entry.file()) {
          entry_copied->set_file(std::make_unique<FileResource>(*entry.file()));
        }
      }
      if (entry.type() == ChangeResource::TEAM_DRIVE && entry.team_drive()) {
        entry_copied->set_team_drive(
            std::make_unique<TeamDriveResource>(*entry.team_drive()));
      }
      entry_copied->set_modification_date(entry.modification_date());
      entries.push_back(std::move(entry_copied));
    }
  }

  std::unique_ptr<ChangeList> change_list(new ChangeList);
  if (start_changestamp > 0 && start_offset == 0) {
    auto largest_change_id = about_resource_->largest_change_id();
    change_list->set_largest_change_id(largest_change_id);
    change_list->set_new_start_page_token(
        drive::util::ConvertChangestampToStartPageToken(largest_change_id));
  }

  // If |max_results| is set, trim the entries if the number exceeded the max
  // results.
  if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
    entries.erase(entries.begin() + max_results, entries.end());
    // Adds the next URL.
    // Here, we embed information which is needed for continuing the
    // GetChangeList request in the next invocation into url query
    // parameters.
    GURL next_url(
        base::StringPrintf("http://localhost/?start-offset=%d&max-results=%d",
                           start_offset + max_results, max_results));
    if (start_changestamp > 0) {
      next_url = net::AppendOrReplaceQueryParameter(
          next_url, "changestamp", base::NumberToString(start_changestamp));
    }
    if (!search_query.empty()) {
      next_url =
          net::AppendOrReplaceQueryParameter(next_url, "q", search_query);
    }
    if (!directory_resource_id.empty()) {
      next_url = net::AppendOrReplaceQueryParameter(next_url, "parent",
                                                    directory_resource_id);
    }
    if (!team_drive_id.empty()) {
      next_url = net::AppendOrReplaceQueryParameter(next_url, "team-drive-id",
                                                    team_drive_id);
    }

    change_list->set_next_link(next_url);
  }
  *change_list->mutable_items() = std::move(entries);

  if (load_counter)
    *load_counter += 1;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), HTTP_SUCCESS,
                                std::move(change_list)));
}

GURL FakeDriveService::GetNewUploadSessionUrl() {
  return GURL("https://upload_session_url/" +
              base::NumberToString(next_upload_sequence_number_++));
}

CancelCallbackOnce FakeDriveService::AddPermission(
    const std::string& resource_id,
    const std::string& email,
    google_apis::drive::PermissionRole role,
    google_apis::EntryActionCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(callback);

  NOTREACHED();
}

std::unique_ptr<BatchRequestConfiguratorInterface>
FakeDriveService::StartBatchRequest() {
  DCHECK(thread_checker_.CalledOnValidThread());

  NOTREACHED();
}

void FakeDriveService::NotifyObservers() {
  for (auto& observer : change_observers_)
    observer.OnNewChangeAvailable();
}

void FakeDriveService::UpdateLatestChangelistId(
    int64_t change_list_id,
    const std::string& team_drive_id) {
  if (team_drive_id.empty()) {
    about_resource_->set_largest_change_id(change_list_id);
    start_page_token_->set_start_page_token(
        drive::util::ConvertChangestampToStartPageToken(change_list_id));
  } else {
    DCHECK_GT(team_drive_start_page_tokens_.count(team_drive_id), 0UL);
    team_drive_start_page_tokens_[team_drive_id]->set_start_page_token(
        drive::util::ConvertChangestampToStartPageToken(change_list_id));
  }
}

}  // namespace drive