Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(453)

Unified Diff: ui/base/clipboard/clipboard_android.cc

Issue 9264014: Upstream the clipboard implementation for Android (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Another clean-up Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..d9e8c8fe9ea0ef3829223535dec8b9336b3bae74
--- /dev/null
+++ b/ui/base/clipboard/clipboard_android.cc
@@ -0,0 +1,544 @@
+// 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/lazy_instance.h"
+#include "base/logging.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;
+
+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.
+typedef std::map<std::string, std::string> ClipboardMap;
+ClipboardMap* g_clipboard_map = NULL;
+base::LazyInstance<base::Lock> g_clipboard_map_lock = LAZY_INSTANCE_INITIALIZER;
sky 2012/01/19 21:26:56 Are we really using Clipboard on mutliple threads
Peter Beverloo 2012/02/01 14:43:29 Added a TODO.
+
+// Various format we support
+const char* const kPlainTextFormat = "text";
+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),
sky 2012/01/19 21:26:56 : should be on a newline.
Peter Beverloo 2012/01/20 15:13:51 Done.
+ set_text_(NULL),
+ has_text_(NULL),
+ get_text_(NULL) {
sky 2012/01/19 21:26:56 YOu didn't initialize to_string_ here.
Peter Beverloo 2012/01/20 15:13:51 Done.
+ JNIEnv* env = AttachCurrentThread();
sky 2012/01/19 21:26:56 Is there a corresponding Detach?
Peter Beverloo 2012/01/25 18:19:06 Yes, but a thread only needs to call this before i
+ DCHECK(env);
+
+ // Get the context
+ jobject context = env->NewLocalRef(base::android::GetApplicationContext());
+
+ if (context == NULL) {
+ // Should be during testing only
+ // Get the ActivityThread class
+ jclass activity_thread_class = env->FindClass("android/app/ActivityThread");
sky 2012/01/19 21:26:56 How about a scoped object that does the DeleteLoca
Peter Beverloo 2012/01/25 18:19:06 Done.
+ DCHECK(activity_thread_class);
+
+ // Try to get the current activity thread.
+ jmethodID current_activity_method_id =
+ env->GetStaticMethodID(activity_thread_class,
sky 2012/01/19 21:26:56 indent 4 here.
Peter Beverloo 2012/01/20 15:13:51 Done.
+ "currentActivityThread",
+ "()Landroid/app/ActivityThread;");
+ DCHECK(current_activity_method_id);
+ jobject current_activity =
+ env->CallStaticObjectMethod(activity_thread_class,
+ current_activity_method_id);
+ if (ClearException(env))
+ current_activity = NULL;
+
+ if (!current_activity) {
+ // There is no current activity, create one
+
+ jclass looper_class = env->FindClass("android/os/Looper");
+ jmethodID prepare_method_id =
+ env->GetStaticMethodID(looper_class, "prepareMainLooper", "()V");
sky 2012/01/19 21:26:56 indent 4
Peter Beverloo 2012/01/20 15:13:51 Done.
+ env->CallStaticVoidMethod(looper_class, prepare_method_id);
+ CheckException(env);
+ env->DeleteLocalRef(looper_class);
+
+ jmethodID system_main_method_id =
+ env->GetStaticMethodID(activity_thread_class, "systemMain",
+ "()Landroid/app/ActivityThread;");
+ DCHECK(system_main_method_id);
+
+ current_activity = env->CallStaticObjectMethod(activity_thread_class,
+ system_main_method_id);
+ DCHECK(current_activity);
+ CheckException(env);
+ }
+
+ // Get the context
+ jmethodID get_system_context_id =
+ env->GetMethodID(activity_thread_class, "getSystemContext",
+ "()Landroid/app/ContextImpl;");
+ env->DeleteLocalRef(activity_thread_class);
+
+ 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");
sky 2012/01/19 21:26:56 Is it worth a single method that takes the class n
Peter Beverloo 2012/01/25 18:19:06 Work on such utility methods (and more robust way
+ DCHECK(context_class);
+ // Get the system service method
+ jmethodID get_system_service =
+ env->GetMethodID(context_class, "getSystemService",
sky 2012/01/19 21:26:56 indent 4
Peter Beverloo 2012/01/20 15:13:51 Done.
+ "(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();
sky 2012/01/19 21:26:56 If you really need a lock, use AutoLock here and e
+ 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);
+ }
+}
+
+void Clipboard::ClearInternalClipboard() const {
sky 2012/01/19 21:26:56 Order of method should match that of header.
Peter Beverloo 2012/01/20 15:13:51 Done.
+ base::AutoLock lock(g_clipboard_map_lock.Get());
+ g_clipboard_map->clear();
+}
+
+void Clipboard::Clear() {
+ JNIEnv* env = AttachCurrentThread();
+ env->CallVoidMethod(clipboard_manager_, set_text_, NULL);
+ ClearInternalClipboard();
+}
+
+void Clipboard::Set(const std::string& key, const std::string& value) {
+ base::AutoLock lock(g_clipboard_map_lock.Get());
+ (*g_clipboard_map)[key] = value;
+}
+
+void Clipboard::WriteText(const char* text_data, size_t text_len) {
+ // Write the text in the Android Clipboard
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+ char buff[255];
+ char* ptr;
+
+ if (text_len < (sizeof(buff) - 1))
sky 2012/01/19 21:26:56 I don't think this is a performance critical secti
Peter Beverloo 2012/01/20 15:13:51 Replaced with dcheng's std::string() suggestion.
+ ptr = buff;
+ else
+ ptr = new char[text_len+1];
+
+ memcpy(ptr, text_data, text_len);
+ ptr[text_len] = '\0';
+
+ jstring str = env->NewStringUTF(ptr);
+ DCHECK(str);
+
+ env->CallVoidMethod(clipboard_manager_, set_text_, str);
+ env->DeleteLocalRef(str);
+
+ if (ptr != buff)
+ delete [] ptr;
+
+ // 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) {
+ static const char* html_prefix = "<meta http-equiv=\"content-type\" "
+ "content=\"text/html; charset=utf-8\">";
+
+ std::string value(html_prefix);
+ value.append(std::string(markup_data, markup_len));
+ Set(kHTMLFormat, value);
+}
+
+// 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(""));
sky 2012/01/19 21:26:56 No "", just std::string()
Peter Beverloo 2012/01/20 15:13:51 Done.
+}
+
+// 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;
+ char* tmp = new char[total_size];
sky 2012/01/19 21:26:56 scoped_array
Peter Beverloo 2012/01/20 15:13:51 Done.
+
+ char* p = tmp;
+ int n = size->width();
+ memcpy(p, (unsigned char*)&n, sizeof(int));
+ 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(tmp, total_size));
+
+ delete [] tmp;
+}
+
+// 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));
+}
+
+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();
+ }
+}
+
+bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format,
+ Clipboard::Buffer buffer) const {
+ if (buffer != BUFFER_STANDARD)
+ return false;
+
+ 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,
sky 2012/01/19 21:26:56 each param on its own line.
Peter Beverloo 2012/01/20 15:13:51 Done.
+ 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 {
+ SkBitmap bmp;
+
+ ValidateInternalClipboard();
+
+ g_clipboard_map_lock.Get().Acquire();
+ ClipboardMap::const_iterator it = g_clipboard_map->find(kBitmapFormat);
+ if (it != g_clipboard_map->end() && !it->second.empty()) {
+ bmp.lockPixels();
+ memcpy(bmp.getPixels(), it->second.c_str(), it->second.length());
sky 2012/01/19 21:26:56 Does this really work? How does bmp know the size
Peter Beverloo 2012/02/01 14:43:29 Stubbed out the method and added a call to NOTIMPL
+ bmp.unlockPixels();
+
+ // TODO: We currently do not remember the width and height of the bitmap,
+ // so we cannot create a SkBitmap config here.
+ NOTIMPLEMENTED();
+ }
+ g_clipboard_map_lock.Get().Release();
+
+ return bmp;
+}
+
+void Clipboard::ReadData(const Clipboard::FormatType& format,
+ std::string* result) const {
+ result->clear();
+
+ // If ReadData is called for some text, just use the basic call
+ if (!format.compare(kPlainTextFormat)) {
+ ReadAsciiText(BUFFER_STANDARD, result);
+ return;
+ }
+
+ 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();
+}
+
+void Clipboard::ReadCustomData(Buffer buffer,
+ const string16& type,
+ string16* result) const {
+ NOTIMPLEMENTED();
+}
+
+void Clipboard::ReadBookmark(string16* title, std::string* url) const {
+ NOTIMPLEMENTED();
+}
+
+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;
+}
+
+// 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::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::GetWebKitSmartPasteFormatType() {
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat));
+ return type;
+}
+
+// static
+const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() {
+ CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData));
+ return type;
+}
+
+} // namespace ui
« ui/base/clipboard/clipboard.h ('K') | « ui/base/clipboard/clipboard.h ('k') | ui/ui.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698