chromium/chrome/browser/ui/ash/projector/screencast_manager.cc

// Copyright 2022 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/ui/ash/projector/screencast_manager.h"

#include <memory>
#include <vector>

#include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
#include "ash/webui/projector_app/public/mojom/projector_types.mojom.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/extensions/file_manager/scoped_suppress_drive_notifications_for_path.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/projector/projector_drivefs_provider.h"
#include "chrome/browser/ui/ash/projector/projector_utils.h"
#include "chrome/services/media_gallery_util/public/cpp/local_media_data_source_factory.h"
#include "chrome/services/media_gallery_util/public/cpp/safe_media_metadata_parser.h"
#include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"

namespace ash {

namespace {

constexpr int kOneSecondInMillisecond = 1000;

void OnMediaMetadataParsed(
    projector::mojom::VideoInfoPtr video,
    ProjectorAppClient::OnGetVideoCallback callback,
    const base::FilePath& video_path,
    std::unique_ptr<SafeMediaMetadataParser> parser_keep_alive,
    bool parse_success,
    chrome::mojom::MediaMetadataPtr metadata,
    std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  if (!parse_success || !metadata || !metadata->duration) {
    // TODO(b/242748137): Add test to cover this error message.
    std::move(callback).Run(
        projector::mojom::GetVideoResult::NewErrorMessage(base::StringPrintf(
            "Failed get media metadata or duration with video file id=%s",
            video->file_id.c_str())));
    return;
  }

  if (metadata->duration < 0) {
    // The video file might be malformed if duration is -1.
    std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage(
        base::StringPrintf("Media might be malformed with video file id=%s",
                           video->file_id.c_str())));
    return;
  }

  video->duration_millis = metadata->duration * kOneSecondInMillisecond;
  // Launches app on UI thread when duration is valid.

  // Even though the video file id is not a file path, we need to pass it to the
  // launch event to match up with the original request.
  base::FilePath video_id_as_path(video->file_id);
  SendFilesToProjectorApp({video_id_as_path, video_path});
  std::move(callback).Run(
      projector::mojom::GetVideoResult::NewVideo(std::move(video)));
}

// Caller of this method requires a sequenced context. Gets video metadata for
// `video_path`, triggers the flow to set `duration_millis` in the given video
// object and triggers the callback. Should not be called on UI thread.
void GetVideoMetadata(const base::FilePath& video_path,
                      projector::mojom::VideoInfoPtr video,
                      ProjectorAppClient::OnGetVideoCallback callback) {
  DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  int64_t size_in_byte;
  if (!base::PathExists(video_path) ||
      !base::GetFileSize(video_path, &size_in_byte)) {
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            std::move(callback),
            projector::mojom::GetVideoResult::NewErrorMessage(
                base::StringPrintf(
                    "Path does not exist or cannot read video size with "
                    "video file id=%s",
                    video->file_id.c_str()))));
    return;
  }

  auto parser = std::make_unique<SafeMediaMetadataParser>(
      size_in_byte, kProjectorMediaMimeType,
      /*get_attached_images=*/false,
      std::make_unique<LocalMediaDataSourceFactory>(
          video_path, base::SingleThreadTaskRunner::GetCurrentDefault()));

  // Uses raw pointer since the `parser` is moved before calling Start().
  SafeMediaMetadataParser* parser_ptr = parser.get();
  parser_ptr->Start(base::BindPostTask(
      content::GetUIThreadTaskRunner({}),
      base::BindOnce(&OnMediaMetadataParsed, std::move(video),
                     std::move(callback), video_path, std::move(parser))));
}

}  // namespace

// CreateSingleThreadTaskRunner for `video_metadata_task_runner` since
// LocalMediaDataSource requires a single thread context.
ScreencastManager::ScreencastManager()
    : video_metadata_task_runner_(
          base::ThreadPool::CreateSingleThreadTaskRunner({base::MayBlock()})) {}
ScreencastManager::~ScreencastManager() = default;

void ScreencastManager::GetVideo(
    const std::string& video_file_id,
    const std::optional<std::string>& resource_key,
    ProjectorAppClient::OnGetVideoCallback callback) const {
  // TODO(b/237089852): Handle the resource key once LocateFilesByItemIds()
  // supports it.

  drive::DriveIntegrationService* integration_service =
      ProjectorDriveFsProvider::GetActiveDriveIntegrationService();
  integration_service->LocateFilesByItemIds(
      {video_file_id},
      base::BindOnce(&ScreencastManager::OnVideoFilePathLocated,
                     weak_ptr_factory_.GetMutableWeakPtr(), video_file_id,
                     std::move(callback)));
}

void ScreencastManager::ResetScopeSuppressDriveNotifications() {
  suppress_drive_notifications_for_path_.reset();
}

void ScreencastManager::OnVideoFilePathLocated(
    const std::string& video_id,
    ProjectorAppClient::OnGetVideoCallback callback,
    std::optional<std::vector<drivefs::mojom::FilePathOrErrorPtr>> paths) {
  if (!paths || paths.value().size() != 1u) {
    std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage(
        base::StringPrintf("Failed to find DriveFS path with video file id=%s",
                           video_id.c_str())));

    return;
  }

  const auto& path_or_error = paths.value()[0];
  if (path_or_error->is_error() || !path_or_error->is_path()) {
    std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage(
        base::StringPrintf("Failed to fetch DriveFS file with video file id=%s "
                           "and error code=%d",
                           video_id.c_str(), path_or_error->get_error())));
    return;
  }

  const base::FilePath& relative_drivefs_path = path_or_error->get_path();
  if (!relative_drivefs_path.MatchesExtension(kProjectorMediaFileExtension)) {
    std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage(
        base::StringPrintf("Failed to fetch video file with video file id=%s",
                           video_id.c_str())));
    return;
  }

  const base::FilePath& mounted_path =
      ProjectorDriveFsProvider::GetDriveFsMountPointPath();
  const base::FilePath& video_path = mounted_path.Append(relative_drivefs_path);

  // Suppresses the notification before calling GetVideoMetadata, which might
  // trigger file syncing event that triggers Drive notifications.
  suppress_drive_notifications_for_path_ =
      std::make_unique<file_manager::ScopedSuppressDriveNotificationsForPath>(
          ProfileManager::GetActiveUserProfile(),
          base::FilePath("/").Append(relative_drivefs_path));

  // Post task to:
  // 1. Fill the video duration which also serves the purpose of video file
  // validation.
  // 2. If the duration is valid, trigger LaunchProjectorAppWithFiles().
  // 3. Trigger `callback`.
  auto video = projector::mojom::VideoInfo::New();
  video->file_id = video_id;
  video_metadata_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&GetVideoMetadata, video_path, std::move(video),
                                std::move(callback)));
}

}  // namespace ash