Index: ui/base/clipboard/clipboard_android.cc |
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d932d8e1f2bb5ad515d77d330339cc325481dbc4 |
--- /dev/null |
+++ b/ui/base/clipboard/clipboard_android.cc |
@@ -0,0 +1,467 @@ |
+// Copyright (c) 2012 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 "ui/base/clipboard/clipboard.h" |
+ |
+#include <jni.h> |
+ |
+#include "base/android/jni_android.h" |
+#include "base/android/jni_string.h" |
+#include "base/lazy_instance.h" |
+#include "base/logging.h" |
+#include "base/memory/scoped_ptr.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: |
+// Android's clipboard system only supports text content, so use it only when |
+// text is added to or retrieved from the system. For other data types, store |
+// the value in a map. This has the consequence that the clipboard's contents |
+// will only be available within the current 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 { |
+// As Android only supports text in the clipboard, the following map will be |
+// used for other kinds of data. Use the lock to make this thread-safe. |
+// TODO(beverloo): http://crbug.com/112286 Investigate whether the locks in |
+// this file are required. |
+typedef std::map<std::string, std::string> ClipboardMap; |
+ClipboardMap* g_clipboard_map = NULL; |
+base::LazyInstance<base::Lock> g_clipboard_map_lock = LAZY_INSTANCE_INITIALIZER; |
+ |
+// Various format we support. |
+const char kPlainTextFormat[] = "text"; |
+const char kHTMLFormat[] = "html"; |
+const char kBitmapFormat[] = "bitmap"; |
+const char kWebKitSmartPasteFormat[] = "webkit_smart"; |
+const char kBookmarkFormat[] = "bookmark"; |
+const char kMimeTypeWebCustomData[] = "chromium/x-web-custom-data"; |
+ |
+} // namespace |
+ |
+Clipboard::FormatType::FormatType() { |
+} |
+ |
+Clipboard::FormatType::FormatType(const std::string& native_format) |
+ : data_(native_format) { |
+} |
+ |
+Clipboard::FormatType::~FormatType() { |
+} |
+ |
+std::string Clipboard::FormatType::Serialize() const { |
+ return data_; |
+} |
+ |
+// static |
+Clipboard::FormatType Clipboard::FormatType::Deserialize( |
+ const std::string& serialization) { |
+ return FormatType(serialization); |
+} |
+ |
+bool Clipboard::FormatType::Equals(const FormatType& other) const { |
+ return data_ == other.data_; |
+} |
+ |
+// The clipboard object on the Android platform is simply wrapping the Java |
+// object for the text data format. For non-text format, a global map is used. |
+Clipboard::Clipboard() : set_text_(NULL), has_text_(NULL), get_text_(NULL) { |
+ JNIEnv* env = AttachCurrentThread(); |
+ DCHECK(env); |
+ |
+ // Get the context. |
+ // We have need a ScopedJavaLocalRef to clean-up the local ref if we have to |
+ // create the Context. |
+ jobject context = base::android::GetApplicationContext(); |
+ ScopedJavaLocalRef<jobject> scoped_context; |
+ if (!context) { |
+ // Should be during testing only. |
+ // Get the ActivityThread class. |
+ ScopedJavaLocalRef<jclass> activity_thread_class = |
+ GetClass(env, "android/app/ActivityThread"); |
+ |
+ // Try to get the current activity thread. |
+ jmethodID current_activity_method_id = GetMethodID( |
+ env, activity_thread_class, |
+ "currentActivityThread", "()Landroid/app/ActivityThread;"); |
+ |
+ ScopedJavaLocalRef<jobject> current_activity(env, |
+ env->CallStaticObjectMethod(activity_thread_class.obj(), |
+ current_activity_method_id)); |
+ ClearException(env); |
+ |
+ if (!current_activity.obj()) { |
+ // There is no current activity, create one. |
+ ScopedJavaLocalRef<jclass> looper_class = |
+ GetClass(env, "android/os/Looper"); |
+ jmethodID prepare_method_id = |
+ GetStaticMethodID(env, looper_class, "prepareMainLooper", "()V"); |
+ env->CallStaticVoidMethod(looper_class.obj(), prepare_method_id); |
+ CheckException(env); |
+ |
+ jmethodID system_main_method_id = |
+ GetStaticMethodID(env, activity_thread_class, "systemMain", |
+ "()Landroid/app/ActivityThread;"); |
+ |
+ current_activity.Reset(env, env->CallStaticObjectMethod( |
+ activity_thread_class.obj(), system_main_method_id)); |
+ DCHECK(current_activity.obj()); |
+ CheckException(env); |
+ } |
+ |
+ // Get the context. |
+ jmethodID get_system_context_id = GetMethodID(env, activity_thread_class, |
+ "getSystemContext", "()Landroid/app/ContextImpl;"); |
+ scoped_context.Reset(env, env->CallObjectMethod(current_activity.obj(), |
+ get_system_context_id)); |
+ context = scoped_context.obj(); |
+ 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())); |
+ ClearException(env); |
+ DCHECK(clipboard_manager_.obj()); |
+ |
+ // 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"); |
+ has_text_ = GetMethodID(env, clipboard_class, "hasText", "()Z"); |
+ get_text_ = GetMethodID(env, clipboard_class, |
+ "getText", "()Ljava/lang/CharSequence;"); |
+ |
+ // 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;"); |
+ |
+ // Create the object map if we are the first clipboard |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ if (!g_clipboard_map) |
+ g_clipboard_map = new ClipboardMap; |
+} |
+ |
+Clipboard::~Clipboard() { |
+} |
+ |
+// Main entry point used to write several values in the clipboard. |
+void Clipboard::WriteObjects(const ObjectMap& objects) { |
+ 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 { |
+ DCHECK_EQ(buffer, BUFFER_STANDARD); |
+ |
+ if (!format.compare(kPlainTextFormat)) |
+ return IsTextAvailableFromAndroid(); |
+ |
+ ValidateInternalClipboard(); |
+ |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ return g_clipboard_map->find(format.ToString()) != g_clipboard_map->end(); |
+} |
+ |
+void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, |
+ bool* contains_filenames) const { |
+ if (!types || !contains_filenames) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ // This is unimplemented on the other platforms (e.g. win, linux). |
+ NOTIMPLEMENTED(); |
+ |
+ types->clear(); |
+ *contains_filenames = false; |
+} |
+ |
+void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { |
+ JNIEnv* env = AttachCurrentThread(); |
+ |
+ result->clear(); |
+ if (!env->CallBooleanMethod(clipboard_manager_.obj(), has_text_)) |
+ 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_))); |
+ *result = ConvertJavaStringToUTF16(tmp_string); |
+} |
+ |
+void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, |
+ std::string* result) const { |
+ JNIEnv* env = AttachCurrentThread(); |
+ |
+ result->clear(); |
+ if (!env->CallBooleanMethod(clipboard_manager_.obj(), has_text_)) |
+ 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_))); |
+ *result = ConvertJavaStringToUTF8(tmp_string); |
+} |
+ |
+// 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 { |
+ markup->clear(); |
+ if (src_url) |
+ src_url->clear(); |
+ *fragment_start = 0; |
+ *fragment_end = 0; |
+ |
+ std::string input; |
+ |
+ ValidateInternalClipboard(); |
+ |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ ClipboardMap::const_iterator it = g_clipboard_map->find(kHTMLFormat); |
+ if (it != g_clipboard_map->end()) |
+ input = it->second; |
+ |
+ if (input.empty()) |
+ return; |
+ |
+ *fragment_end = static_cast<uint32>(input.length()); |
+ |
+ UTF8ToUTF16(input.c_str(), input.length(), markup); |
+} |
+ |
+SkBitmap Clipboard::ReadImage(Buffer buffer) const { |
+ NOTIMPLEMENTED(); |
+ return SkBitmap(); |
+} |
+ |
+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->clear(); |
+ |
+ ValidateInternalClipboard(); |
+ |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ ClipboardMap::const_iterator it = g_clipboard_map->find(format.ToString()); |
+ if (it != g_clipboard_map->end()) |
+ result->assign(it->second); |
+} |
+ |
+// static |
+Clipboard::FormatType Clipboard::GetFormatType( |
+ const std::string& format_string) { |
+ return FormatType::Deserialize(format_string); |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); |
+ return type; |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); |
+ return type; |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat)); |
+ return type; |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kHTMLFormat)); |
+ return type; |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kBitmapFormat)); |
+ return type; |
+} |
+ |
+// static |
+const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { |
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); |
+ return type; |
+} |
+ |
+void Clipboard::WriteText(const char* text_data, size_t text_len) { |
+ // Write the text in the Android Clipboard. |
+ JNIEnv* env = AttachCurrentThread(); |
+ DCHECK(env); |
+ |
+ std::string data(text_data, text_len); |
+ ScopedJavaLocalRef<jstring> str(env, env->NewStringUTF(data.c_str())); |
+ DCHECK(str.obj() && !ClearException(env)); |
+ |
+ env->CallVoidMethod(clipboard_manager_.obj(), set_text_, str.obj()); |
+ |
+ // Then write it in our internal data structure. We keep it there to check if |
+ // another app performed a copy. See ValidateInternalClipboard(). |
+ 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) { |
+ Set(kHTMLFormat, std::string(markup_data, markup_len)); |
+} |
+ |
+// 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) { |
+ 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() { |
+ 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; |
+ int total_size = (sizeof(int) * 2) + bm_size; |
+ scoped_array<char> buffer(new char[total_size]); |
+ |
+ char* p = buffer.get(); |
+ int n = size->width(); |
+ memcpy(p, &n, sizeof(int)); |
+ p += sizeof(int); |
+ n = size->height(); |
+ memcpy(p, &n, sizeof(int)); |
+ p += sizeof(int); |
+ memcpy(p, pixel_data, bm_size); |
+ |
+ Set(kBitmapFormat, std::string(buffer.get(), total_size)); |
+} |
+ |
+void Clipboard::WriteData(const Clipboard::FormatType& format, |
+ const char* data_data, size_t data_len) { |
+ Set(format.ToString(), std::string(data_data, data_len)); |
+} |
+ |
+bool Clipboard::IsTextAvailableFromAndroid() const { |
+ JNIEnv* env = AttachCurrentThread(); |
+ return env->CallBooleanMethod(clipboard_manager_.obj(), has_text_); |
+} |
+ |
+void Clipboard::ValidateInternalClipboard() const { |
+ JNIEnv* env = AttachCurrentThread(); |
+ |
+ // First collect what text we currently have in our internal clipboard. |
+ bool has_internal_text; |
+ std::string internal_text; |
+ |
+ { |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ ClipboardMap::const_iterator it = g_clipboard_map->find(kPlainTextFormat); |
+ if (it != g_clipboard_map->end()) { |
+ has_internal_text = true; |
+ internal_text = it->second; |
+ } else { |
+ has_internal_text = false; |
+ } |
+ } |
+ |
+ if (IsTextAvailableFromAndroid()) { |
+ // Make sure the text in the Android Clipboard matches what we think it |
+ // should be. |
+ ScopedJavaLocalRef<jstring> tmp_string(env, |
+ static_cast<jstring>(env->CallObjectMethod( |
+ env->CallObjectMethod(clipboard_manager_.obj(), get_text_), |
+ to_string_))); |
+ std::string android_text = ConvertJavaStringToUTF8(tmp_string); |
+ |
+ // If the android text doesn't match what we think it should be, our |
+ // internal representation is no longer valid. |
+ if (android_text.compare(internal_text)) |
+ ClearInternalClipboard(); |
+ } else { |
+ // If Android has no text but we have some internal text, our internal |
+ // representation is no longer valid. |
+ if (has_internal_text) |
+ ClearInternalClipboard(); |
+ } |
+} |
+ |
+void Clipboard::Clear() { |
+ JNIEnv* env = AttachCurrentThread(); |
+ env->CallVoidMethod(clipboard_manager_.obj(), set_text_, NULL); |
+ ClearInternalClipboard(); |
+} |
+ |
+void Clipboard::ClearInternalClipboard() const { |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ g_clipboard_map->clear(); |
+} |
+ |
+void Clipboard::Set(const std::string& key, const std::string& value) { |
+ base::AutoLock lock(g_clipboard_map_lock.Get()); |
+ (*g_clipboard_map)[key] = value; |
+} |
+ |
+} // namespace ui |