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