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: |
| 66 enum class MapState { |
| 67 kOutOfDate, |
| 68 kUpToDate, |
| 69 kPreparingCommit, |
| 70 }; |
| 71 |
60 void UpdateFromAndroidClipboard(); | 72 void UpdateFromAndroidClipboard(); |
61 std::map<std::string, std::string> map_; | 73 std::map<std::string, std::string> map_; |
| 74 MapState map_state_; |
62 base::Lock lock_; | 75 base::Lock lock_; |
63 | 76 |
64 int64_t last_clipboard_change_time_ms_; | 77 uint64_t sequence_number_; |
| 78 base::Time last_modified_time_; |
65 | 79 |
66 // Java class and methods for the Android ClipboardManager. | 80 // Java class and methods for the Android ClipboardManager. |
67 ScopedJavaGlobalRef<jobject> clipboard_manager_; | 81 ScopedJavaGlobalRef<jobject> clipboard_manager_; |
68 }; | 82 }; |
69 base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; | 83 base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; |
70 | 84 |
71 ClipboardMap::ClipboardMap() { | 85 ClipboardMap::ClipboardMap() : map_state_(MapState::kOutOfDate) { |
72 clipboard_manager_.Reset(Java_Clipboard_getInstance(AttachCurrentThread())); | 86 clipboard_manager_.Reset(Java_Clipboard_getInstance(AttachCurrentThread())); |
73 DCHECK(clipboard_manager_.obj()); | 87 DCHECK(clipboard_manager_.obj()); |
74 } | 88 } |
75 | 89 |
76 std::string ClipboardMap::Get(const std::string& format) { | 90 std::string ClipboardMap::Get(const std::string& format) { |
77 base::AutoLock lock(lock_); | 91 base::AutoLock lock(lock_); |
78 UpdateFromAndroidClipboard(); | 92 UpdateFromAndroidClipboard(); |
79 std::map<std::string, std::string>::const_iterator it = map_.find(format); | 93 std::map<std::string, std::string>::const_iterator it = map_.find(format); |
80 return it == map_.end() ? std::string() : it->second; | 94 return it == map_.end() ? std::string() : it->second; |
81 } | 95 } |
82 | 96 |
83 int64_t ClipboardMap::GetLastClipboardChangeTimeInMillis() { | 97 uint64_t ClipboardMap::GetSequenceNumber() const { |
84 base::AutoLock lock(lock_); | 98 return sequence_number_; |
85 UpdateFromAndroidClipboard(); | 99 } |
86 return last_clipboard_change_time_ms_; | 100 |
| 101 base::Time ClipboardMap::GetLastModifiedTime() const { |
| 102 return last_modified_time_; |
| 103 } |
| 104 |
| 105 void ClipboardMap::ClearLastModifiedTime() { |
| 106 last_modified_time_ = base::Time(); |
87 } | 107 } |
88 | 108 |
89 bool ClipboardMap::HasFormat(const std::string& format) { | 109 bool ClipboardMap::HasFormat(const std::string& format) { |
90 base::AutoLock lock(lock_); | 110 base::AutoLock lock(lock_); |
91 UpdateFromAndroidClipboard(); | 111 UpdateFromAndroidClipboard(); |
92 return base::ContainsKey(map_, format); | 112 return base::ContainsKey(map_, format); |
93 } | 113 } |
94 | 114 |
| 115 void ClipboardMap::OnPrimaryClipboardChanged() { |
| 116 sequence_number_++; |
| 117 last_modified_time_ = base::Time::Now(); |
| 118 map_state_ = MapState::kOutOfDate; |
| 119 } |
| 120 |
95 void ClipboardMap::Set(const std::string& format, const std::string& data) { | 121 void ClipboardMap::Set(const std::string& format, const std::string& data) { |
96 base::AutoLock lock(lock_); | 122 base::AutoLock lock(lock_); |
97 map_[format] = data; | 123 map_[format] = data; |
| 124 map_state_ = MapState::kPreparingCommit; |
98 } | 125 } |
99 | 126 |
100 void ClipboardMap::CommitToAndroidClipboard() { | 127 void ClipboardMap::CommitToAndroidClipboard() { |
101 JNIEnv* env = AttachCurrentThread(); | 128 JNIEnv* env = AttachCurrentThread(); |
102 base::AutoLock lock(lock_); | 129 base::AutoLock lock(lock_); |
103 if (base::ContainsKey(map_, kHTMLFormat)) { | 130 if (base::ContainsKey(map_, kHTMLFormat)) { |
104 // Android's API for storing HTML content on the clipboard requires a plain- | 131 // Android's API for storing HTML content on the clipboard requires a plain- |
105 // text representation to be available as well. | 132 // text representation to be available as well. |
106 if (!base::ContainsKey(map_, kPlainTextFormat)) | 133 if (!base::ContainsKey(map_, kPlainTextFormat)) |
107 return; | 134 return; |
108 | 135 |
109 ScopedJavaLocalRef<jstring> html = | 136 ScopedJavaLocalRef<jstring> html = |
110 ConvertUTF8ToJavaString(env, map_[kHTMLFormat]); | 137 ConvertUTF8ToJavaString(env, map_[kHTMLFormat]); |
111 ScopedJavaLocalRef<jstring> text = | 138 ScopedJavaLocalRef<jstring> text = |
112 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); | 139 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); |
113 | 140 |
114 DCHECK(html.obj() && text.obj()); | 141 DCHECK(html.obj() && text.obj()); |
115 Java_Clipboard_setHTMLText(env, clipboard_manager_, html, text); | 142 Java_Clipboard_setHTMLText(env, clipboard_manager_, html, text); |
116 } else if (base::ContainsKey(map_, kPlainTextFormat)) { | 143 } else if (base::ContainsKey(map_, kPlainTextFormat)) { |
117 ScopedJavaLocalRef<jstring> str = | 144 ScopedJavaLocalRef<jstring> str = |
118 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); | 145 ConvertUTF8ToJavaString(env, map_[kPlainTextFormat]); |
119 DCHECK(str.obj()); | 146 DCHECK(str.obj()); |
120 Java_Clipboard_setText(env, clipboard_manager_, str); | 147 Java_Clipboard_setText(env, clipboard_manager_, str); |
121 } else { | 148 } else { |
122 Java_Clipboard_clear(env, clipboard_manager_); | 149 Java_Clipboard_clear(env, clipboard_manager_); |
123 NOTIMPLEMENTED(); | 150 NOTIMPLEMENTED(); |
124 } | 151 } |
| 152 map_state_ = MapState::kUpToDate; |
| 153 sequence_number_++; |
| 154 last_modified_time_ = base::Time::Now(); |
125 } | 155 } |
126 | 156 |
127 void ClipboardMap::Clear() { | 157 void ClipboardMap::Clear() { |
128 JNIEnv* env = AttachCurrentThread(); | 158 JNIEnv* env = AttachCurrentThread(); |
129 base::AutoLock lock(lock_); | 159 base::AutoLock lock(lock_); |
130 map_.clear(); | 160 map_.clear(); |
131 Java_Clipboard_clear(env, clipboard_manager_); | 161 Java_Clipboard_clear(env, clipboard_manager_); |
| 162 map_state_ = MapState::kUpToDate; |
| 163 sequence_number_++; |
| 164 last_modified_time_ = base::Time::Now(); |
132 } | 165 } |
133 | 166 |
134 // Add a key:jstr pair to map, but only if jstr is not null, and also | 167 // Add a key:jstr pair to map, but only if jstr is not null, and also |
135 // not empty. | 168 // not empty. |
136 void AddMapEntry(JNIEnv* env, | 169 void AddMapEntry(JNIEnv* env, |
137 std::map<std::string, std::string>* map, | 170 std::map<std::string, std::string>* map, |
138 const char* key, | 171 const char* key, |
139 const ScopedJavaLocalRef<jstring>& jstr) { | 172 const ScopedJavaLocalRef<jstring>& jstr) { |
140 if (!jstr.is_null()) { | 173 if (!jstr.is_null()) { |
141 std::string str = ConvertJavaStringToUTF8(env, jstr.obj()); | 174 std::string str = ConvertJavaStringToUTF8(env, jstr.obj()); |
142 if (!str.empty()) | 175 if (!str.empty()) |
143 (*map)[key] = str; | 176 (*map)[key] = str; |
144 } | 177 } |
145 } | 178 } |
146 | 179 |
147 // Return true if all the key-value pairs in map1 are also in map2. | 180 void ClipboardMap::UpdateFromAndroidClipboard() { |
148 bool MapIsSubset(const std::map<std::string, std::string>& map1, | 181 DCHECK_NE(MapState::kPreparingCommit, map_state_); |
149 const std::map<std::string, std::string>& map2) { | 182 if (map_state_ == MapState::kUpToDate) |
150 for (const auto& val : map1) { | 183 return; |
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 | 184 |
158 void ClipboardMap::UpdateFromAndroidClipboard() { | 185 // 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(); | 186 lock_.AssertAcquired(); |
162 JNIEnv* env = AttachCurrentThread(); | 187 JNIEnv* env = AttachCurrentThread(); |
163 | 188 |
164 std::map<std::string, std::string> android_clipboard_state; | |
165 | |
166 ScopedJavaLocalRef<jstring> jtext = | 189 ScopedJavaLocalRef<jstring> jtext = |
167 Java_Clipboard_getCoercedText(env, clipboard_manager_); | 190 Java_Clipboard_getCoercedText(env, clipboard_manager_); |
168 ScopedJavaLocalRef<jstring> jhtml = | 191 ScopedJavaLocalRef<jstring> jhtml = |
169 Java_Clipboard_getHTMLText(env, clipboard_manager_); | 192 Java_Clipboard_getHTMLText(env, clipboard_manager_); |
170 | 193 |
171 AddMapEntry(env, &android_clipboard_state, kPlainTextFormat, jtext); | 194 AddMapEntry(env, &map_, kPlainTextFormat, jtext); |
172 AddMapEntry(env, &android_clipboard_state, kHTMLFormat, jhtml); | 195 AddMapEntry(env, &map_, kHTMLFormat, jhtml); |
173 last_clipboard_change_time_ms_ = | |
174 Java_Clipboard_getClipboardContentChangeTimeInMillis(env, | |
175 clipboard_manager_); | |
176 | 196 |
177 if (!MapIsSubset(android_clipboard_state, map_)) | 197 map_state_ = MapState::kUpToDate; |
178 android_clipboard_state.swap(map_); | |
179 } | 198 } |
180 | 199 |
181 } // namespace | 200 } // namespace |
182 | 201 |
183 // Clipboard::FormatType implementation. | 202 // Clipboard::FormatType implementation. |
184 Clipboard::FormatType::FormatType() { | 203 Clipboard::FormatType::FormatType() { |
185 } | 204 } |
186 | 205 |
187 Clipboard::FormatType::FormatType(const std::string& native_format) | 206 Clipboard::FormatType::FormatType(const std::string& native_format) |
188 : data_(native_format) { | 207 : data_(native_format) { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
270 return type; | 289 return type; |
271 } | 290 } |
272 | 291 |
273 // Clipboard factory method. | 292 // Clipboard factory method. |
274 // static | 293 // static |
275 Clipboard* Clipboard::Create() { | 294 Clipboard* Clipboard::Create() { |
276 return new ClipboardAndroid; | 295 return new ClipboardAndroid; |
277 } | 296 } |
278 | 297 |
279 // ClipboardAndroid implementation. | 298 // ClipboardAndroid implementation. |
| 299 |
| 300 void ClipboardAndroid::OnPrimaryClipChanged( |
| 301 JNIEnv* env, |
| 302 const base::android::JavaParamRef<jobject>& obj) { |
| 303 g_map.Get().OnPrimaryClipboardChanged(); |
| 304 } |
| 305 |
280 ClipboardAndroid::ClipboardAndroid() { | 306 ClipboardAndroid::ClipboardAndroid() { |
281 DCHECK(CalledOnValidThread()); | 307 DCHECK(CalledOnValidThread()); |
282 } | 308 } |
283 | 309 |
284 ClipboardAndroid::~ClipboardAndroid() { | 310 ClipboardAndroid::~ClipboardAndroid() { |
285 DCHECK(CalledOnValidThread()); | 311 DCHECK(CalledOnValidThread()); |
286 } | 312 } |
287 | 313 |
288 void ClipboardAndroid::OnPreShutdown() {} | 314 void ClipboardAndroid::OnPreShutdown() {} |
289 | 315 |
290 uint64_t ClipboardAndroid::GetSequenceNumber(ClipboardType /* type */) const { | 316 uint64_t ClipboardAndroid::GetSequenceNumber(ClipboardType /* type */) const { |
291 DCHECK(CalledOnValidThread()); | 317 DCHECK(CalledOnValidThread()); |
292 // TODO: implement this. For now this interface will advertise | 318 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 } | 319 } |
297 | 320 |
298 bool ClipboardAndroid::IsFormatAvailable(const Clipboard::FormatType& format, | 321 bool ClipboardAndroid::IsFormatAvailable(const Clipboard::FormatType& format, |
299 ClipboardType type) const { | 322 ClipboardType type) const { |
300 DCHECK(CalledOnValidThread()); | 323 DCHECK(CalledOnValidThread()); |
301 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); | 324 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
302 return g_map.Get().HasFormat(format.ToString()); | 325 return g_map.Get().HasFormat(format.ToString()); |
303 } | 326 } |
304 | 327 |
305 void ClipboardAndroid::Clear(ClipboardType type) { | 328 void ClipboardAndroid::Clear(ClipboardType type) { |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
407 DCHECK(CalledOnValidThread()); | 430 DCHECK(CalledOnValidThread()); |
408 NOTIMPLEMENTED(); | 431 NOTIMPLEMENTED(); |
409 } | 432 } |
410 | 433 |
411 void ClipboardAndroid::ReadData(const Clipboard::FormatType& format, | 434 void ClipboardAndroid::ReadData(const Clipboard::FormatType& format, |
412 std::string* result) const { | 435 std::string* result) const { |
413 DCHECK(CalledOnValidThread()); | 436 DCHECK(CalledOnValidThread()); |
414 *result = g_map.Get().Get(format.ToString()); | 437 *result = g_map.Get().Get(format.ToString()); |
415 } | 438 } |
416 | 439 |
417 base::Time ClipboardAndroid::GetClipboardLastModifiedTime() const { | 440 base::Time ClipboardAndroid::GetLastModifiedTime() const { |
418 DCHECK(CalledOnValidThread()); | 441 DCHECK(CalledOnValidThread()); |
419 return base::Time::FromJavaTime( | 442 return g_map.Get().GetLastModifiedTime(); |
420 g_map.Get().GetLastClipboardChangeTimeInMillis()); | 443 } |
| 444 |
| 445 void ClipboardAndroid::ClearLastModifiedTime() { |
| 446 DCHECK(CalledOnValidThread()); |
| 447 g_map.Get().ClearLastModifiedTime(); |
421 } | 448 } |
422 | 449 |
423 // Main entry point used to write several values in the clipboard. | 450 // Main entry point used to write several values in the clipboard. |
424 void ClipboardAndroid::WriteObjects(ClipboardType type, | 451 void ClipboardAndroid::WriteObjects(ClipboardType type, |
425 const ObjectMap& objects) { | 452 const ObjectMap& objects) { |
426 DCHECK(CalledOnValidThread()); | 453 DCHECK(CalledOnValidThread()); |
427 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); | 454 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
428 g_map.Get().Clear(); | 455 g_map.Get().Clear(); |
429 | 456 |
430 for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end(); | 457 for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end(); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 } | 505 } |
479 g_map.Get().Set(kBitmapFormat, packed); | 506 g_map.Get().Set(kBitmapFormat, packed); |
480 } | 507 } |
481 | 508 |
482 void ClipboardAndroid::WriteData(const Clipboard::FormatType& format, | 509 void ClipboardAndroid::WriteData(const Clipboard::FormatType& format, |
483 const char* data_data, | 510 const char* data_data, |
484 size_t data_len) { | 511 size_t data_len) { |
485 g_map.Get().Set(format.ToString(), std::string(data_data, data_len)); | 512 g_map.Get().Set(format.ToString(), std::string(data_data, data_len)); |
486 } | 513 } |
487 | 514 |
| 515 bool RegisterClipboardAndroid(JNIEnv* env) { |
| 516 return RegisterNativesImpl(env); |
| 517 } |
| 518 |
| 519 // Returns a pointer to the current ClipboardAndroid object. |
| 520 static jlong Init(JNIEnv* env, |
| 521 const base::android::JavaParamRef<jobject>& obj) { |
| 522 return reinterpret_cast<intptr_t>(Clipboard::GetForCurrentThread()); |
| 523 } |
| 524 |
488 } // namespace ui | 525 } // namespace ui |
OLD | NEW |