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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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;
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.
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),
sky 2012/01/19 21:26:56 : should be on a newline.
Peter Beverloo 2012/01/20 15:13:51 Done.
73 set_text_(NULL),
74 has_text_(NULL),
75 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.
76 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
77 DCHECK(env);
78
79 // Get the context
80 jobject context = env->NewLocalRef(base::android::GetApplicationContext());
81
82 if (context == NULL) {
83 // Should be during testing only
84 // Get the ActivityThread class
85 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.
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,
sky 2012/01/19 21:26:56 indent 4 here.
Peter Beverloo 2012/01/20 15:13:51 Done.
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");
sky 2012/01/19 21:26:56 indent 4
Peter Beverloo 2012/01/20 15:13:51 Done.
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");
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
137 DCHECK(context_class);
138 // Get the system service method
139 jmethodID get_system_service =
140 env->GetMethodID(context_class, "getSystemService",
sky 2012/01/19 21:26:56 indent 4
Peter Beverloo 2012/01/20 15:13:51 Done.
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();
sky 2012/01/19 21:26:56 If you really need a lock, use AutoLock here and e
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 {
sky 2012/01/19 21:26:56 Order of method should match that of header.
Peter Beverloo 2012/01/20 15:13:51 Done.
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))
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.
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;
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\">";
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(""));
sky 2012/01/19 21:26:56 No "", just std::string()
Peter Beverloo 2012/01/20 15:13:51 Done.
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.
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];
sky 2012/01/19 21:26:56 scoped_array
Peter Beverloo 2012/01/20 15:13:51 Done.
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)
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).
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,
sky 2012/01/19 21:26:56 each param on its own line.
Peter Beverloo 2012/01/20 15:13:51 Done.
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());
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
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
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
OLDNEW
« 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