Cherry-pick "Android should be able to copy HTML snippets to the system clipboard."

Bug 10863973

Taken from upstream CL 9e7f61b8c (http://crrev.com/231337)

Original description:

Currently the only clipboard data we actually push to the Android system
clipboard is plain-text data, so any formatting and links will be removed
when someone pastes content from Chromium to, for example, Gmail.

In order to push HTML data on the Android clipboard, we do need to know
the plain-text representation so that non-HTML consumers can still use
what's on there (there only is one primary clip). For that reason, we only
push HTML to the Android system clipboard if there also is a plain-text
representation available in the ClipboardMap.

There are a few situations in which we end up calling Clipboard::WriteHTML.
Android doesn't care about Pepper and drag and drop data right now. The
WebClipboardImpl::writeImage() method will write a HTML snippet, but we'll
ignore that because no plain-text data will be pasted. The fourth case is
WebClipboardImpl::writeHTML(), used for DOM fragments and rich content,
which will work fine after this patch.

BUG=214200

Review URL: https://codereview.chromium.org/25668005

Change-Id: I340c9befd8580e6f3de8d308053759054b9a8858
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231337 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ClipboardTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ClipboardTest.java
new file mode 100644
index 0000000..644216d
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ClipboardTest.java
@@ -0,0 +1,97 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.content.ClipboardManager;
+import android.content.ClipData;
+import android.content.Context;
+import android.os.Build;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.content.browser.input.ImeAdapter;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content_shell_apk.ContentShellTestBase;
+
+import java.util.concurrent.Callable;
+
+public class ClipboardTest extends ContentShellTestBase {
+    private static final String TEST_PAGE_DATA_URL = UrlUtils.encodeHtmlDataUri(
+            "<html><body>Hello, <a href=\"http://www.example.com/\">world</a>, how <b> " +
+            "Chromium</b> doing today?</body></html>");
+
+    private static final String EXPECTED_TEXT_RESULT = "Hello, world, how Chromium doing today?";
+
+    // String to search for in the HTML representation on the clipboard.
+    private static final String EXPECTED_HTML_NEEDLE = "http://www.example.com/";
+
+    /**
+     * Tests that copying document fragments will put at least a plain-text representation
+     * of the contents on the clipboard. For Android JellyBean and higher, we also expect
+     * the HTML representation of the fragment to be available.
+     */
+    @LargeTest
+    @Feature({"Clipboard","TextInput"})
+    public void testCopyDocumentFragment() throws Throwable {
+        launchContentShellWithUrl(TEST_PAGE_DATA_URL);
+        assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
+
+        final ClipboardManager clipboardManager = (ClipboardManager)
+                getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+        assertNotNull(clipboardManager);
+
+        // Clear the clipboard to make sure we start with a clean state.
+        clipboardManager.setPrimaryClip(ClipData.newPlainText(null, ""));
+        assertFalse(hasPrimaryClip(clipboardManager));
+
+        getImeAdapter().selectAll();
+        getImeAdapter().copy();
+
+        // Waits until data has been made available on the Android clipboard.
+        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        return hasPrimaryClip(clipboardManager);
+                    }
+                });
+            }
+        }));
+
+        // Verify that the data on the clipboard is what we expect it to be. For Android JB MR2
+        // and higher we expect HTML content, for other versions the plain-text representation.
+        final ClipData clip = clipboardManager.getPrimaryClip();
+        assertEquals(EXPECTED_TEXT_RESULT, clip.getItemAt(0).coerceToText(getActivity()));
+
+        // Android JellyBean and higher should have a HTML representation on the clipboard as well.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            String htmlText = clip.getItemAt(0).getHtmlText();
+
+            assertNotNull(htmlText);
+            assertTrue(htmlText.contains(EXPECTED_HTML_NEEDLE));
+        }
+    }
+
+    private ImeAdapter getImeAdapter() {
+        return getContentViewCore().getImeAdapterForTest();
+    }
+
+    // Returns whether there is a primary clip with content on the current clipboard.
+    private Boolean hasPrimaryClip(ClipboardManager clipboardManager) {
+        final ClipData clip = clipboardManager.getPrimaryClip();
+        if (clip != null && clip.getItemCount() > 0) {
+            return !TextUtils.isEmpty(clip.getItemAt(0).getText());
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/ui/android/java/src/org/chromium/ui/Clipboard.java b/ui/android/java/src/org/chromium/ui/Clipboard.java
index a66fa3d..b4e0c21 100644
--- a/ui/android/java/src/org/chromium/ui/Clipboard.java
+++ b/ui/android/java/src/org/chromium/ui/Clipboard.java
@@ -10,6 +10,7 @@
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.os.Build;
 import android.text.TextUtils;
 
 /**
@@ -90,6 +91,22 @@
     }
 
     /**
+     * Writes HTML to the clipboard, together with a plain-text representation
+     * of that very data. This API is only available in Android JellyBean+ and
+     * will be a no-operation in older versions.
+     *
+     * @param html The HTML content to be pasted to the clipboard.
+     * @param text Plain-text representation of the HTML content.
+     */
+    @CalledByNative
+    private void setHTMLText(final String html, final String text) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            mClipboardManager.setPrimaryClip(
+                    ClipData.newHtmlText(null, text, html));
+        }
+    }
+
+    /**
      * Approximates the behavior of the now-deprecated
      * {@link android.text.ClipboardManager#hasText()}, returning true if and
      * only if the clipboard has a primary clip and that clip contains a plain
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc
index 4eb052a..f5fec47 100644
--- a/ui/base/clipboard/clipboard_android.cc
+++ b/ui/base/clipboard/clipboard_android.cc
@@ -97,10 +97,26 @@
 
   map_[format] = data;
   if (format == kPlainTextFormat) {
-    ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(
-        env, data.c_str());
-    DCHECK(str.obj() && !ClearException(env));
+    ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, data);
+    DCHECK(str.obj());
+
     Java_Clipboard_setText(env, clipboard_manager_.obj(), str.obj());
+  } else if (format == kHTMLFormat) {
+    // Android's API for storing HTML content on the clipboard requires a plain-
+    // text representation to be available as well. ScopedClipboardWriter has a
+    // stable order for setting clipboard data, ensuring that plain-text data
+    // is available first. Do not write to the clipboard when only HTML data is
+    // available, because otherwise others apps may not be able to paste it.
+    if (!ContainsKey(map_, kPlainTextFormat))
+      return;
+
+    ScopedJavaLocalRef<jstring> html = ConvertUTF8ToJavaString(env, data);
+    ScopedJavaLocalRef<jstring> text = ConvertUTF8ToJavaString(
+        env, map_[kPlainTextFormat].c_str());
+
+    DCHECK(html.obj() && text.obj());
+    Java_Clipboard_setHTMLText(
+        env, clipboard_manager_.obj(), html.obj(), text.obj());
   }
 }