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