Index: ui/base/clipboard/clipboard_android.cc |
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc |
index 1a7d6ba13f64ae89df1dfc1c8b42a02bf6e725ba..19d57528bbcdefc09e57a97e95054216a73c689c 100644 |
--- a/ui/base/clipboard/clipboard_android.cc |
+++ b/ui/base/clipboard/clipboard_android.cc |
@@ -4,14 +4,34 @@ |
#include "ui/base/clipboard/clipboard.h" |
+#include "base/android/jni_string.h" |
+#include "base/lazy_instance.h" |
+#include "base/stl_util.h" |
+#include "base/synchronization/lock.h" |
+#include "base/utf_string_conversions.h" |
#include "third_party/skia/include/core/SkBitmap.h" |
#include "ui/gfx/size.h" |
+// Important note: |
+// The Android clipboard system only supports text format. So we use the |
+// Android system when some text is added or retrieved from the system. For |
+// anything else, we currently store the value in some process wide static |
+// variable protected by a lock. So the (non-text) clipboard will only work |
+// within the same process. |
+ |
+using base::android::AttachCurrentThread; |
+using base::android::CheckException; |
+using base::android::ClearException; |
+using base::android::ConvertJavaStringToUTF16; |
+using base::android::ConvertJavaStringToUTF8; |
+using base::android::GetClass; |
+using base::android::GetMethodID; |
+using base::android::ScopedJavaLocalRef; |
+ |
namespace ui { |
namespace { |
- |
-// Various format we support. |
+// Various formats we support. |
const char kPlainTextFormat[] = "text"; |
const char kHTMLFormat[] = "html"; |
const char kRTFFormat[] = "rtf"; |
@@ -20,6 +40,129 @@ const char kWebKitSmartPasteFormat[] = "webkit_smart"; |
const char kBookmarkFormat[] = "bookmark"; |
const char kMimeTypeWebCustomData[] = "chromium/x-web-custom-data"; |
+class ClipboardMap { |
+ public: |
+ ClipboardMap(); |
+ std::string Get(const std::string& format); |
+ bool HasFormat(const std::string& format); |
+ void Set(const std::string& format, const std::string& data); |
+ void Clear(); |
+ |
+ private: |
+ void SyncWithAndroidClipboard(); |
+ std::map<std::string, std::string> map_; |
+ base::Lock lock_; |
+ |
+ // Java class and methods for the Android ClipboardManager. |
+ base::android::ScopedJavaGlobalRef<jobject> clipboard_manager_; |
+ jmethodID set_text_; |
+ jmethodID get_text_; |
+ jmethodID has_text_; |
+ jmethodID to_string_; |
+}; |
+base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; |
+ |
+ClipboardMap::ClipboardMap() { |
+ JNIEnv* env = AttachCurrentThread(); |
+ DCHECK(env); |
+ |
+ // Get the context. |
+ jobject context = base::android::GetApplicationContext(); |
+ DCHECK(context); |
+ |
+ // Get the context class. |
+ ScopedJavaLocalRef<jclass> context_class = |
+ GetClass(env, "android/content/Context"); |
+ |
+ // Get the system service method. |
+ jmethodID get_system_service = GetMethodID(env, context_class, |
+ "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); |
+ |
+ // Retrieve the system service. |
+ ScopedJavaLocalRef<jstring> service_name(env, env->NewStringUTF("clipboard")); |
+ clipboard_manager_.Reset(env, env->CallObjectMethod(context, |
+ get_system_service, service_name.obj())); |
+ DCHECK(clipboard_manager_.obj() && !ClearException(env)); |
+ |
+ // Retain a few methods we'll keep using. |
+ ScopedJavaLocalRef<jclass> clipboard_class = |
+ GetClass(env, "android/text/ClipboardManager"); |
+ set_text_ = GetMethodID(env, clipboard_class, |
+ "setText", "(Ljava/lang/CharSequence;)V"); |
+ get_text_ = GetMethodID(env, clipboard_class, |
+ "getText", "()Ljava/lang/CharSequence;"); |
+ has_text_ = GetMethodID(env, clipboard_class, |
+ "hasText", "()Z"); |
+ |
+ // Will need to call toString as CharSequence is not always a String. |
+ ScopedJavaLocalRef<jclass> charsequence_class = |
+ GetClass(env, "java/lang/CharSequence"); |
+ to_string_ = GetMethodID(env, charsequence_class, |
+ "toString", "()Ljava/lang/String;"); |
+} |
+ |
+std::string ClipboardMap::Get(const std::string& format) { |
+ base::AutoLock lock(lock_); |
+ SyncWithAndroidClipboard(); |
+ std::map<std::string, std::string>::const_iterator it = map_.find(format); |
+ return it == map_.end() ? std::string() : it->second; |
+} |
+ |
+bool ClipboardMap::HasFormat(const std::string& format) { |
+ base::AutoLock lock(lock_); |
+ SyncWithAndroidClipboard(); |
+ return ContainsKey(map_, format); |
+} |
+ |
+void ClipboardMap::Set(const std::string& format, const std::string& data) { |
+ JNIEnv* env = AttachCurrentThread(); |
+ base::AutoLock lock(lock_); |
+ SyncWithAndroidClipboard(); |
+ |
+ map_[format] = data; |
+ if (format == kPlainTextFormat) { |
+ ScopedJavaLocalRef<jstring> str( |
+ env, env->NewStringUTF(data.c_str())); |
+ DCHECK(str.obj() && !ClearException(env)); |
+ env->CallVoidMethod(clipboard_manager_.obj(), set_text_, str.obj()); |
+ } |
+} |
+ |
+void ClipboardMap::Clear() { |
+ JNIEnv* env = AttachCurrentThread(); |
+ base::AutoLock lock(lock_); |
+ map_.clear(); |
+ env->CallVoidMethod(clipboard_manager_.obj(), set_text_, NULL); |
+} |
+ |
+// If the text in the internal map does not match that in the Android clipboard, |
+// clear the map and insert the Android text into it. |
+void ClipboardMap::SyncWithAndroidClipboard() { |
+ lock_.AssertAcquired(); |
+ JNIEnv* env = AttachCurrentThread(); |
+ |
+ std::map<std::string, std::string>::const_iterator it = |
+ map_.find(kPlainTextFormat); |
+ |
+ if (!env->CallBooleanMethod(clipboard_manager_.obj(), has_text_)) { |
+ if (it != map_.end()) |
+ map_.clear(); |
+ return; |
+ } |
+ |
+ ScopedJavaLocalRef<jobject> char_seq_text( |
+ env, env->CallObjectMethod(clipboard_manager_.obj(), get_text_)); |
+ ScopedJavaLocalRef<jstring> tmp_string(env, |
+ static_cast<jstring>(env->CallObjectMethod(char_seq_text.obj(), |
+ to_string_))); |
+ std::string android_string = ConvertJavaStringToUTF8(tmp_string); |
+ |
+ if (it == map_.end() || it->second != android_string) { |
+ map_.clear(); |
+ map_[kPlainTextFormat] = android_string; |
+ } |
+} |
+ |
} // namespace |
Clipboard::FormatType::FormatType() { |
@@ -46,63 +189,123 @@ bool Clipboard::FormatType::Equals(const FormatType& other) const { |
return data_ == other.data_; |
} |
-Clipboard::Clipboard() : set_text_(NULL), has_text_(NULL), get_text_(NULL) { |
+Clipboard::Clipboard() { |
} |
Clipboard::~Clipboard() { |
} |
+// Main entry point used to write several values in the clipboard. |
void Clipboard::WriteObjects(Buffer buffer, const ObjectMap& objects) { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ g_map.Get().Clear(); |
+ for (ObjectMap::const_iterator iter = objects.begin(); |
+ iter != objects.end(); ++iter) { |
+ DispatchObject(static_cast<ObjectType>(iter->first), iter->second); |
+ } |
} |
uint64 Clipboard::GetSequenceNumber(Clipboard::Buffer /* buffer */) { |
+ // TODO: implement this. For now this interface will advertise |
+ // that the clipboard never changes. That's fine as long as we |
+ // don't rely on this signal. |
return 0; |
} |
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, |
Clipboard::Buffer buffer) const { |
- return false; |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ return g_map.Get().HasFormat(format.data()); |
} |
void Clipboard::Clear(Buffer buffer) { |
- |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ g_map.Get().Clear(); |
} |
void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, |
bool* contains_filenames) const { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ |
+ if (!types || !contains_filenames) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ NOTIMPLEMENTED(); |
+ |
+ types->clear(); |
+ *contains_filenames = false; |
} |
void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ std::string utf8; |
+ ReadAsciiText(buffer, &utf8); |
+ *result = UTF8ToUTF16(utf8); |
} |
void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, |
std::string* result) const { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ *result = g_map.Get().Get(kPlainTextFormat); |
} |
+// Note: |src_url| isn't really used. It is only implemented in Windows |
void Clipboard::ReadHTML(Clipboard::Buffer buffer, |
string16* markup, |
std::string* src_url, |
uint32* fragment_start, |
uint32* fragment_end) const { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ if (src_url) |
+ src_url->clear(); |
+ |
+ std::string input = g_map.Get().Get(kHTMLFormat); |
+ *markup = UTF8ToUTF16(input); |
+ |
+ *fragment_start = 0; |
+ *fragment_end = static_cast<uint32>(markup->length()); |
} |
void Clipboard::ReadRTF(Buffer buffer, std::string* result) const { |
+ NOTIMPLEMENTED(); |
} |
SkBitmap Clipboard::ReadImage(Buffer buffer) const { |
- return SkBitmap(); |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ std::string input = g_map.Get().Get(kBitmapFormat); |
+ |
+ SkBitmap bmp; |
+ if (!input.empty()) { |
+ DCHECK_LE(sizeof(gfx::Size), input.size()); |
+ const gfx::Size* size = reinterpret_cast<const gfx::Size*>(input.data()); |
+ |
+ bmp.setConfig( |
+ SkBitmap::kARGB_8888_Config, size->width(), size->height(), 0); |
+ bmp.allocPixels(); |
+ |
+ int bm_size = size->width() * size->height() * 4; |
+ DCHECK_EQ(sizeof(gfx::Size) + bm_size, input.size()); |
+ |
+ memcpy(bmp.getPixels(), input.data() + sizeof(gfx::Size), bm_size); |
+ } |
+ return bmp; |
} |
void Clipboard::ReadCustomData(Buffer buffer, |
const string16& type, |
string16* result) const { |
+ NOTIMPLEMENTED(); |
} |
void Clipboard::ReadBookmark(string16* title, std::string* url) const { |
+ NOTIMPLEMENTED(); |
} |
void Clipboard::ReadData(const Clipboard::FormatType& format, |
std::string* result) const { |
+ *result = g_map.Get().Get(format.data()); |
} |
// static |
@@ -154,45 +357,49 @@ const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { |
} |
void Clipboard::WriteText(const char* text_data, size_t text_len) { |
+ g_map.Get().Set(kPlainTextFormat, std::string(text_data, text_len)); |
} |
void Clipboard::WriteHTML(const char* markup_data, |
size_t markup_len, |
const char* url_data, |
size_t url_len) { |
+ g_map.Get().Set(kHTMLFormat, std::string(markup_data, markup_len)); |
} |
void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { |
+ NOTIMPLEMENTED(); |
} |
+// Note: according to other platforms implementations, this really writes the |
+// URL spec. |
void Clipboard::WriteBookmark(const char* title_data, size_t title_len, |
const char* url_data, size_t url_len) { |
+ g_map.Get().Set(kBookmarkFormat, std::string(url_data, url_len)); |
} |
+// Write an extra flavor that signifies WebKit was the last to modify the |
+// pasteboard. This flavor has no data. |
void Clipboard::WriteWebSmartPaste() { |
+ g_map.Get().Set(kWebKitSmartPasteFormat, std::string()); |
} |
+// All platforms use gfx::Size for size data but it is passed as a const char* |
+// Further, pixel_data is expected to be 32 bits per pixel |
+// Note: we implement this to pass all unit tests but it is currently unclear |
+// how some code would consume this. |
void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { |
+ const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); |
+ int bm_size = size->width() * size->height() * 4; |
+ |
+ std::string packed(size_data, sizeof(gfx::Size)); |
+ packed += std::string(pixel_data, bm_size); |
+ g_map.Get().Set(kBitmapFormat, packed); |
} |
void Clipboard::WriteData(const Clipboard::FormatType& format, |
const char* data_data, size_t data_len) { |
+ g_map.Get().Set(format.data(), std::string(data_data, data_len)); |
} |
-bool Clipboard::IsTextAvailableFromAndroid() const { |
- return false; |
-} |
- |
-void Clipboard::ValidateInternalClipboard() const { |
-} |
- |
-void Clipboard::Clear() { |
-} |
- |
-void Clipboard::ClearInternalClipboard() const { |
-} |
- |
-void Clipboard::Set(const std::string& key, const std::string& value) { |
-} |
- |
-} // namespace ui |
+} // namespace ui |