Chromium Code Reviews| 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..ea29737b9d32b51d4ba0e15e81c4395dcf56c197 |
| --- /dev/null |
| +++ b/ui/base/clipboard/clipboard_android.cc |
| @@ -0,0 +1,510 @@ |
| +// 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/scoped_java_ref.h" |
| +#include "base/lazy_instance.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.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. |
| + |
| +// Global contents map and its lock. |
| +using base::android::AttachCurrentThread; |
| +using base::android::CheckException; |
| +using base::android::ClearException; |
| +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* const kPlainTextFormat = "text"; |
|
dcheng
2012/02/01 19:02:04
I recall that in the past, there was a slight pref
Peter Beverloo
2012/02/02 12:16:11
Done.
|
| +const char* const kHTMLFormat = "html"; |
| +const char* const kBitmapFormat = "bitmap"; |
| +const char* const kWebKitSmartPasteFormat = "webkit_smart"; |
| +const char* const kBookmarkFormat = "bookmark"; |
| +const char* const 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() |
| + : clipboard_manager_(NULL), |
| + set_text_(NULL), |
| + has_text_(NULL), |
| + get_text_(NULL), |
| + to_string_(NULL) { |
| + JNIEnv* env = AttachCurrentThread(); |
| + DCHECK(env); |
| + |
| + // Get the context |
| + jobject context = env->NewLocalRef(base::android::GetApplicationContext()); |
| + |
| + if (!context) { |
| + // Should be during testing only |
| + // Get the ActivityThread class |
| + ScopedJavaLocalRef<jclass> activity_thread_class(env, |
| + env->FindClass("android/app/ActivityThread")); |
| + DCHECK(activity_thread_class.obj()); |
| + |
| + // Try to get the current activity thread. |
| + jmethodID current_activity_method_id = |
| + env->GetStaticMethodID(activity_thread_class.obj(), |
| + "currentActivityThread", |
| + "()Landroid/app/ActivityThread;"); |
| + DCHECK(current_activity_method_id); |
| + jobject current_activity = |
| + env->CallStaticObjectMethod(activity_thread_class.obj(), |
| + current_activity_method_id); |
| + if (ClearException(env)) |
| + current_activity = NULL; |
| + |
| + if (!current_activity) { |
| + // There is no current activity, create one |
| + ScopedJavaLocalRef<jclass> looper_class(env, |
| + env->FindClass("android/os/Looper")); |
| + jmethodID prepare_method_id = env->GetStaticMethodID(looper_class.obj(), |
| + "prepareMainLooper", "()V"); |
| + env->CallStaticVoidMethod(looper_class.obj(), prepare_method_id); |
| + CheckException(env); |
| + |
| + jmethodID system_main_method_id = |
| + env->GetStaticMethodID(activity_thread_class.obj(), "systemMain", |
| + "()Landroid/app/ActivityThread;"); |
| + DCHECK(system_main_method_id); |
| + |
| + current_activity = env->CallStaticObjectMethod( |
| + activity_thread_class.obj(), system_main_method_id); |
| + DCHECK(current_activity); |
| + CheckException(env); |
| + } |
| + |
| + // Get the context |
| + jmethodID get_system_context_id = |
| + env->GetMethodID(activity_thread_class.obj(), |
| + "getSystemContext", "()Landroid/app/ContextImpl;"); |
| + |
| + DCHECK(get_system_context_id); |
| + context = env->CallObjectMethod(current_activity, |
| + get_system_context_id); |
| + DCHECK(context); |
| + |
| + env->DeleteLocalRef(current_activity); |
| + } |
| + |
| + // Get the context class |
| + jclass context_class = env->FindClass("android/content/Context"); |
| + DCHECK(context_class); |
| + // Get the system service method |
| + jmethodID get_system_service = |
| + env->GetMethodID(context_class, "getSystemService", |
| + "(Ljava/lang/String;)Ljava/lang/Object;"); |
| + DCHECK(get_system_service); |
| + env->DeleteLocalRef(context_class); |
| + |
| + // Retrieve the system service |
| + jstring service_name = env->NewStringUTF("clipboard"); |
| + jobject cm = env->CallObjectMethod(context, get_system_service, service_name); |
| + if (ClearException(env)) |
| + cm = NULL; |
| + DCHECK(cm); |
| + |
| + // Make a global reference for our clipboard manager. |
| + clipboard_manager_ = env->NewGlobalRef(cm); |
| + DCHECK(clipboard_manager_); |
| + env->DeleteLocalRef(cm); |
| + |
| + // Retain a few methods we'll keep using |
| + jclass clipboard_class = env->FindClass("android/text/ClipboardManager"); |
| + DCHECK(clipboard_class); |
| + set_text_ = env->GetMethodID(clipboard_class, "setText", |
| + "(Ljava/lang/CharSequence;)V"); |
| + DCHECK(set_text_); |
| + has_text_ = env->GetMethodID(clipboard_class, "hasText", "()Z"); |
| + DCHECK(has_text_); |
| + get_text_ = env->GetMethodID(clipboard_class, "getText", |
| + "()Ljava/lang/CharSequence;"); |
| + DCHECK(get_text_); |
| + |
| + // Will need to call toString as CharSequence is not always a String |
| + jclass charsequence_class = env->FindClass("java/lang/CharSequence"); |
| + DCHECK(charsequence_class); |
| + to_string_ = env->GetMethodID(charsequence_class, "toString", |
| + "()Ljava/lang/String;"); |
| + DCHECK(to_string_); |
| + env->DeleteLocalRef(clipboard_class); |
| + env->DeleteLocalRef(charsequence_class); |
| + |
| + // Finally cleanup all our local reference so they don't stay around if this |
| + // code was never called from Java. (unit test case) |
| + env->DeleteLocalRef(service_name); |
| + env->DeleteLocalRef(context); |
| + |
| + // Create the object map if we are the first clipboard |
| + g_clipboard_map_lock.Get().Acquire(); |
| + if (!g_clipboard_map) |
| + g_clipboard_map = new ClipboardMap; |
| + g_clipboard_map_lock.Get().Release(); |
| +} |
| + |
| +Clipboard::~Clipboard() { |
| + // Delete the clipboard manager global ref |
| + if (clipboard_manager_) { |
| + JNIEnv* env = AttachCurrentThread(); |
| + env->DeleteGlobalRef(clipboard_manager_); |
| + } |
| +} |
| + |
| +// 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_, has_text_)) { |
| + jstring tmp_string = |
| + static_cast<jstring>(env->CallObjectMethod( |
| + env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); |
| + jboolean is_copy = JNI_FALSE; |
| + const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); |
| + jsize len = env->GetStringUTFLength(tmp_string); |
| + UTF8ToUTF16(tmp_string_val, len, result); |
| + env->ReleaseStringUTFChars(tmp_string, tmp_string_val); |
| + env->DeleteLocalRef(static_cast<jobject>(tmp_string)); |
| + } |
| +} |
| + |
| +void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, |
| + std::string* result) const { |
| + JNIEnv* env = AttachCurrentThread(); |
| + |
| + result->clear(); |
| + if (env->CallBooleanMethod(clipboard_manager_, has_text_)) { |
| + jstring tmp_string = |
| + static_cast<jstring>(env->CallObjectMethod( |
| + env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); |
| + jboolean is_copy = JNI_FALSE; |
| + const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); |
| + jsize len = env->GetStringUTFLength(tmp_string); |
| + *result = std::string(tmp_string_val, len); |
| + env->ReleaseStringUTFChars(tmp_string, tmp_string_val); |
| + env->DeleteLocalRef(static_cast<jobject>(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(); |
| + |
| + g_clipboard_map_lock.Get().Acquire(); |
| + ClipboardMap::const_iterator it = g_clipboard_map->find(kHTMLFormat); |
| + if (it != g_clipboard_map->end()) |
| + input = it->second; |
| + g_clipboard_map_lock.Get().Release(); |
| + |
| + 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(); |
| + |
| + g_clipboard_map_lock.Get().Acquire(); |
| + ClipboardMap::const_iterator it = g_clipboard_map->find(format.ToString()); |
| + if (it != g_clipboard_map->end()) |
| + result->assign(it->second); |
| + g_clipboard_map_lock.Get().Release(); |
| +} |
| + |
| +// 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); |
| + jstring str = env->NewStringUTF(data.c_str()); |
| + DCHECK(str); |
| + |
| + env->CallVoidMethod(clipboard_manager_, set_text_, str); |
| + env->DeleteLocalRef(str); |
| + |
| + // 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, (unsigned char*)&n, sizeof(int)); |
|
dcheng
2012/02/01 19:02:04
Are these casts necessary? It may be (slightly) sa
Peter Beverloo
2012/02/02 12:16:11
Done.
|
| + p += sizeof(int); |
| + n = size->height(); |
| + memcpy(p, (unsigned char*)&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_, 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; |
| + g_clipboard_map_lock.Get().Acquire(); |
| + 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; |
| + } |
| + g_clipboard_map_lock.Get().Release(); |
| + |
| + if (IsTextAvailableFromAndroid()) { |
| + // Make sure the text in the Android Clipboard matches what we think it |
| + // should be. |
| + jstring tmp_string = |
| + static_cast<jstring>(env->CallObjectMethod( |
| + env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); |
| + jboolean is_copy = JNI_FALSE; |
| + const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); |
| + jsize len = env->GetStringUTFLength(tmp_string); |
| + |
| + std::string android_text(tmp_string_val, len); |
| + |
| + // 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(); |
| + |
| + env->ReleaseStringUTFChars(tmp_string, tmp_string_val); |
| + env->DeleteLocalRef(static_cast<jobject>(tmp_string)); |
| + } 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_, 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 |