OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/base/clipboard/clipboard_android.h" | 5 #include "ui/base/clipboard/clipboard_android.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/android/context_utils.h" | 9 #include "base/android/context_utils.h" |
10 #include "base/android/jni_string.h" | 10 #include "base/android/jni_string.h" |
11 #include "base/android/scoped_java_ref.h" | |
11 #include "base/lazy_instance.h" | 12 #include "base/lazy_instance.h" |
12 #include "base/stl_util.h" | 13 #include "base/stl_util.h" |
13 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
14 #include "base/synchronization/lock.h" | 15 #include "base/synchronization/lock.h" |
16 #include "base/time/time.h" | |
15 #include "jni/Clipboard_jni.h" | 17 #include "jni/Clipboard_jni.h" |
16 #include "third_party/skia/include/core/SkBitmap.h" | 18 #include "third_party/skia/include/core/SkBitmap.h" |
17 #include "ui/gfx/geometry/size.h" | 19 #include "ui/gfx/geometry/size.h" |
18 | 20 |
19 // TODO:(andrewhayden) Support additional formats in Android: Bitmap, URI, HTML, | 21 // TODO:(andrewhayden) Support additional formats in Android: Bitmap, URI, HTML, |
20 // HTML+text now that Android's clipboard system supports them, then nuke the | 22 // HTML+text now that Android's clipboard system supports them, then nuke the |
21 // legacy implementation note below. | 23 // legacy implementation note below. |
22 | 24 |
23 // Legacy implementation note: | 25 // Legacy implementation note: |
24 // The Android clipboard system used to only support text format. So we used the | 26 // The Android clipboard system used to only support text format. So we used the |
25 // Android system when some text was added or retrieved from the system. For | 27 // Android system when some text was added or retrieved from the system. For |
26 // anything else, we STILL store the value in some process wide static | 28 // anything else, we STILL store the value in some process wide static |
27 // variable protected by a lock. So the (non-text) clipboard will only work | 29 // variable protected by a lock. So the (non-text) clipboard will only work |
28 // within the same process. | 30 // within the same process. |
29 | 31 |
30 using base::android::AttachCurrentThread; | 32 using base::android::AttachCurrentThread; |
31 using base::android::ClearException; | 33 using base::android::ClearException; |
32 using base::android::ConvertJavaStringToUTF8; | 34 using base::android::ConvertJavaStringToUTF8; |
33 using base::android::ConvertUTF8ToJavaString; | 35 using base::android::ConvertUTF8ToJavaString; |
34 using base::android::ScopedJavaGlobalRef; | 36 using base::android::ScopedJavaGlobalRef; |
35 using base::android::ScopedJavaLocalRef; | 37 using base::android::ScopedJavaLocalRef; |
36 | 38 |
37 namespace ui { | 39 namespace ui { |
38 | 40 |
39 namespace { | 41 namespace { |
42 | |
40 // Various formats we support. | 43 // Various formats we support. |
41 const char kURLFormat[] = "url"; | 44 const char kURLFormat[] = "url"; |
42 const char kPlainTextFormat[] = "text"; | 45 const char kPlainTextFormat[] = "text"; |
43 const char kHTMLFormat[] = "html"; | 46 const char kHTMLFormat[] = "html"; |
44 const char kRTFFormat[] = "rtf"; | 47 const char kRTFFormat[] = "rtf"; |
45 const char kBitmapFormat[] = "bitmap"; | 48 const char kBitmapFormat[] = "bitmap"; |
46 const char kWebKitSmartPasteFormat[] = "webkit_smart"; | 49 const char kWebKitSmartPasteFormat[] = "webkit_smart"; |
47 const char kBookmarkFormat[] = "bookmark"; | 50 const char kBookmarkFormat[] = "bookmark"; |
48 | 51 |
49 class ClipboardMap { | 52 class ClipboardMap { |
50 public: | 53 public: |
51 ClipboardMap(); | 54 ClipboardMap(); |
52 std::string Get(const std::string& format); | 55 std::string Get(const std::string& format); |
53 int64_t GetLastClipboardChangeTimeInMillis(); | 56 uint64_t GetSequenceNumber() const; |
57 base::Time GetLastModifiedTime() const; | |
58 void ClearLastModifiedTime(); | |
54 bool HasFormat(const std::string& format); | 59 bool HasFormat(const std::string& format); |
60 void OnPrimaryClipboardChanged(); | |
55 void Set(const std::string& format, const std::string& data); | 61 void Set(const std::string& format, const std::string& data); |
56 void CommitToAndroidClipboard(); | 62 void CommitToAndroidClipboard(); |
57 void Clear(); | 63 void Clear(); |
58 | 64 |
59 private: | 65 private: |
60 void UpdateFromAndroidClipboard(); | 66 void UpdateFromAndroidClipboard(); |
61 std::map<std::string, std::string> map_; | 67 std::map<std::string, std::string> map_; |
68 bool map_up_to_date_; | |
62 base::Lock lock_; | 69 base::Lock lock_; |
63 | 70 |
64 int64_t last_clipboard_change_time_ms_; | 71 uint64_t sequence_number_; |
72 base::Time last_modified_time_; | |
65 | 73 |
66 // Java class and methods for the Android ClipboardManager. | 74 // Java class and methods for the Android ClipboardManager. |
67 ScopedJavaGlobalRef<jobject> clipboard_manager_; | 75 ScopedJavaGlobalRef<jobject> clipboard_manager_; |
68 }; | 76 }; |
69 base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; | 77 base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; |
70 | 78 |
71 ClipboardMap::ClipboardMap() { | 79 ClipboardMap::ClipboardMap() { |
72 clipboard_manager_.Reset(Java_Clipboard_getInstance(AttachCurrentThread())); | 80 clipboard_manager_.Reset(Java_Clipboard_getInstance(AttachCurrentThread())); |
73 DCHECK(clipboard_manager_.obj()); | 81 DCHECK(clipboard_manager_.obj()); |
82 map_up_to_date_ = false; | |
dcheng
2017/04/13 20:35:37
Nit: use the initializer list (or use in-class ini
Mark P
2017/04/13 20:54:05
Done.
| |
74 } | 83 } |
75 | 84 |
76 std::string ClipboardMap::Get(const std::string& format) { | 85 std::string ClipboardMap::Get(const std::string& format) { |
77 base::AutoLock lock(lock_); | 86 base::AutoLock lock(lock_); |
78 UpdateFromAndroidClipboard(); | 87 UpdateFromAndroidClipboard(); |
79 std::map<std::string, std::string>::const_iterator it = map_.find(format); | 88 std::map<std::string, std::string>::const_iterator it = map_.find(format); |
80 return it == map_.end() ? std::string() : it->second; | 89 return it == map_.end() ? std::string() : it->second; |
81 } | 90 } |
82 | 91 |
83 int64_t ClipboardMap::GetLastClipboardChangeTimeInMillis() { | 92 uint64_t ClipboardMap::GetSequenceNumber() const { |
84 base::AutoLock lock(lock_); | 93 return sequence_number_; |
85 UpdateFromAndroidClipboard(); | 94 } |
86 return last_clipboard_change_time_ms_; | 95 |
96 base::Time ClipboardMap::GetLastModifiedTime() const { | |
97 return last_modified_time_; | |
98 } | |
99 | |
100 void ClipboardMap::ClearLastModifiedTime() { | |
101 last_modified_time_ = base::Time(); | |
87 } | 102 } |
88 | 103 |
89 bool ClipboardMap::HasFormat(const std::string& format) { | 104 bool ClipboardMap::HasFormat(const std::string& format) { |
90 base::AutoLock lock(lock_); | 105 base::AutoLock lock(lock_); |
91 UpdateFromAndroidClipboard(); | 106 UpdateFromAndroidClipboard(); |
92 return base::ContainsKey(map_, format); | 107 return base::ContainsKey(map_, format); |
93 } | 108 } |
94 | 109 |
110 void ClipboardMap::OnPrimaryClipboardChanged() { | |
111 sequence_number_++; | |
112 last_modified_time_ = base::Time::Now(); | |
113 map_up_to_date_ = false; | |
114 } | |
115 | |
95 void ClipboardMap::Set(const std::string& format, const std::string& data) { | 116 void ClipboardMap::Set(const std::string& format, const std::string& data) { |
96 base::AutoLock lock(lock_); | 117 base::AutoLock lock(lock_); |
97 map_[format] = data; | 118 map_[format] = data; |
119 map_up_to_date_ = false; | |
dcheng
2017/04/13 20:35:37
Can we make this a tri-state enum? Calling UpdateF
Mark P
2017/04/13 20:54:05
Do you mean
MAP_OUT_OF_DATE
MAP_FRESHER_THAN_JAVA
dcheng
2017/04/13 20:59:18
MAP_OUT_OF_DATE
MAP_UP_TO_DATE
MAP_PREPARING_COMMI
Mark P
2017/04/13 21:21:46
Done with the scoped enum. Please take a look.
T
| |
98 } | 120 } |
99 | 121 |
100 void ClipboardMap::CommitToAndroidClipboard() { | 122 void ClipboardMap::CommitToAndroidClipboard() { |
101 JNIEnv* env = AttachCurrentThread(); | 123 JNIEnv* env = AttachCurrentThread(); |
102 base::AutoLock lock(lock_); | 124 base::AutoLock lock(lock_); |
103 if (base::ContainsKey(map_, kHTMLFormat)) { | 125 if (base::ContainsKey(map_, kHTMLFormat)) { |
104 // Android's API for storing HTML content on the clipboard requires a plain- | 126 // Android's API for storing HTML content on the clipboard requires a plain- |
105 // text representation to be available as well. | 127 // text representation to be available as well. |
106 if (!base::ContainsKey(map_, kPlainTextFormat)) | 128 if (!base::ContainsKey(map_, kPlainTextFormat)) |
107 return; | 129 return; |
108 | 130 |
109 ScopedJavaLocalRef<jstring> html = | 131 ScopedJavaLocalRef<jstring> html = |
110 ConvertUTF8ToJavaString(env, map_[kHTMLFormat]); | 132 ConvertUTF8ToJavaString(env, map_[kHTMLFormat]); |
111 ScopedJavaLocalRef<jstring> text = | 133 ScopedJavaLocalRef<jstring> text = |
112 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); | 134 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); |
113 | 135 |
114 DCHECK(html.obj() && text.obj()); | 136 DCHECK(html.obj() && text.obj()); |
115 Java_Clipboard_setHTMLText(env, clipboard_manager_, html, text); | 137 Java_Clipboard_setHTMLText(env, clipboard_manager_, html, text); |
116 } else if (base::ContainsKey(map_, kPlainTextFormat)) { | 138 } else if (base::ContainsKey(map_, kPlainTextFormat)) { |
117 ScopedJavaLocalRef<jstring> str = | 139 ScopedJavaLocalRef<jstring> str = |
118 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); | 140 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); |
119 DCHECK(str.obj()); | 141 DCHECK(str.obj()); |
120 Java_Clipboard_setText(env, clipboard_manager_, str); | 142 Java_Clipboard_setText(env, clipboard_manager_, str); |
121 } else { | 143 } else { |
122 Java_Clipboard_clear(env, clipboard_manager_); | 144 Java_Clipboard_clear(env, clipboard_manager_); |
123 NOTIMPLEMENTED(); | 145 NOTIMPLEMENTED(); |
124 } | 146 } |
147 map_up_to_date_ = true; | |
148 sequence_number_++; | |
149 last_modified_time_ = base::Time::Now(); | |
125 } | 150 } |
126 | 151 |
127 void ClipboardMap::Clear() { | 152 void ClipboardMap::Clear() { |
128 JNIEnv* env = AttachCurrentThread(); | 153 JNIEnv* env = AttachCurrentThread(); |
129 base::AutoLock lock(lock_); | 154 base::AutoLock lock(lock_); |
130 map_.clear(); | 155 map_.clear(); |
131 Java_Clipboard_clear(env, clipboard_manager_); | 156 Java_Clipboard_clear(env, clipboard_manager_); |
157 map_up_to_date_ = true; | |
158 sequence_number_++; | |
159 last_modified_time_ = base::Time::Now(); | |
132 } | 160 } |
133 | 161 |
134 // Add a key:jstr pair to map, but only if jstr is not null, and also | 162 // Add a key:jstr pair to map, but only if jstr is not null, and also |
135 // not empty. | 163 // not empty. |
136 void AddMapEntry(JNIEnv* env, | 164 void AddMapEntry(JNIEnv* env, |
137 std::map<std::string, std::string>* map, | 165 std::map<std::string, std::string>* map, |
138 const char* key, | 166 const char* key, |
139 const ScopedJavaLocalRef<jstring>& jstr) { | 167 const ScopedJavaLocalRef<jstring>& jstr) { |
140 if (!jstr.is_null()) { | 168 if (!jstr.is_null()) { |
141 std::string str = ConvertJavaStringToUTF8(env, jstr.obj()); | 169 std::string str = ConvertJavaStringToUTF8(env, jstr.obj()); |
142 if (!str.empty()) | 170 if (!str.empty()) |
143 (*map)[key] = str; | 171 (*map)[key] = str; |
144 } | 172 } |
145 } | 173 } |
146 | 174 |
147 // Return true if all the key-value pairs in map1 are also in map2. | 175 void ClipboardMap::UpdateFromAndroidClipboard() { |
148 bool MapIsSubset(const std::map<std::string, std::string>& map1, | 176 if (map_up_to_date_) |
149 const std::map<std::string, std::string>& map2) { | 177 return; |
150 for (const auto& val : map1) { | |
151 auto iter = map2.find(val.first); | |
152 if (iter == map2.end() || iter->second != val.second) | |
153 return false; | |
154 } | |
155 return true; | |
156 } | |
157 | 178 |
158 void ClipboardMap::UpdateFromAndroidClipboard() { | 179 // Fetch the current Android clipboard state. |
159 // Fetch the current Android clipboard state. Replace our state with | |
160 // the Android state if the Android state has been changed. | |
161 lock_.AssertAcquired(); | 180 lock_.AssertAcquired(); |
162 JNIEnv* env = AttachCurrentThread(); | 181 JNIEnv* env = AttachCurrentThread(); |
163 | 182 |
164 std::map<std::string, std::string> android_clipboard_state; | |
165 | |
166 ScopedJavaLocalRef<jstring> jtext = | 183 ScopedJavaLocalRef<jstring> jtext = |
167 Java_Clipboard_getCoercedText(env, clipboard_manager_); | 184 Java_Clipboard_getCoercedText(env, clipboard_manager_); |
168 ScopedJavaLocalRef<jstring> jhtml = | 185 ScopedJavaLocalRef<jstring> jhtml = |
169 Java_Clipboard_getHTMLText(env, clipboard_manager_); | 186 Java_Clipboard_getHTMLText(env, clipboard_manager_); |
170 | 187 |
171 AddMapEntry(env, &android_clipboard_state, kPlainTextFormat, jtext); | 188 AddMapEntry(env, &map_, kPlainTextFormat, jtext); |
172 AddMapEntry(env, &android_clipboard_state, kHTMLFormat, jhtml); | 189 AddMapEntry(env, &map_, kHTMLFormat, jhtml); |
173 last_clipboard_change_time_ms_ = | |
174 Java_Clipboard_getClipboardContentChangeTimeInMillis(env, | |
175 clipboard_manager_); | |
176 | 190 |
177 if (!MapIsSubset(android_clipboard_state, map_)) | 191 map_up_to_date_ = true; |
178 android_clipboard_state.swap(map_); | |
179 } | 192 } |
180 | 193 |
181 } // namespace | 194 } // namespace |
182 | 195 |
183 // Clipboard::FormatType implementation. | 196 // Clipboard::FormatType implementation. |
184 Clipboard::FormatType::FormatType() { | 197 Clipboard::FormatType::FormatType() { |
185 } | 198 } |
186 | 199 |
187 Clipboard::FormatType::FormatType(const std::string& native_format) | 200 Clipboard::FormatType::FormatType(const std::string& native_format) |
188 : data_(native_format) { | 201 : data_(native_format) { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
270 return type; | 283 return type; |
271 } | 284 } |
272 | 285 |
273 // Clipboard factory method. | 286 // Clipboard factory method. |
274 // static | 287 // static |
275 Clipboard* Clipboard::Create() { | 288 Clipboard* Clipboard::Create() { |
276 return new ClipboardAndroid; | 289 return new ClipboardAndroid; |
277 } | 290 } |
278 | 291 |
279 // ClipboardAndroid implementation. | 292 // ClipboardAndroid implementation. |
293 | |
294 void ClipboardAndroid::OnPrimaryClipChanged( | |
295 JNIEnv* env, | |
296 const base::android::JavaParamRef<jobject>& obj) { | |
297 g_map.Get().OnPrimaryClipboardChanged(); | |
298 } | |
299 | |
280 ClipboardAndroid::ClipboardAndroid() { | 300 ClipboardAndroid::ClipboardAndroid() { |
281 DCHECK(CalledOnValidThread()); | 301 DCHECK(CalledOnValidThread()); |
282 } | 302 } |
283 | 303 |
284 ClipboardAndroid::~ClipboardAndroid() { | 304 ClipboardAndroid::~ClipboardAndroid() { |
285 DCHECK(CalledOnValidThread()); | 305 DCHECK(CalledOnValidThread()); |
286 } | 306 } |
287 | 307 |
288 void ClipboardAndroid::OnPreShutdown() {} | 308 void ClipboardAndroid::OnPreShutdown() {} |
289 | 309 |
290 uint64_t ClipboardAndroid::GetSequenceNumber(ClipboardType /* type */) const { | 310 uint64_t ClipboardAndroid::GetSequenceNumber(ClipboardType /* type */) const { |
291 DCHECK(CalledOnValidThread()); | 311 DCHECK(CalledOnValidThread()); |
292 // TODO: implement this. For now this interface will advertise | 312 return g_map.Get().GetSequenceNumber(); |
293 // that the clipboard never changes. That's fine as long as we | |
294 // don't rely on this signal. | |
295 return 0; | |
296 } | 313 } |
297 | 314 |
298 bool ClipboardAndroid::IsFormatAvailable(const Clipboard::FormatType& format, | 315 bool ClipboardAndroid::IsFormatAvailable(const Clipboard::FormatType& format, |
299 ClipboardType type) const { | 316 ClipboardType type) const { |
300 DCHECK(CalledOnValidThread()); | 317 DCHECK(CalledOnValidThread()); |
301 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); | 318 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
302 return g_map.Get().HasFormat(format.ToString()); | 319 return g_map.Get().HasFormat(format.ToString()); |
303 } | 320 } |
304 | 321 |
305 void ClipboardAndroid::Clear(ClipboardType type) { | 322 void ClipboardAndroid::Clear(ClipboardType type) { |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
407 DCHECK(CalledOnValidThread()); | 424 DCHECK(CalledOnValidThread()); |
408 NOTIMPLEMENTED(); | 425 NOTIMPLEMENTED(); |
409 } | 426 } |
410 | 427 |
411 void ClipboardAndroid::ReadData(const Clipboard::FormatType& format, | 428 void ClipboardAndroid::ReadData(const Clipboard::FormatType& format, |
412 std::string* result) const { | 429 std::string* result) const { |
413 DCHECK(CalledOnValidThread()); | 430 DCHECK(CalledOnValidThread()); |
414 *result = g_map.Get().Get(format.ToString()); | 431 *result = g_map.Get().Get(format.ToString()); |
415 } | 432 } |
416 | 433 |
417 base::Time ClipboardAndroid::GetClipboardLastModifiedTime() const { | 434 base::Time ClipboardAndroid::GetLastModifiedTime() const { |
418 DCHECK(CalledOnValidThread()); | 435 DCHECK(CalledOnValidThread()); |
419 return base::Time::FromJavaTime( | 436 return g_map.Get().GetLastModifiedTime(); |
420 g_map.Get().GetLastClipboardChangeTimeInMillis()); | 437 } |
438 | |
439 void ClipboardAndroid::ClearLastModifiedTime() { | |
440 DCHECK(CalledOnValidThread()); | |
441 g_map.Get().ClearLastModifiedTime(); | |
421 } | 442 } |
422 | 443 |
423 // Main entry point used to write several values in the clipboard. | 444 // Main entry point used to write several values in the clipboard. |
424 void ClipboardAndroid::WriteObjects(ClipboardType type, | 445 void ClipboardAndroid::WriteObjects(ClipboardType type, |
425 const ObjectMap& objects) { | 446 const ObjectMap& objects) { |
426 DCHECK(CalledOnValidThread()); | 447 DCHECK(CalledOnValidThread()); |
427 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); | 448 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
428 g_map.Get().Clear(); | 449 g_map.Get().Clear(); |
429 | 450 |
430 for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end(); | 451 for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end(); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
478 } | 499 } |
479 g_map.Get().Set(kBitmapFormat, packed); | 500 g_map.Get().Set(kBitmapFormat, packed); |
480 } | 501 } |
481 | 502 |
482 void ClipboardAndroid::WriteData(const Clipboard::FormatType& format, | 503 void ClipboardAndroid::WriteData(const Clipboard::FormatType& format, |
483 const char* data_data, | 504 const char* data_data, |
484 size_t data_len) { | 505 size_t data_len) { |
485 g_map.Get().Set(format.ToString(), std::string(data_data, data_len)); | 506 g_map.Get().Set(format.ToString(), std::string(data_data, data_len)); |
486 } | 507 } |
487 | 508 |
509 bool RegisterClipboardAndroid(JNIEnv* env) { | |
510 return RegisterNativesImpl(env); | |
511 } | |
512 | |
513 // Returns a pointer to the current ClipboardAndroid object. | |
514 static jlong Init(JNIEnv* env, | |
515 const base::android::JavaParamRef<jobject>& obj) { | |
516 return reinterpret_cast<intptr_t>(Clipboard::GetForCurrentThread()); | |
517 } | |
518 | |
488 } // namespace ui | 519 } // namespace ui |
OLD | NEW |