blob: 7bc909cc3bcf3d67970a9e45d7832db51a77b602 [file]
// 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 "content/browser/android/drop_data_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "ui/events/android/drag_event_android.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "ui/android/ui_android_jni_headers/DropDataAndroid_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaByteArray;
namespace content {
// This function refers to //content/browser/renderer_host/data_transfer_util.cc
// on how data fields are populated. If data_transfer_util.cc ever changed, this
// function will need updates to keep in sync.
ScopedJavaLocalRef<jobject> ToJavaDropData(const DropData& drop_data) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> jtext;
if (drop_data.text) {
jtext = ConvertUTF16ToJavaString(env, *drop_data.text);
}
ScopedJavaLocalRef<jobject> jgurl;
if (!drop_data.url_infos.empty()) {
const ui::ClipboardUrlInfo& url_info = drop_data.url_infos.front();
jgurl = url::GURLAndroid::FromNativeGURL(env, url_info.url);
jtext = ConvertUTF16ToJavaString(env, url_info.title);
}
// If file_contents is not empty, user is trying to drag image out of the
// web contents. If the image contains a link, the link URL, represented by
// |jgurl|, will be ignored.
// drop_data.file_contents_source_url represents the image source URL.
ScopedJavaLocalRef<jbyteArray> jimage_bytes;
ScopedJavaLocalRef<jstring> jimage_extension;
ScopedJavaLocalRef<jstring> jimage_filename;
if (!drop_data.file_contents.empty()) {
jimage_bytes = ToJavaByteArray(env, drop_data.file_contents);
jimage_extension = ConvertUTF8ToJavaString(
env, drop_data.file_contents_filename_extension);
std::optional<base::FilePath> filename =
drop_data.GetSafeFilenameForImageFileContents();
if (filename) {
jimage_filename =
ConvertUTF16ToJavaString(env, filename->LossyDisplayName());
} else {
// Use the current timestamp as the image file name in case the file name
// is not retrieved from the source.
jimage_filename = ConvertUTF8ToJavaString(
env, base::NumberToString(
base::Time::Now().since_origin().InMilliseconds()) +
"." + drop_data.file_contents_filename_extension);
}
}
base::DictValue custom_data_dict;
for (const auto& pair : drop_data.custom_data) {
// Key must be converted to UTF-8 as base::Value only supports UTF-8 keys.
// Value can remain UTF-16 as there is a specific overload for it.
custom_data_dict.Set(base::UTF16ToUTF8(pair.first), pair.second);
}
std::optional<std::string> custom_data_json =
base::WriteJson(base::Value(std::move(custom_data_dict)));
ScopedJavaLocalRef<jstring> jcustom_data;
if (custom_data_json) {
jcustom_data = ConvertUTF8ToJavaString(env, *custom_data_json);
}
ScopedJavaLocalRef<jstring> jeffect_allowed;
if (drop_data.source_effect_allowed) {
jeffect_allowed =
ConvertUTF16ToJavaString(env, *drop_data.source_effect_allowed);
}
return ui::Java_DropDataAndroid_create(env, jtext, jgurl, jimage_bytes,
jimage_extension, jimage_filename,
jcustom_data, jeffect_allowed);
} // ToJavaDropData
namespace {
void PopulateCustomDataFromEvent(JNIEnv* env,
const ui::DragEventAndroid& event,
DropData* drop_data) {
if (event.GetJavaCustomData().is_null()) {
return;
}
std::string custom_data_json =
base::android::ConvertJavaStringToUTF8(env, event.GetJavaCustomData());
if (custom_data_json.empty()) {
return;
}
std::optional<base::Value> value =
base::JSONReader::Read(custom_data_json, 0);
if (!value || !value->is_dict()) {
return;
}
const base::DictValue* dict = value->GetIfDict();
for (auto item : *dict) {
const std::string* str = item.second.GetIfString();
if (!str) {
continue;
}
drop_data->custom_data.emplace(base::UTF8ToUTF16(item.first),
base::UTF8ToUTF16(*str));
}
}
} // namespace
void PopulateDropDataFromEvent(const ui::DragEventAndroid& event,
DropData* drop_data) {
JNIEnv* env = AttachCurrentThread();
std::vector<std::vector<std::string>> filenames;
if (!event.GetJavaFilenames().is_null()) {
base::android::Java2dStringArrayTo2dStringVector(
env, event.GetJavaFilenames(), &filenames);
}
for (const auto& info : filenames) {
CHECK_EQ(info.size(), 2u);
drop_data->filenames.emplace_back(base::FilePath(info[0]),
base::FilePath(info[1]));
}
if (!event.GetJavaText().is_null()) {
drop_data->text =
base::android::ConvertJavaStringToUTF16(env, event.GetJavaText());
}
if (!event.GetJavaHtml().is_null()) {
drop_data->html =
base::android::ConvertJavaStringToUTF16(env, event.GetJavaHtml());
}
if (!event.GetJavaUrl().is_null()) {
GURL url(base::android::ConvertJavaStringToUTF16(env, event.GetJavaUrl()));
drop_data->url_infos.emplace_back(std::move(url), std::u16string());
}
// Handle custom data.
PopulateCustomDataFromEvent(env, event, drop_data);
// Handle effectAllowed.
if (!event.GetJavaEffectAllowed().is_null()) {
drop_data->source_effect_allowed = base::android::ConvertJavaStringToUTF16(
env, event.GetJavaEffectAllowed());
}
}
} // namespace content
DEFINE_JNI(DropDataAndroid)