OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ui/base/clipboard/clipboard.h" |
| 6 |
| 7 #include <jni.h> |
| 8 |
| 9 #include "base/android/jni_android.h" |
| 10 #include "base/android/jni_string.h" |
| 11 #include "base/lazy_instance.h" |
| 12 #include "base/logging.h" |
| 13 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/synchronization/lock.h" |
| 15 #include "base/utf_string_conversions.h" |
| 16 #include "third_party/skia/include/core/SkBitmap.h" |
| 17 #include "ui/gfx/size.h" |
| 18 |
| 19 // Important note: |
| 20 // Android's clipboard system only supports text content, so use it only when |
| 21 // text is added to or retrieved from the system. For other data types, store |
| 22 // the value in a map. This has the consequence that the clipboard's contents |
| 23 // will only be available within the current process. |
| 24 |
| 25 using base::android::AttachCurrentThread; |
| 26 using base::android::CheckException; |
| 27 using base::android::ClearException; |
| 28 using base::android::ConvertJavaStringToUTF16; |
| 29 using base::android::ConvertJavaStringToUTF8; |
| 30 using base::android::GetClass; |
| 31 using base::android::GetMethodID; |
| 32 using base::android::ScopedJavaLocalRef; |
| 33 |
| 34 namespace ui { |
| 35 |
| 36 namespace { |
| 37 // As Android only supports text in the clipboard, the following map will be |
| 38 // used for other kinds of data. Use the lock to make this thread-safe. |
| 39 // TODO(beverloo): http://crbug.com/112286 Investigate whether the locks in |
| 40 // this file are required. |
| 41 typedef std::map<std::string, std::string> ClipboardMap; |
| 42 ClipboardMap* g_clipboard_map = NULL; |
| 43 base::LazyInstance<base::Lock> g_clipboard_map_lock = LAZY_INSTANCE_INITIALIZER; |
| 44 |
| 45 // Various format we support. |
| 46 const char kPlainTextFormat[] = "text"; |
| 47 const char kHTMLFormat[] = "html"; |
| 48 const char kBitmapFormat[] = "bitmap"; |
| 49 const char kWebKitSmartPasteFormat[] = "webkit_smart"; |
| 50 const char kBookmarkFormat[] = "bookmark"; |
| 51 const char kMimeTypeWebCustomData[] = "chromium/x-web-custom-data"; |
| 52 |
| 53 } // namespace |
| 54 |
| 55 Clipboard::FormatType::FormatType() { |
| 56 } |
| 57 |
| 58 Clipboard::FormatType::FormatType(const std::string& native_format) |
| 59 : data_(native_format) { |
| 60 } |
| 61 |
| 62 Clipboard::FormatType::~FormatType() { |
| 63 } |
| 64 |
| 65 std::string Clipboard::FormatType::Serialize() const { |
| 66 return data_; |
| 67 } |
| 68 |
| 69 // static |
| 70 Clipboard::FormatType Clipboard::FormatType::Deserialize( |
| 71 const std::string& serialization) { |
| 72 return FormatType(serialization); |
| 73 } |
| 74 |
| 75 bool Clipboard::FormatType::Equals(const FormatType& other) const { |
| 76 return data_ == other.data_; |
| 77 } |
| 78 |
| 79 // The clipboard object on the Android platform is simply wrapping the Java |
| 80 // object for the text data format. For non-text format, a global map is used. |
| 81 Clipboard::Clipboard() : set_text_(NULL), has_text_(NULL), get_text_(NULL) { |
| 82 JNIEnv* env = AttachCurrentThread(); |
| 83 DCHECK(env); |
| 84 |
| 85 // Get the context. |
| 86 // We have need a ScopedJavaLocalRef to clean-up the local ref if we have to |
| 87 // create the Context. |
| 88 jobject context = base::android::GetApplicationContext(); |
| 89 ScopedJavaLocalRef<jobject> scoped_context; |
| 90 if (!context) { |
| 91 // Should be during testing only. |
| 92 // Get the ActivityThread class. |
| 93 ScopedJavaLocalRef<jclass> activity_thread_class = |
| 94 GetClass(env, "android/app/ActivityThread"); |
| 95 |
| 96 // Try to get the current activity thread. |
| 97 jmethodID current_activity_method_id = GetMethodID( |
| 98 env, activity_thread_class, |
| 99 "currentActivityThread", "()Landroid/app/ActivityThread;"); |
| 100 |
| 101 ScopedJavaLocalRef<jobject> current_activity(env, |
| 102 env->CallStaticObjectMethod(activity_thread_class.obj(), |
| 103 current_activity_method_id)); |
| 104 ClearException(env); |
| 105 |
| 106 if (!current_activity.obj()) { |
| 107 // There is no current activity, create one. |
| 108 ScopedJavaLocalRef<jclass> looper_class = |
| 109 GetClass(env, "android/os/Looper"); |
| 110 jmethodID prepare_method_id = |
| 111 GetStaticMethodID(env, looper_class, "prepareMainLooper", "()V"); |
| 112 env->CallStaticVoidMethod(looper_class.obj(), prepare_method_id); |
| 113 CheckException(env); |
| 114 |
| 115 jmethodID system_main_method_id = |
| 116 GetStaticMethodID(env, activity_thread_class, "systemMain", |
| 117 "()Landroid/app/ActivityThread;"); |
| 118 |
| 119 current_activity.Reset(env, env->CallStaticObjectMethod( |
| 120 activity_thread_class.obj(), system_main_method_id)); |
| 121 DCHECK(current_activity.obj()); |
| 122 CheckException(env); |
| 123 } |
| 124 |
| 125 // Get the context. |
| 126 jmethodID get_system_context_id = GetMethodID(env, activity_thread_class, |
| 127 "getSystemContext", "()Landroid/app/ContextImpl;"); |
| 128 scoped_context.Reset(env, env->CallObjectMethod(current_activity.obj(), |
| 129 get_system_context_id)); |
| 130 context = scoped_context.obj(); |
| 131 DCHECK(context); |
| 132 } |
| 133 |
| 134 // Get the context class. |
| 135 ScopedJavaLocalRef<jclass> context_class = |
| 136 GetClass(env, "android/content/Context"); |
| 137 // Get the system service method. |
| 138 jmethodID get_system_service = GetMethodID(env, context_class, |
| 139 "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); |
| 140 |
| 141 // Retrieve the system service. |
| 142 ScopedJavaLocalRef<jstring> service_name(env, env->NewStringUTF("clipboard")); |
| 143 clipboard_manager_.Reset(env, env->CallObjectMethod(context, |
| 144 get_system_service, service_name.obj())); |
| 145 ClearException(env); |
| 146 DCHECK(clipboard_manager_.obj()); |
| 147 |
| 148 // Retain a few methods we'll keep using. |
| 149 ScopedJavaLocalRef<jclass> clipboard_class = |
| 150 GetClass(env, "android/text/ClipboardManager"); |
| 151 set_text_ = GetMethodID(env, clipboard_class, |
| 152 "setText", "(Ljava/lang/CharSequence;)V"); |
| 153 has_text_ = GetMethodID(env, clipboard_class, "hasText", "()Z"); |
| 154 get_text_ = GetMethodID(env, clipboard_class, |
| 155 "getText", "()Ljava/lang/CharSequence;"); |
| 156 |
| 157 // Will need to call toString as CharSequence is not always a String. |
| 158 ScopedJavaLocalRef<jclass> charsequence_class = |
| 159 GetClass(env, "java/lang/CharSequence"); |
| 160 to_string_ = GetMethodID(env, charsequence_class, |
| 161 "toString", "()Ljava/lang/String;"); |
| 162 |
| 163 // Create the object map if we are the first clipboard |
| 164 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 165 if (!g_clipboard_map) |
| 166 g_clipboard_map = new ClipboardMap; |
| 167 } |
| 168 |
| 169 Clipboard::~Clipboard() { |
| 170 } |
| 171 |
| 172 // Main entry point used to write several values in the clipboard. |
| 173 void Clipboard::WriteObjects(const ObjectMap& objects) { |
| 174 Clear(); |
| 175 for (ObjectMap::const_iterator iter = objects.begin(); |
| 176 iter != objects.end(); ++iter) { |
| 177 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); |
| 178 } |
| 179 } |
| 180 |
| 181 uint64 Clipboard::GetSequenceNumber(Clipboard::Buffer /* buffer */) { |
| 182 // TODO: Implement this. For now this interface will advertise |
| 183 // that the clipboard never changes. That's fine as long as we |
| 184 // don't rely on this signal. |
| 185 return 0; |
| 186 } |
| 187 |
| 188 bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, |
| 189 Clipboard::Buffer buffer) const { |
| 190 DCHECK_EQ(buffer, BUFFER_STANDARD); |
| 191 |
| 192 if (!format.compare(kPlainTextFormat)) |
| 193 return IsTextAvailableFromAndroid(); |
| 194 |
| 195 ValidateInternalClipboard(); |
| 196 |
| 197 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 198 return g_clipboard_map->find(format.ToString()) != g_clipboard_map->end(); |
| 199 } |
| 200 |
| 201 void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, |
| 202 bool* contains_filenames) const { |
| 203 if (!types || !contains_filenames) { |
| 204 NOTREACHED(); |
| 205 return; |
| 206 } |
| 207 |
| 208 // This is unimplemented on the other platforms (e.g. win, linux). |
| 209 NOTIMPLEMENTED(); |
| 210 |
| 211 types->clear(); |
| 212 *contains_filenames = false; |
| 213 } |
| 214 |
| 215 void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { |
| 216 JNIEnv* env = AttachCurrentThread(); |
| 217 |
| 218 result->clear(); |
| 219 if (!env->CallBooleanMethod(clipboard_manager_.obj(), has_text_)) |
| 220 return; |
| 221 |
| 222 ScopedJavaLocalRef<jobject> char_seq_text(env, |
| 223 env->CallObjectMethod(clipboard_manager_.obj(), get_text_)); |
| 224 ScopedJavaLocalRef<jstring> tmp_string(env, |
| 225 static_cast<jstring>(env->CallObjectMethod(char_seq_text.obj(), |
| 226 to_string_))); |
| 227 *result = ConvertJavaStringToUTF16(tmp_string); |
| 228 } |
| 229 |
| 230 void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, |
| 231 std::string* result) const { |
| 232 JNIEnv* env = AttachCurrentThread(); |
| 233 |
| 234 result->clear(); |
| 235 if (!env->CallBooleanMethod(clipboard_manager_.obj(), has_text_)) |
| 236 return; |
| 237 |
| 238 ScopedJavaLocalRef<jobject> char_seq_text(env, |
| 239 env->CallObjectMethod(clipboard_manager_.obj(), get_text_)); |
| 240 ScopedJavaLocalRef<jstring> tmp_string(env, |
| 241 static_cast<jstring>(env->CallObjectMethod(char_seq_text.obj(), |
| 242 to_string_))); |
| 243 *result = ConvertJavaStringToUTF8(tmp_string); |
| 244 } |
| 245 |
| 246 // Note: |src_url| isn't really used. It is only implemented in Windows. |
| 247 void Clipboard::ReadHTML(Clipboard::Buffer buffer, |
| 248 string16* markup, |
| 249 std::string* src_url, |
| 250 uint32* fragment_start, |
| 251 uint32* fragment_end) const { |
| 252 markup->clear(); |
| 253 if (src_url) |
| 254 src_url->clear(); |
| 255 *fragment_start = 0; |
| 256 *fragment_end = 0; |
| 257 |
| 258 std::string input; |
| 259 |
| 260 ValidateInternalClipboard(); |
| 261 |
| 262 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 263 ClipboardMap::const_iterator it = g_clipboard_map->find(kHTMLFormat); |
| 264 if (it != g_clipboard_map->end()) |
| 265 input = it->second; |
| 266 |
| 267 if (input.empty()) |
| 268 return; |
| 269 |
| 270 *fragment_end = static_cast<uint32>(input.length()); |
| 271 |
| 272 UTF8ToUTF16(input.c_str(), input.length(), markup); |
| 273 } |
| 274 |
| 275 SkBitmap Clipboard::ReadImage(Buffer buffer) const { |
| 276 NOTIMPLEMENTED(); |
| 277 return SkBitmap(); |
| 278 } |
| 279 |
| 280 void Clipboard::ReadCustomData(Buffer buffer, |
| 281 const string16& type, |
| 282 string16* result) const { |
| 283 NOTIMPLEMENTED(); |
| 284 } |
| 285 |
| 286 void Clipboard::ReadBookmark(string16* title, std::string* url) const { |
| 287 NOTIMPLEMENTED(); |
| 288 } |
| 289 |
| 290 void Clipboard::ReadData(const Clipboard::FormatType& format, |
| 291 std::string* result) const { |
| 292 result->clear(); |
| 293 |
| 294 ValidateInternalClipboard(); |
| 295 |
| 296 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 297 ClipboardMap::const_iterator it = g_clipboard_map->find(format.ToString()); |
| 298 if (it != g_clipboard_map->end()) |
| 299 result->assign(it->second); |
| 300 } |
| 301 |
| 302 // static |
| 303 Clipboard::FormatType Clipboard::GetFormatType( |
| 304 const std::string& format_string) { |
| 305 return FormatType::Deserialize(format_string); |
| 306 } |
| 307 |
| 308 // static |
| 309 const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { |
| 310 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); |
| 311 return type; |
| 312 } |
| 313 |
| 314 // static |
| 315 const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { |
| 316 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); |
| 317 return type; |
| 318 } |
| 319 |
| 320 // static |
| 321 const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { |
| 322 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat)); |
| 323 return type; |
| 324 } |
| 325 |
| 326 // static |
| 327 const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { |
| 328 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kHTMLFormat)); |
| 329 return type; |
| 330 } |
| 331 |
| 332 // static |
| 333 const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { |
| 334 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kBitmapFormat)); |
| 335 return type; |
| 336 } |
| 337 |
| 338 // static |
| 339 const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { |
| 340 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); |
| 341 return type; |
| 342 } |
| 343 |
| 344 void Clipboard::WriteText(const char* text_data, size_t text_len) { |
| 345 // Write the text in the Android Clipboard. |
| 346 JNIEnv* env = AttachCurrentThread(); |
| 347 DCHECK(env); |
| 348 |
| 349 std::string data(text_data, text_len); |
| 350 ScopedJavaLocalRef<jstring> str(env, env->NewStringUTF(data.c_str())); |
| 351 DCHECK(str.obj() && !ClearException(env)); |
| 352 |
| 353 env->CallVoidMethod(clipboard_manager_.obj(), set_text_, str.obj()); |
| 354 |
| 355 // Then write it in our internal data structure. We keep it there to check if |
| 356 // another app performed a copy. See ValidateInternalClipboard(). |
| 357 Set(kPlainTextFormat, std::string(text_data, text_len)); |
| 358 } |
| 359 |
| 360 void Clipboard::WriteHTML(const char* markup_data, |
| 361 size_t markup_len, |
| 362 const char* url_data, |
| 363 size_t url_len) { |
| 364 Set(kHTMLFormat, std::string(markup_data, markup_len)); |
| 365 } |
| 366 |
| 367 // Note: according to other platforms implementations, this really writes the |
| 368 // URL spec. |
| 369 void Clipboard::WriteBookmark(const char* title_data, size_t title_len, |
| 370 const char* url_data, size_t url_len) { |
| 371 Set(kBookmarkFormat, std::string(url_data, url_len)); |
| 372 } |
| 373 |
| 374 // Write an extra flavor that signifies WebKit was the last to modify the |
| 375 // pasteboard. This flavor has no data. |
| 376 void Clipboard::WriteWebSmartPaste() { |
| 377 Set(kWebKitSmartPasteFormat, std::string()); |
| 378 } |
| 379 |
| 380 // All platforms use gfx::Size for size data but it is passed as a const char* |
| 381 // Further, pixel_data is expected to be 32 bits per pixel. |
| 382 // Note: we implement this to pass all unit tests but it is currently unclear |
| 383 // how some code would consume this. |
| 384 void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { |
| 385 const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); |
| 386 int bm_size = size->width() * size->height() * 4; |
| 387 int total_size = (sizeof(int) * 2) + bm_size; |
| 388 scoped_array<char> buffer(new char[total_size]); |
| 389 |
| 390 char* p = buffer.get(); |
| 391 int n = size->width(); |
| 392 memcpy(p, &n, sizeof(int)); |
| 393 p += sizeof(int); |
| 394 n = size->height(); |
| 395 memcpy(p, &n, sizeof(int)); |
| 396 p += sizeof(int); |
| 397 memcpy(p, pixel_data, bm_size); |
| 398 |
| 399 Set(kBitmapFormat, std::string(buffer.get(), total_size)); |
| 400 } |
| 401 |
| 402 void Clipboard::WriteData(const Clipboard::FormatType& format, |
| 403 const char* data_data, size_t data_len) { |
| 404 Set(format.ToString(), std::string(data_data, data_len)); |
| 405 } |
| 406 |
| 407 bool Clipboard::IsTextAvailableFromAndroid() const { |
| 408 JNIEnv* env = AttachCurrentThread(); |
| 409 return env->CallBooleanMethod(clipboard_manager_.obj(), has_text_); |
| 410 } |
| 411 |
| 412 void Clipboard::ValidateInternalClipboard() const { |
| 413 JNIEnv* env = AttachCurrentThread(); |
| 414 |
| 415 // First collect what text we currently have in our internal clipboard. |
| 416 bool has_internal_text; |
| 417 std::string internal_text; |
| 418 |
| 419 { |
| 420 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 421 ClipboardMap::const_iterator it = g_clipboard_map->find(kPlainTextFormat); |
| 422 if (it != g_clipboard_map->end()) { |
| 423 has_internal_text = true; |
| 424 internal_text = it->second; |
| 425 } else { |
| 426 has_internal_text = false; |
| 427 } |
| 428 } |
| 429 |
| 430 if (IsTextAvailableFromAndroid()) { |
| 431 // Make sure the text in the Android Clipboard matches what we think it |
| 432 // should be. |
| 433 ScopedJavaLocalRef<jstring> tmp_string(env, |
| 434 static_cast<jstring>(env->CallObjectMethod( |
| 435 env->CallObjectMethod(clipboard_manager_.obj(), get_text_), |
| 436 to_string_))); |
| 437 std::string android_text = ConvertJavaStringToUTF8(tmp_string); |
| 438 |
| 439 // If the android text doesn't match what we think it should be, our |
| 440 // internal representation is no longer valid. |
| 441 if (android_text.compare(internal_text)) |
| 442 ClearInternalClipboard(); |
| 443 } else { |
| 444 // If Android has no text but we have some internal text, our internal |
| 445 // representation is no longer valid. |
| 446 if (has_internal_text) |
| 447 ClearInternalClipboard(); |
| 448 } |
| 449 } |
| 450 |
| 451 void Clipboard::Clear() { |
| 452 JNIEnv* env = AttachCurrentThread(); |
| 453 env->CallVoidMethod(clipboard_manager_.obj(), set_text_, NULL); |
| 454 ClearInternalClipboard(); |
| 455 } |
| 456 |
| 457 void Clipboard::ClearInternalClipboard() const { |
| 458 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 459 g_clipboard_map->clear(); |
| 460 } |
| 461 |
| 462 void Clipboard::Set(const std::string& key, const std::string& value) { |
| 463 base::AutoLock lock(g_clipboard_map_lock.Get()); |
| 464 (*g_clipboard_map)[key] = value; |
| 465 } |
| 466 |
| 467 } // namespace ui |
OLD | NEW |