chromium/chrome/browser/ash/extensions/file_manager/private_api_dialog.cc

// Copyright 2013 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/extensions/file_manager/private_api_dialog.h"

#include <stddef.h>

#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/bind_post_task.h"
#include "chrome/browser/ash/arc/fileapi/arc_select_files_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/extensions/file_manager/private_api_util.h"
#include "chrome/browser/ash/extensions/file_manager/select_file_dialog_extension_user_data.h"
#include "chrome/browser/ash/file_manager/file_tasks_notifier.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/filesystem_api_util.h"
#include "chrome/browser/ash/file_manager/office_file_tasks.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/select_file_dialog_extension.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/mime_util.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "ui/shell_dialogs/selected_file_info.h"

using content::BrowserThread;

namespace extensions {

namespace {

// Computes the routing ID for SelectFileDialogExtension from the |function|.
SelectFileDialogExtension::RoutingID GetFileDialogRoutingID(
    ExtensionFunction* function) {
  return SelectFileDialogExtensionUserData::GetRoutingIdForWebContents(
      function->GetSenderWebContents());
}

}  // namespace

ExtensionFunction::ResponseAction
FileManagerPrivateCancelDialogFunction::Run() {
  SelectFileDialogExtension::OnFileSelectionCanceled(
      GetFileDialogRoutingID(this));
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction FileManagerPrivateSelectFileFunction::Run() {
  using extensions::api::file_manager_private::SelectFile::Params;
  const std::optional<Params> params = Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  Profile* profile = Profile::FromBrowserContext(browser_context());
  base::FilePath local_path = file_manager::util::GetLocalPathFromURL(
      file_manager::util::GetFileSystemContextForRenderFrameHost(
          profile, render_frame_host()),
      GURL(params->selected_path));
  if (local_path.empty()) {
    return RespondNow(Error("Path not supported"));
  }

  file_manager::util::GetSelectedFileInfoLocalPathOption option =
      file_manager::util::NO_LOCAL_PATH_RESOLUTION;
  if (params->should_return_local_path) {
    option = params->for_opening
                 ? file_manager::util::NEED_LOCAL_PATH_FOR_OPENING
                 : file_manager::util::NEED_LOCAL_PATH_FOR_SAVING;
  }

  if (file_manager::util::IsDriveLocalPath(profile, local_path) &&
      file_manager::file_tasks::IsOfficeFile(local_path) &&
      params->for_opening) {
    UMA_HISTOGRAM_ENUMERATION(
        file_manager::file_tasks::kUseOutsideDriveMetricName,
        file_manager::file_tasks::OfficeFilesUseOutsideDriveHook::
            FILE_PICKER_SELECTION);
    if (auto* drive_service =
            drive::util::GetIntegrationServiceByProfile(profile)) {
      drive_service->ForceReSyncFile(
          local_path,
          base::BindOnce(
              &file_manager::util::GetSelectedFileInfo, profile,
              std::vector({local_path}), option,
              base::BindOnce(&FileManagerPrivateSelectFileFunction::
                                 GetSelectedFileInfoResponse,
                             this, params->for_opening, params->index)));
      return RespondLater();
    }
  }

  file_manager::util::GetSelectedFileInfo(
      profile, {local_path}, option,
      base::BindOnce(
          &FileManagerPrivateSelectFileFunction::GetSelectedFileInfoResponse,
          this, params->for_opening, params->index));
  return RespondLater();
}

void FileManagerPrivateSelectFileFunction::GetSelectedFileInfoResponse(
    bool for_open,
    int index,
    const std::vector<ui::SelectedFileInfo>& files) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (files.size() != 1) {
    Respond(Error("No file selected"));
    return;
  }
  SelectFileDialogExtension::OnFileSelected(GetFileDialogRoutingID(this),
                                            files[0], index);
  if (auto* notifier =
          file_manager::file_tasks::FileTasksNotifier::GetForProfile(
              Profile::FromBrowserContext(browser_context()))) {
    notifier->NotifyFileDialogSelection({files[0]}, for_open);
  }
  Respond(NoArguments());
}

FileManagerPrivateSelectFilesFunction::FileManagerPrivateSelectFilesFunction() =
    default;
FileManagerPrivateSelectFilesFunction::
    ~FileManagerPrivateSelectFilesFunction() = default;

ExtensionFunction::ResponseAction FileManagerPrivateSelectFilesFunction::Run() {
  using extensions::api::file_manager_private::SelectFiles::Params;
  const std::optional<Params> params = Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  should_return_local_path_ = params->should_return_local_path;

  Profile* const profile = Profile::FromBrowserContext(browser_context());

  std::vector<base::FilePath> local_paths;
  auto* drive_service = drive::util::GetIntegrationServiceByProfile(profile);
  for (const auto& selected_path : params->selected_paths) {
    base::FilePath local_path = file_manager::util::GetLocalPathFromURL(
        file_manager::util::GetFileSystemContextForRenderFrameHost(
            profile, render_frame_host()),
        GURL(selected_path));
    if (local_path.empty()) {
      continue;
    }

    if (drive_service &&
        file_manager::util::IsDriveLocalPath(profile, local_path) &&
        file_manager::file_tasks::IsOfficeFile(local_path)) {
      UMA_HISTOGRAM_ENUMERATION(
          file_manager::file_tasks::kUseOutsideDriveMetricName,
          file_manager::file_tasks::OfficeFilesUseOutsideDriveHook::
              FILE_PICKER_SELECTION);
      ++resync_files_remaining_;
      // ForceReSyncFile may call its callback synchronously, so BindPostTask
      // the callback avoid that.
      drive_service->ForceReSyncFile(
          local_path,
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &FileManagerPrivateSelectFilesFunction::OnReSyncFile, this)));
    }
    local_paths.push_back(std::move(local_path));
  }
  if (resync_files_remaining_ > 0) {
    local_paths_for_resync_callback_ = std::move(local_paths);
    return RespondLater();
  }

  file_manager::util::GetSelectedFileInfo(
      Profile::FromBrowserContext(browser_context()), std::move(local_paths),
      params->should_return_local_path
          ? file_manager::util::NEED_LOCAL_PATH_FOR_OPENING
          : file_manager::util::NO_LOCAL_PATH_RESOLUTION,
      base::BindOnce(
          &FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse,
          this, true));
  return RespondLater();
}

void FileManagerPrivateSelectFilesFunction::OnReSyncFile() {
  DCHECK(resync_files_remaining_ > 0);
  if (--resync_files_remaining_ > 0) {
    return;
  }
  file_manager::util::GetSelectedFileInfo(
      Profile::FromBrowserContext(browser_context()),
      std::move(local_paths_for_resync_callback_),
      should_return_local_path_
          ? file_manager::util::NEED_LOCAL_PATH_FOR_OPENING
          : file_manager::util::NO_LOCAL_PATH_RESOLUTION,
      base::BindOnce(
          &FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse,
          this, true));
}

void FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse(
    bool for_open,
    const std::vector<ui::SelectedFileInfo>& files) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (files.empty()) {
    Respond(Error("No files selected"));
    return;
  }

  SelectFileDialogExtension::OnMultiFilesSelected(GetFileDialogRoutingID(this),
                                                  files);
  if (auto* notifier =
          file_manager::file_tasks::FileTasksNotifier::GetForProfile(
              Profile::FromBrowserContext(browser_context()))) {
    notifier->NotifyFileDialogSelection(files, for_open);
  }
  Respond(NoArguments());
}

ExtensionFunction::ResponseAction
FileManagerPrivateGetAndroidPickerAppsFunction::Run() {
  using extensions::api::file_manager_private::GetAndroidPickerApps::Params;
  const std::optional<Params> params = Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  auto* intent_helper = ARC_GET_INSTANCE_FOR_METHOD(
      arc::ArcServiceManager::Get()->arc_bridge_service()->intent_helper(),
      RequestIntentHandlerList);
  if (!intent_helper) {
    return RespondNow(Error("Can't get ARC intent helper"));
  }

  arc::mojom::IntentInfoPtr intent = arc::mojom::IntentInfo::New();
  intent->action = "android.intent.action.GET_CONTENT";
  std::vector<std::string> categories;
  categories.push_back("android.intent.category.OPENABLE");
  intent->categories = categories;

  // Convert extensions to MIME
  std::vector<std::string> mime_types;
  for (const std::string& extension : params->extensions) {
    std::string mime_type;
    if (net::GetMimeTypeFromExtension(extension, &mime_type)) {
      mime_types.push_back(mime_type);
    } else {
      LOG(ERROR) << "Failed to get MIME type for: " << extension;
    }
  }
  if (mime_types.size() == 1) {
    intent->type = mime_types[0];
  } else {
    intent->type = "*/*";
    intent->extras = base::flat_map<std::string, std::string>();
    for (const std::string& mime_type : mime_types) {
      intent->extras.value().insert(
          std::make_pair("android.intent.extra.MIME_TYPES", mime_type));
    }
  }

  intent_helper->RequestIntentHandlerList(
      std::move(intent),
      base::BindOnce(
          &FileManagerPrivateGetAndroidPickerAppsFunction::OnActivitiesLoaded,
          this));
  return RespondLater();
}

void FileManagerPrivateGetAndroidPickerAppsFunction::OnActivitiesLoaded(
    std::vector<arc::mojom::IntentHandlerInfoPtr> handlers) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  Profile* profile = Profile::FromBrowserContext(browser_context());
  auto* intent_helper =
      arc::ArcIntentHelperBridge::GetForBrowserContext(profile);
  std::vector<arc::ArcIntentHelperBridge::ActivityName> activity_names;
  for (const auto& handler : handlers) {
    activity_names.emplace_back(handler->package_name, handler->activity_name);
  }
  intent_helper->GetActivityIcons(
      activity_names,
      base::BindOnce(
          &FileManagerPrivateGetAndroidPickerAppsFunction::OnIconsLoaded, this,
          std::move(handlers)));
}

void FileManagerPrivateGetAndroidPickerAppsFunction::OnIconsLoaded(
    std::vector<arc::mojom::IntentHandlerInfoPtr> handlers,
    std::unique_ptr<arc::ArcIntentHelperBridge::ActivityToIconsMap> icons) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  using api::file_manager_private::FileTask;
  std::vector<api::file_manager_private::AndroidApp> results;
  for (const auto& handler : handlers) {
    if (arc::IsPickerPackageToExclude(handler->package_name)) {
      continue;
    }

    api::file_manager_private::AndroidApp app;
    app.name = handler->name;
    app.package_name = handler->package_name;
    app.activity_name = handler->activity_name;
    auto it = icons->find(arc::ArcIntentHelperBridge::ActivityName(
        handler->package_name, handler->activity_name));
    if (it != icons->end()) {
      app.icon_set.emplace();
      app.icon_set->icon32x32_url = it->second.icon16_dataurl->data.spec();
    }
    results.push_back(std::move(app));
  }
  Respond(ArgumentList(extensions::api::file_manager_private::
                           GetAndroidPickerApps::Results::Create(results)));
}

ExtensionFunction::ResponseAction
FileManagerPrivateSelectAndroidPickerAppFunction::Run() {
  using extensions::api::file_manager_private::SelectAndroidPickerApp::Params;
  const std::optional<Params> params = Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  // Though the user didn't select an actual file, we generate a virtual file
  // path that represents the selected picker app and pass it back to the dialog
  // listener with OnFileSelected function.
  ui::SelectedFileInfo file;
  file.file_path = arc::ConvertAndroidActivityToFilePath(
      params->android_app.package_name, params->android_app.activity_name);
  SelectFileDialogExtension::OnFileSelected(GetFileDialogRoutingID(this), file,
                                            0);
  return RespondNow(NoArguments());
}

}  // namespace extensions