blob: 091fca0acbbde2a627b80ac79c4e8c22ff88fabf [file] [log] [blame]
// Copyright (c) 2016 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/arc_file_tasks.h"
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/grit/generated_resources.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/common/intent_helper.mojom.h"
#include "components/arc/intent_helper/activity_icon_loader.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/entry_info.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "net/base/escape.h"
#include "net/base/filename_util.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "url/gurl.h"
namespace file_manager {
namespace file_tasks {
namespace {
constexpr int kArcIntentHelperVersionWithUrlListSupport = 4;
constexpr int kArcIntentHelperVersionWithFullActivityName = 5;
constexpr base::FilePath::CharType kArcDownloadPath[] =
FILE_PATH_LITERAL("/sdcard/Download");
constexpr base::FilePath::CharType kRemovableMediaPath[] =
FILE_PATH_LITERAL("/media/removable");
constexpr char kArcRemovableMediaProviderUrl[] =
"content://org.chromium.arc.removablemediaprovider/";
constexpr char kAppIdSeparator = '/';
constexpr char kPngDataUrlPrefix[] = "data:image/png;base64,";
// Returns the Mojo interface for ARC Intent Helper, with version |minVersion|
// or above. If the ARC bridge is not established, returns null.
arc::mojom::IntentHelperInstance* GetArcIntentHelper(Profile* profile,
uint32_t min_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// File manager in secondary profile cannot access ARC.
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile))
return nullptr;
arc::ArcBridgeService* arc_service = arc::ArcBridgeService::Get();
if (!arc_service)
return nullptr;
arc::mojom::IntentHelperInstance* intent_helper_instance =
arc_service->intent_helper()->instance();
if (!intent_helper_instance)
return nullptr;
if (arc_service->intent_helper()->version() < min_version) {
DLOG(WARNING) << "ARC intent helper instance is too old.";
return nullptr;
}
return intent_helper_instance;
}
// Returns the icon loader that wraps the Mojo interface for ARC Intent Helper.
scoped_refptr<arc::ActivityIconLoader> GetArcActivityIconLoader() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc::ArcServiceManager* arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager)
return nullptr;
return arc_service_manager->icon_loader();
}
std::string ArcActionToString(arc::mojom::ActionType action) {
switch (action) {
case arc::mojom::ActionType::VIEW:
return "view";
case arc::mojom::ActionType::SEND:
return "send";
case arc::mojom::ActionType::SEND_MULTIPLE:
return "send_multiple";
}
NOTREACHED();
return "";
}
arc::mojom::ActionType StringToArcAction(const std::string& str) {
if (str == "view")
return arc::mojom::ActionType::VIEW;
if (str == "send")
return arc::mojom::ActionType::SEND;
if (str == "send_multiple")
return arc::mojom::ActionType::SEND_MULTIPLE;
NOTREACHED();
return arc::mojom::ActionType::VIEW;
}
std::string ActivityNameToAppId(const std::string& package_name,
const std::string& activity_name) {
return package_name + kAppIdSeparator + activity_name;
}
arc::mojom::ActivityNamePtr AppIdToActivityName(const std::string& id) {
arc::mojom::ActivityNamePtr name = arc::mojom::ActivityName::New();
const size_t separator = id.find(kAppIdSeparator);
if (separator == std::string::npos) {
name->package_name = id;
name->activity_name = mojo::String();
} else {
name->package_name = id.substr(0, separator);
name->activity_name = id.substr(separator + 1);
}
return name;
}
// Converts the Chrome OS file path to ARC file URL.
bool ConvertToArcUrl(const base::FilePath& path, GURL* arc_url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Obtain the primary profile. This information is required because currently
// only the file systems for the primary profile is exposed to ARC.
if (!user_manager::UserManager::IsInitialized())
return false;
const user_manager::User* primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
if (!primary_user)
return false;
Profile* primary_profile =
chromeos::ProfileHelper::Get()->GetProfileByUser(primary_user);
if (!primary_profile)
return false;
// Convert paths under primary profile's Downloads directory.
base::FilePath primary_downloads =
util::GetDownloadsFolderForProfile(primary_profile);
base::FilePath result_path(kArcDownloadPath);
if (primary_downloads.AppendRelativePath(path, &result_path)) {
*arc_url = net::FilePathToFileURL(result_path);
return true;
}
// Convert paths under /media/removable.
base::FilePath relative_path;
if (base::FilePath(kRemovableMediaPath)
.AppendRelativePath(path, &relative_path)) {
*arc_url = GURL(kArcRemovableMediaProviderUrl)
.Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
return true;
}
// TODO(kinaba): Add conversion logic once other file systems are supported.
return false;
}
// Below is the sequence of thread-hopping for loading ARC file tasks.
void OnArcHandlerList(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers);
void OnArcIconLoaded(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers,
std::unique_ptr<arc::ActivityIconLoader::ActivityToIconsMap> icons);
typedef std::map<arc::ActivityIconLoader::ActivityName, GURL> IconUrlMap;
std::unique_ptr<IconUrlMap> EncodeIconsToDataURLs(
std::unique_ptr<arc::ActivityIconLoader::ActivityToIconsMap> icons);
void OnArcIconEncoded(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers,
std::unique_ptr<IconUrlMap> icons);
// Called after the handlers from ARC is obtained. Proceeds to OnArcIconLoaded.
void OnArcHandlerList(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<arc::ActivityIconLoader> icon_loader =
GetArcActivityIconLoader();
if (!icon_loader) {
callback.Run(std::move(result_list));
return;
}
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers_filtered =
arc::ArcIntentHelperBridge::FilterOutIntentHelper(std::move(handlers));
std::vector<arc::ActivityIconLoader::ActivityName> activity_names;
for (const arc::mojom::UrlHandlerInfoPtr& handler : handlers_filtered)
activity_names.emplace_back(handler->package_name, handler->activity_name);
icon_loader->GetActivityIcons(
activity_names, base::Bind(&OnArcIconLoaded, base::Passed(&result_list),
callback, base::Passed(&handlers_filtered)));
}
// Called after icon data for ARC apps are loaded. Proceeds to OnArcIconEncoded.
void OnArcIconLoaded(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers,
std::unique_ptr<arc::ActivityIconLoader::ActivityToIconsMap> icons) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskAndReplyWithResult(
content::BrowserThread::GetBlockingPool(), FROM_HERE,
base::Bind(&EncodeIconsToDataURLs, base::Passed(&icons)),
base::Bind(&OnArcIconEncoded, base::Passed(&result_list), callback,
base::Passed(&handlers)));
}
// Encode the set of icon images to data URLs.
std::unique_ptr<IconUrlMap> EncodeIconsToDataURLs(
std::unique_ptr<arc::ActivityIconLoader::ActivityToIconsMap> icons) {
base::ThreadRestrictions::AssertIOAllowed();
std::unique_ptr<IconUrlMap> result(new IconUrlMap);
for (const auto& entry : *icons) {
scoped_refptr<base::RefCountedMemory> img =
entry.second.icon16.As1xPNGBytes();
if (!img)
continue;
std::string encoded;
base::Base64Encode(base::StringPiece(img->front_as<char>(), img->size()),
&encoded);
result->insert(
std::make_pair(entry.first, GURL(kPngDataUrlPrefix + encoded)));
}
return result;
}
// Called after icon data is encoded on the blocking pool.
void OnArcIconEncoded(
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback,
mojo::Array<arc::mojom::UrlHandlerInfoPtr> handlers,
std::unique_ptr<IconUrlMap> icons) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
using extensions::api::file_manager_private::Verb;
for (const arc::mojom::UrlHandlerInfoPtr& handler : handlers) {
std::string name(handler->name);
Verb handler_verb = Verb::VERB_NONE;
if (handler->action == arc::mojom::ActionType::SEND ||
handler->action == arc::mojom::ActionType::SEND_MULTIPLE) {
handler_verb = Verb::VERB_SHARE_WITH;
}
const GURL& icon_url = (*icons)[arc::ActivityIconLoader::ActivityName(
handler->package_name, handler->activity_name)];
result_list->push_back(FullTaskDescriptor(
TaskDescriptor(
ActivityNameToAppId(handler->package_name, handler->activity_name),
TASK_TYPE_ARC_APP, ArcActionToString(handler->action)),
name, handler_verb, icon_url, false /* is_default */,
handler->action != arc::mojom::ActionType::VIEW /* is_generic */));
}
callback.Run(std::move(result_list));
}
} // namespace
void FindArcTasks(Profile* profile,
const std::vector<extensions::EntryInfo>& entries,
std::unique_ptr<std::vector<FullTaskDescriptor>> result_list,
const FindTasksCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc::mojom::IntentHelperInstance* arc_intent_helper =
GetArcIntentHelper(profile, kArcIntentHelperVersionWithUrlListSupport);
if (!arc_intent_helper) {
callback.Run(std::move(result_list));
return;
}
mojo::Array<arc::mojom::UrlWithMimeTypePtr> urls;
for (const extensions::EntryInfo& entry : entries) {
if (entry.is_directory) { // ARC apps don't support directories.
callback.Run(std::move(result_list));
return;
}
GURL url;
if (!ConvertToArcUrl(entry.path, &url)) {
callback.Run(std::move(result_list));
return;
}
arc::mojom::UrlWithMimeTypePtr url_with_type =
arc::mojom::UrlWithMimeType::New();
url_with_type->url = url.spec();
url_with_type->mime_type = entry.mime_type;
urls.push_back(std::move(url_with_type));
}
arc_intent_helper->RequestUrlListHandlerList(
std::move(urls),
base::Bind(&OnArcHandlerList, base::Passed(&result_list), callback));
}
bool ExecuteArcTask(Profile* profile,
const TaskDescriptor& task,
const std::vector<storage::FileSystemURL>& file_urls,
const std::vector<std::string>& mime_types) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(file_urls.size(), mime_types.size());
arc::mojom::IntentHelperInstance* const arc_intent_helper =
GetArcIntentHelper(profile, kArcIntentHelperVersionWithUrlListSupport);
if (!arc_intent_helper)
return false;
mojo::Array<arc::mojom::UrlWithMimeTypePtr> urls;
for (size_t i = 0; i < file_urls.size(); ++i) {
GURL url;
if (!ConvertToArcUrl(file_urls[i].path(), &url)) {
LOG(ERROR) << "File on unsuppored path";
return false;
}
arc::mojom::UrlWithMimeTypePtr url_with_type =
arc::mojom::UrlWithMimeType::New();
url_with_type->url = url.spec();
url_with_type->mime_type = mime_types[i];
urls.push_back(std::move(url_with_type));
}
if (GetArcIntentHelper(profile,
kArcIntentHelperVersionWithFullActivityName)) {
arc_intent_helper->HandleUrlList(std::move(urls),
AppIdToActivityName(task.app_id),
StringToArcAction(task.action_id));
} else {
arc_intent_helper->HandleUrlListDeprecated(
std::move(urls), AppIdToActivityName(task.app_id)->package_name,
StringToArcAction(task.action_id));
}
return true;
}
} // namespace file_tasks
} // namespace file_manager