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