| 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 |