blob: aa4a244927e90c56719df1c36b9f95c017b5d73b [file] [log] [blame]
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/file_manager/guest_os_file_tasks.h"
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/chromeos/crostini/crostini_features.h"
#include "chrome/browser/chromeos/crostini/crostini_mime_types_service.h"
#include "chrome/browser/chromeos/crostini/crostini_mime_types_service_factory.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_files.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
#include "extensions/browser/entry_info.h"
#include "storage/browser/file_system/file_system_url.h"
#include "ui/base/layout.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "url/gurl.h"
namespace file_manager {
namespace file_tasks {
namespace {
constexpr base::TimeDelta kIconLoadTimeout =
base::TimeDelta::FromMilliseconds(100);
constexpr size_t kIconSizeInDip = 16;
// When MIME type detection is done; if we can't be properly determined then
// the detection system will end up guessing one of the 2 values below depending
// upon whether it "thinks" it is binary or text content.
constexpr char kUnknownBinaryMimeType[] = "application/octet-stream";
constexpr char kUnknownTextMimeType[] = "text/plain";
GURL GeneratePNGDataUrl(const SkBitmap& sk_bitmap) {
std::vector<unsigned char> output;
gfx::PNGCodec::EncodeBGRASkBitmap(sk_bitmap, false /* discard_transparency */,
&output);
std::string encoded;
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(output.data()),
output.size()),
&encoded);
return GURL("data:image/png;base64," + encoded);
}
void OnAppIconsLoaded(
Profile* profile,
const std::vector<std::string>& app_ids,
const std::vector<std::string>& app_names,
const std::vector<guest_os::GuestOsRegistryService::VmType>& vm_types,
ui::ScaleFactor scale_factor,
std::vector<FullTaskDescriptor>* result_list,
base::OnceClosure completion_closure,
const std::vector<gfx::ImageSkia>& icons) {
DCHECK(!app_ids.empty());
DCHECK_EQ(app_ids.size(), icons.size());
float scale = ui::GetScaleForScaleFactor(scale_factor);
std::vector<TaskType> task_types;
task_types.reserve(vm_types.size());
for (auto vm_type : vm_types) {
switch (vm_type) {
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_TERMINA:
task_types.push_back(TASK_TYPE_CROSTINI_APP);
break;
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM:
task_types.push_back(TASK_TYPE_PLUGIN_VM_APP);
break;
default:
LOG(ERROR) << "Unsupported VmType: " << static_cast<int>(vm_type);
return;
}
}
for (size_t i = 0; i < app_ids.size(); ++i) {
result_list->push_back(FullTaskDescriptor(
TaskDescriptor(app_ids[i], task_types[i], kGuestOsAppActionID),
app_names[i],
extensions::api::file_manager_private::Verb::VERB_OPEN_WITH,
GeneratePNGDataUrl(icons[i].GetRepresentation(scale).GetBitmap()),
false /* is_default */, false /* is_generic */,
false /* is_file_extension_match */));
}
std::move(completion_closure).Run();
}
void OnTaskComplete(FileTaskFinishedCallback done,
bool success,
const std::string& failure_reason) {
if (!success) {
LOG(ERROR) << "Crostini task error: " << failure_reason;
}
std::move(done).Run(
success ? extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT
: extensions::api::file_manager_private::TASK_RESULT_FAILED,
failure_reason);
}
bool HasSupportedMimeType(
const std::set<std::string>& supported_mime_types,
const std::string& vm_name,
const std::string& container_name,
const crostini::CrostiniMimeTypesService& mime_types_service,
const extensions::EntryInfo& entry) {
if (supported_mime_types.find(entry.mime_type) !=
supported_mime_types.end()) {
return true;
}
// Allow files with type text/* to be opened with a text/plain application
// as per xdg spec.
// https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html.
// TODO(crbug.com/1032910): Add xdg mime support for aliases, subclasses.
if (base::StartsWith(entry.mime_type, "text/",
base::CompareCase::SENSITIVE) &&
supported_mime_types.find(kUnknownTextMimeType) !=
supported_mime_types.end()) {
return true;
}
// If we see either of these then we use the Linux container MIME type
// mappings as alternates for finding an appropriate app since these are
// the defaults when Chrome can't figure out the exact MIME type (but they
// can also be the actual MIME type, so we don't exclude them above).
if (entry.mime_type == kUnknownBinaryMimeType ||
entry.mime_type == kUnknownTextMimeType) {
const std::string& alternate_mime_type =
mime_types_service.GetMimeType(entry.path, vm_name, container_name);
if (supported_mime_types.find(alternate_mime_type) !=
supported_mime_types.end()) {
return true;
}
}
return false;
}
bool HasSupportedExtension(const std::set<std::string>& supported_extensions,
const extensions::EntryInfo& entry) {
const auto& extension = entry.path.Extension();
if (extension.size() <= 1 || extension[0] != '.')
return false;
// Strip the leading period.
return supported_extensions.find({extension.begin() + 1, extension.end()}) !=
supported_extensions.end();
}
bool AppSupportsAllEntries(
const crostini::CrostiniMimeTypesService& mime_types_service,
const std::vector<extensions::EntryInfo>& entries,
const guest_os::GuestOsRegistryService::Registration& app) {
guest_os::GuestOsRegistryService::VmType vm_type = app.VmType();
switch (vm_type) {
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_TERMINA:
return std::all_of(
cbegin(entries), cend(entries),
// Get fields once, as their getters are not cheap.
[supported_mime_types = app.MimeTypes(), vm_name = app.VmName(),
container_name = app.ContainerName(),
&mime_types_service](const auto& entry) {
return HasSupportedMimeType(supported_mime_types, vm_name,
container_name, mime_types_service,
entry);
});
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM:
return std::all_of(
cbegin(entries), cend(entries),
[supported_extensions = app.Extensions()](const auto& entry) {
return HasSupportedExtension(supported_extensions, entry);
});
default:
LOG(ERROR) << "Unsupported VmType: " << static_cast<int>(vm_type);
return false;
}
}
} // namespace
void FindGuestOsApps(
Profile* profile,
const std::vector<extensions::EntryInfo>& entries,
std::vector<std::string>* app_ids,
std::vector<std::string>* app_names,
std::vector<guest_os::GuestOsRegistryService::VmType>* vm_types) {
auto* registry_service =
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
crostini::CrostiniMimeTypesService* mime_types_service =
crostini::CrostiniMimeTypesServiceFactory::GetForProfile(profile);
for (const auto& pair : registry_service->GetAllRegisteredApps()) {
const std::string& app_id = pair.first;
const auto& registration = pair.second;
if (!AppSupportsAllEntries(*mime_types_service, entries, registration))
continue;
app_ids->push_back(app_id);
app_names->push_back(registration.Name());
vm_types->push_back(registration.VmType());
}
}
void FindGuestOsTasks(Profile* profile,
const std::vector<extensions::EntryInfo>& entries,
std::vector<FullTaskDescriptor>* result_list,
base::OnceClosure completion_closure) {
if (!crostini::CrostiniFeatures::Get()->IsUIAllowed(profile) &&
!plugin_vm::IsPluginVmEnabled(profile)) {
std::move(completion_closure).Run();
return;
}
std::vector<std::string> result_app_ids;
std::vector<std::string> result_app_names;
std::vector<guest_os::GuestOsRegistryService::VmType> result_vm_types;
FindGuestOsApps(profile, entries, &result_app_ids, &result_app_names,
&result_vm_types);
if (result_app_ids.empty()) {
std::move(completion_closure).Run();
return;
}
ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactors().back();
crostini::LoadIcons(
profile, result_app_ids, kIconSizeInDip, scale_factor, kIconLoadTimeout,
base::BindOnce(OnAppIconsLoaded, profile, result_app_ids,
std::move(result_app_names), std::move(result_vm_types),
scale_factor, result_list, std::move(completion_closure)));
}
void ExecuteGuestOsTask(
Profile* profile,
const TaskDescriptor& task,
const std::vector<storage::FileSystemURL>& file_system_urls,
FileTaskFinishedCallback done) {
auto* registry_service =
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
base::Optional<guest_os::GuestOsRegistryService::Registration> registration =
registry_service->GetRegistration(task.app_id);
if (!registration) {
std::move(done).Run(
extensions::api::file_manager_private::TASK_RESULT_FAILED,
"Unknown app_id: " + task.app_id);
return;
}
guest_os::GuestOsRegistryService::VmType vm_type = registration->VmType();
switch (vm_type) {
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_TERMINA:
DCHECK(crostini::CrostiniFeatures::Get()->IsUIAllowed(profile));
crostini::LaunchCrostiniApp(
profile, task.app_id, display::kInvalidDisplayId, file_system_urls,
base::BindOnce(OnTaskComplete, std::move(done)));
return;
case guest_os::GuestOsRegistryService::VmType::
ApplicationList_VmType_PLUGIN_VM:
DCHECK(plugin_vm::IsPluginVmEnabled(profile));
plugin_vm::LaunchPluginVmApp(
profile, task.app_id, file_system_urls,
base::BindOnce(OnTaskComplete, std::move(done)));
return;
default:
std::move(done).Run(
extensions::api::file_manager_private::TASK_RESULT_FAILED,
base::StringPrintf("Unsupported VmType: %d",
static_cast<int>(vm_type)));
}
}
} // namespace file_tasks
} // namespace file_manager