clipboard_android.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 "base/android/jni_string.h" 8#include "base/lazy_instance.h" 9#include "base/stl_util.h" 10#include "base/strings/utf_string_conversions.h" 11#include "base/synchronization/lock.h" 12#include "jni/Clipboard_jni.h" 13#include "third_party/skia/include/core/SkBitmap.h" 14#include "ui/base/clipboard/clipboard_android_initialization.h" 15#include "ui/gfx/size.h" 16 17// TODO:(andrewhayden) Support additional formats in Android: Bitmap, URI, HTML, 18// HTML+text now that Android's clipboard system supports them, then nuke the 19// legacy implementation note below. 20 21// Legacy implementation note: 22// The Android clipboard system used to only support text format. So we used the 23// Android system when some text was added or retrieved from the system. For 24// anything else, we STILL store the value in some process wide static 25// variable protected by a lock. So the (non-text) clipboard will only work 26// within the same process. 27 28using base::android::AttachCurrentThread; 29using base::android::ClearException; 30using base::android::ConvertJavaStringToUTF8; 31using base::android::ConvertUTF8ToJavaString; 32using base::android::ScopedJavaGlobalRef; 33using base::android::ScopedJavaLocalRef; 34 35namespace ui { 36 37namespace { 38// Various formats we support. 39const char kPlainTextFormat[] = "text"; 40const char kHTMLFormat[] = "html"; 41const char kRTFFormat[] = "rtf"; 42const char kBitmapFormat[] = "bitmap"; 43const char kWebKitSmartPasteFormat[] = "webkit_smart"; 44const char kBookmarkFormat[] = "bookmark"; 45const char kMimeTypePepperCustomData[] = "chromium/x-pepper-custom-data"; 46const char kMimeTypeWebCustomData[] = "chromium/x-web-custom-data"; 47 48class ClipboardMap { 49 public: 50 ClipboardMap(); 51 std::string Get(const std::string& format); 52 bool HasFormat(const std::string& format); 53 void Set(const std::string& format, const std::string& data); 54 void Clear(); 55 56 private: 57 void SyncWithAndroidClipboard(); 58 std::map<std::string, std::string> map_; 59 base::Lock lock_; 60 61 // Java class and methods for the Android ClipboardManager. 62 ScopedJavaGlobalRef<jobject> clipboard_manager_; 63}; 64base::LazyInstance<ClipboardMap>::Leaky g_map = LAZY_INSTANCE_INITIALIZER; 65 66ClipboardMap::ClipboardMap() { 67 JNIEnv* env = AttachCurrentThread(); 68 DCHECK(env); 69 70 // Get the context. 71 jobject context = base::android::GetApplicationContext(); 72 DCHECK(context); 73 74 ScopedJavaLocalRef<jobject> local_ref = 75 Java_Clipboard_create(env, context); 76 DCHECK(local_ref.obj()); 77 clipboard_manager_.Reset(env, local_ref.Release()); 78} 79 80std::string ClipboardMap::Get(const std::string& format) { 81 base::AutoLock lock(lock_); 82 SyncWithAndroidClipboard(); 83 std::map<std::string, std::string>::const_iterator it = map_.find(format); 84 return it == map_.end() ? std::string() : it->second; 85} 86 87bool ClipboardMap::HasFormat(const std::string& format) { 88 base::AutoLock lock(lock_); 89 SyncWithAndroidClipboard(); 90 return ContainsKey(map_, format); 91} 92 93void ClipboardMap::Set(const std::string& format, const std::string& data) { 94 JNIEnv* env = AttachCurrentThread(); 95 base::AutoLock lock(lock_); 96 SyncWithAndroidClipboard(); 97 98 map_[format] = data; 99 if (format == kPlainTextFormat) { 100 ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, data); 101 DCHECK(str.obj()); 102 103 Java_Clipboard_setText(env, clipboard_manager_.obj(), str.obj()); 104 } else if (format == kHTMLFormat) { 105 // Android's API for storing HTML content on the clipboard requires a plain- 106 // text representation to be available as well. ScopedClipboardWriter has a 107 // stable order for setting clipboard data, ensuring that plain-text data 108 // is available first. Do not write to the clipboard when only HTML data is 109 // available, because otherwise others apps may not be able to paste it. 110 if (!ContainsKey(map_, kPlainTextFormat)) 111 return; 112 113 ScopedJavaLocalRef<jstring> html = ConvertUTF8ToJavaString(env, data); 114 ScopedJavaLocalRef<jstring> text = ConvertUTF8ToJavaString( 115 env, map_[kPlainTextFormat].c_str()); 116 117 DCHECK(html.obj() && text.obj()); 118 Java_Clipboard_setHTMLText( 119 env, clipboard_manager_.obj(), html.obj(), text.obj()); 120 } 121} 122 123void ClipboardMap::Clear() { 124 JNIEnv* env = AttachCurrentThread(); 125 base::AutoLock lock(lock_); 126 map_.clear(); 127 Java_Clipboard_setText(env, clipboard_manager_.obj(), NULL); 128} 129 130// If the internal map contains a plain-text entry and it does not match that 131// in the Android clipboard, clear the map and insert the Android text into it. 132void ClipboardMap::SyncWithAndroidClipboard() { 133 lock_.AssertAcquired(); 134 JNIEnv* env = AttachCurrentThread(); 135 136 std::map<std::string, std::string>::const_iterator it = 137 map_.find(kPlainTextFormat); 138 139 if (!Java_Clipboard_hasPlainText(env, clipboard_manager_.obj())) { 140 if (it != map_.end()) 141 // We have plain text on this side, but Android doesn't. Nuke ours. 142 map_.clear(); 143 return; 144 } 145 146 ScopedJavaLocalRef<jstring> java_string = 147 Java_Clipboard_getCoercedText(env, clipboard_manager_.obj()); 148 149 if (!java_string.obj()) { 150 // Tolerate a null value from the Java side, even though that should not 151 // happen since hasPlainText has already returned true. 152 // Should only happen if someone is using the clipboard on multiple 153 // threads and clears it out after hasPlainText but before we get here... 154 if (it != map_.end()) 155 // We have plain text on this side, but Android doesn't. Nuke ours. 156 map_.clear(); 157 return; 158 } 159 160 // If Android text differs from ours (or we have none), then copy Android's. 161 std::string android_string = ConvertJavaStringToUTF8(java_string); 162 if (it == map_.end() || it->second != android_string) { 163 map_.clear(); 164 map_[kPlainTextFormat] = android_string; 165 } 166} 167 168} // namespace 169 170Clipboard::FormatType::FormatType() { 171} 172 173Clipboard::FormatType::FormatType(const std::string& native_format) 174 : data_(native_format) { 175} 176 177Clipboard::FormatType::~FormatType() { 178} 179 180std::string Clipboard::FormatType::Serialize() const { 181 return data_; 182} 183 184// static 185Clipboard::FormatType Clipboard::FormatType::Deserialize( 186 const std::string& serialization) { 187 return FormatType(serialization); 188} 189 190bool Clipboard::FormatType::Equals(const FormatType& other) const { 191 return data_ == other.data_; 192} 193 194Clipboard::Clipboard() { 195 DCHECK(CalledOnValidThread()); 196} 197 198Clipboard::~Clipboard() { 199 DCHECK(CalledOnValidThread()); 200} 201 202// Main entry point used to write several values in the clipboard. 203void Clipboard::WriteObjects(ClipboardType type, const ObjectMap& objects) { 204 DCHECK(CalledOnValidThread()); 205 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 206 g_map.Get().Clear(); 207 for (ObjectMap::const_iterator iter = objects.begin(); 208 iter != objects.end(); ++iter) { 209 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); 210 } 211} 212 213uint64 Clipboard::GetSequenceNumber(ClipboardType /* type */) { 214 DCHECK(CalledOnValidThread()); 215 // TODO: implement this. For now this interface will advertise 216 // that the clipboard never changes. That's fine as long as we 217 // don't rely on this signal. 218 return 0; 219} 220 221bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, 222 ClipboardType type) const { 223 DCHECK(CalledOnValidThread()); 224 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 225 return g_map.Get().HasFormat(format.data()); 226} 227 228void Clipboard::Clear(ClipboardType type) { 229 DCHECK(CalledOnValidThread()); 230 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 231 g_map.Get().Clear(); 232} 233 234void Clipboard::ReadAvailableTypes(ClipboardType type, 235 std::vector<string16>* types, 236 bool* contains_filenames) const { 237 DCHECK(CalledOnValidThread()); 238 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 239 240 if (!types || !contains_filenames) { 241 NOTREACHED(); 242 return; 243 } 244 245 NOTIMPLEMENTED(); 246 247 types->clear(); 248 *contains_filenames = false; 249} 250 251void Clipboard::ReadText(ClipboardType type, string16* result) const { 252 DCHECK(CalledOnValidThread()); 253 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 254 std::string utf8; 255 ReadAsciiText(type, &utf8); 256 *result = UTF8ToUTF16(utf8); 257} 258 259void Clipboard::ReadAsciiText(ClipboardType type, std::string* result) const { 260 DCHECK(CalledOnValidThread()); 261 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 262 *result = g_map.Get().Get(kPlainTextFormat); 263} 264 265// Note: |src_url| isn't really used. It is only implemented in Windows 266void Clipboard::ReadHTML(ClipboardType type, 267 string16* markup, 268 std::string* src_url, 269 uint32* fragment_start, 270 uint32* fragment_end) const { 271 DCHECK(CalledOnValidThread()); 272 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 273 if (src_url) 274 src_url->clear(); 275 276 std::string input = g_map.Get().Get(kHTMLFormat); 277 *markup = UTF8ToUTF16(input); 278 279 *fragment_start = 0; 280 *fragment_end = static_cast<uint32>(markup->length()); 281} 282 283void Clipboard::ReadRTF(ClipboardType type, std::string* result) const { 284 DCHECK(CalledOnValidThread()); 285 NOTIMPLEMENTED(); 286} 287 288SkBitmap Clipboard::ReadImage(ClipboardType type) const { 289 DCHECK(CalledOnValidThread()); 290 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 291 std::string input = g_map.Get().Get(kBitmapFormat); 292 293 SkBitmap bmp; 294 if (!input.empty()) { 295 DCHECK_LE(sizeof(gfx::Size), input.size()); 296 const gfx::Size* size = reinterpret_cast<const gfx::Size*>(input.data()); 297 298 bmp.setConfig(SkBitmap::kARGB_8888_Config, size->width(), size->height()); 299 bmp.allocPixels(); 300 301 DCHECK_EQ(sizeof(gfx::Size) + bmp.getSize(), input.size()); 302 303 memcpy(bmp.getPixels(), input.data() + sizeof(gfx::Size), bmp.getSize()); 304 } 305 return bmp; 306} 307 308void Clipboard::ReadCustomData(ClipboardType clipboard_type, 309 const string16& type, 310 string16* result) const { 311 DCHECK(CalledOnValidThread()); 312 NOTIMPLEMENTED(); 313} 314 315void Clipboard::ReadBookmark(string16* title, std::string* url) const { 316 DCHECK(CalledOnValidThread()); 317 NOTIMPLEMENTED(); 318} 319 320void Clipboard::ReadData(const Clipboard::FormatType& format, 321 std::string* result) const { 322 DCHECK(CalledOnValidThread()); 323 *result = g_map.Get().Get(format.data()); 324} 325 326// static 327Clipboard::FormatType Clipboard::GetFormatType( 328 const std::string& format_string) { 329 return FormatType::Deserialize(format_string); 330} 331 332// static 333const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { 334 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); 335 return type; 336} 337 338// static 339const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { 340 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); 341 return type; 342} 343 344// static 345const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { 346 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat)); 347 return type; 348} 349 350// static 351const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { 352 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kHTMLFormat)); 353 return type; 354} 355 356// static 357const Clipboard::FormatType& Clipboard::GetRtfFormatType() { 358 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kRTFFormat)); 359 return type; 360} 361 362// static 363const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { 364 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kBitmapFormat)); 365 return type; 366} 367 368// static 369const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { 370 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); 371 return type; 372} 373 374// static 375const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { 376 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData)); 377 return type; 378} 379 380void Clipboard::WriteText(const char* text_data, size_t text_len) { 381 g_map.Get().Set(kPlainTextFormat, std::string(text_data, text_len)); 382} 383 384void Clipboard::WriteHTML(const char* markup_data, 385 size_t markup_len, 386 const char* url_data, 387 size_t url_len) { 388 g_map.Get().Set(kHTMLFormat, std::string(markup_data, markup_len)); 389} 390 391void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { 392 NOTIMPLEMENTED(); 393} 394 395// Note: according to other platforms implementations, this really writes the 396// URL spec. 397void Clipboard::WriteBookmark(const char* title_data, size_t title_len, 398 const char* url_data, size_t url_len) { 399 g_map.Get().Set(kBookmarkFormat, std::string(url_data, url_len)); 400} 401 402// Write an extra flavor that signifies WebKit was the last to modify the 403// pasteboard. This flavor has no data. 404void Clipboard::WriteWebSmartPaste() { 405 g_map.Get().Set(kWebKitSmartPasteFormat, std::string()); 406} 407 408// Note: we implement this to pass all unit tests but it is currently unclear 409// how some code would consume this. 410void Clipboard::WriteBitmap(const SkBitmap& bitmap) { 411 gfx::Size size(bitmap.width(), bitmap.height()); 412 413 std::string packed(reinterpret_cast<const char*>(&size), sizeof(size)); 414 { 415 SkAutoLockPixels bitmap_lock(bitmap); 416 packed += std::string(static_cast<const char*>(bitmap.getPixels()), 417 bitmap.getSize()); 418 } 419 g_map.Get().Set(kBitmapFormat, packed); 420} 421 422void Clipboard::WriteData(const Clipboard::FormatType& format, 423 const char* data_data, size_t data_len) { 424 g_map.Get().Set(format.data(), std::string(data_data, data_len)); 425} 426 427// See clipboard_android_initialization.h for more information. 428bool RegisterClipboardAndroid(JNIEnv* env) { 429 return RegisterNativesImpl(env); 430} 431 432} // namespace ui 433