clipboard_android.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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( 299 SkBitmap::kARGB_8888_Config, size->width(), size->height(), 0); 300 bmp.allocPixels(); 301 302 int bm_size = size->width() * size->height() * 4; 303 DCHECK_EQ(sizeof(gfx::Size) + bm_size, input.size()); 304 305 memcpy(bmp.getPixels(), input.data() + sizeof(gfx::Size), bm_size); 306 } 307 return bmp; 308} 309 310void Clipboard::ReadCustomData(ClipboardType clipboard_type, 311 const string16& type, 312 string16* result) const { 313 DCHECK(CalledOnValidThread()); 314 NOTIMPLEMENTED(); 315} 316 317void Clipboard::ReadBookmark(string16* title, std::string* url) const { 318 DCHECK(CalledOnValidThread()); 319 NOTIMPLEMENTED(); 320} 321 322void Clipboard::ReadData(const Clipboard::FormatType& format, 323 std::string* result) const { 324 DCHECK(CalledOnValidThread()); 325 *result = g_map.Get().Get(format.data()); 326} 327 328// static 329Clipboard::FormatType Clipboard::GetFormatType( 330 const std::string& format_string) { 331 return FormatType::Deserialize(format_string); 332} 333 334// static 335const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { 336 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); 337 return type; 338} 339 340// static 341const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { 342 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); 343 return type; 344} 345 346// static 347const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { 348 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat)); 349 return type; 350} 351 352// static 353const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { 354 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kHTMLFormat)); 355 return type; 356} 357 358// static 359const Clipboard::FormatType& Clipboard::GetRtfFormatType() { 360 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kRTFFormat)); 361 return type; 362} 363 364// static 365const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { 366 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kBitmapFormat)); 367 return type; 368} 369 370// static 371const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { 372 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); 373 return type; 374} 375 376// static 377const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { 378 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData)); 379 return type; 380} 381 382void Clipboard::WriteText(const char* text_data, size_t text_len) { 383 g_map.Get().Set(kPlainTextFormat, std::string(text_data, text_len)); 384} 385 386void Clipboard::WriteHTML(const char* markup_data, 387 size_t markup_len, 388 const char* url_data, 389 size_t url_len) { 390 g_map.Get().Set(kHTMLFormat, std::string(markup_data, markup_len)); 391} 392 393void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { 394 NOTIMPLEMENTED(); 395} 396 397// Note: according to other platforms implementations, this really writes the 398// URL spec. 399void Clipboard::WriteBookmark(const char* title_data, size_t title_len, 400 const char* url_data, size_t url_len) { 401 g_map.Get().Set(kBookmarkFormat, std::string(url_data, url_len)); 402} 403 404// Write an extra flavor that signifies WebKit was the last to modify the 405// pasteboard. This flavor has no data. 406void Clipboard::WriteWebSmartPaste() { 407 g_map.Get().Set(kWebKitSmartPasteFormat, std::string()); 408} 409 410// All platforms use gfx::Size for size data but it is passed as a const char* 411// Further, pixel_data is expected to be 32 bits per pixel 412// Note: we implement this to pass all unit tests but it is currently unclear 413// how some code would consume this. 414void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { 415 const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); 416 int bm_size = size->width() * size->height() * 4; 417 418 std::string packed(size_data, sizeof(gfx::Size)); 419 packed += std::string(pixel_data, bm_size); 420 g_map.Get().Set(kBitmapFormat, packed); 421} 422 423void Clipboard::WriteData(const Clipboard::FormatType& format, 424 const char* data_data, size_t data_len) { 425 g_map.Get().Set(format.data(), std::string(data_data, data_len)); 426} 427 428// See clipboard_android_initialization.h for more information. 429bool RegisterClipboardAndroid(JNIEnv* env) { 430 return RegisterNativesImpl(env); 431} 432 433} // namespace ui 434