Index: ui/android/java/src/org/chromium/ui/base/Clipboard.java |
diff --git a/ui/android/java/src/org/chromium/ui/base/Clipboard.java b/ui/android/java/src/org/chromium/ui/base/Clipboard.java |
index 4497c60fa49b925005f3322337b1146f626c9770..ecf60e687a7f00fe704355c141b73e470eb0e970 100644 |
--- a/ui/android/java/src/org/chromium/ui/base/Clipboard.java |
+++ b/ui/android/java/src/org/chromium/ui/base/Clipboard.java |
@@ -8,24 +8,41 @@ import android.content.ClipData; |
import android.content.ClipboardManager; |
import android.content.Context; |
+import org.chromium.base.Log; |
import org.chromium.base.annotations.CalledByNative; |
import org.chromium.base.annotations.JNINamespace; |
import org.chromium.base.annotations.SuppressFBWarnings; |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.base.metrics.RecordUserAction; |
import org.chromium.ui.R; |
import org.chromium.ui.widget.Toast; |
+import java.security.MessageDigest; |
+import java.security.NoSuchAlgorithmException; |
+import java.util.Arrays; |
+ |
/** |
* Simple proxy that provides C++ code with an access pathway to the Android |
* clipboard. |
*/ |
@JNINamespace("ui") |
-public class Clipboard { |
+public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener { |
Ted C
2017/03/24 03:26:51
Hmm...a couple things I realized.
This is not a s
Mark P
2017/03/24 05:20:25
Have several questions here.
1. Why does it matte
Ted C
2017/03/24 16:13:50
I'm worried one isn't created on startup and only
Mark P
2017/03/24 21:53:25
Thanks for the detailed responses to my comments.
|
+ private static final String TAG = "Clipboard"; |
+ |
// Necessary for coercing clipboard contents to text if they require |
// access to network resources, etceteras (e.g., URI in clipboard) |
private final Context mContext; |
private final ClipboardManager mClipboardManager; |
+ // A message hasher that's used to hash clipboard contents so we can tell |
+ // when a clipboard changes without storing the full contents. |
+ private MessageDigest mMd5Hasher; |
+ // The hash of the current clipboard. |
+ private byte[] mClipboardMd5; |
+ // The time when the clipboard was last updated. Set to 0 if unknown. |
+ private long mClipboardChangeTime; |
+ |
/** |
* Use the factory constructor instead. |
* |
@@ -35,6 +52,20 @@ public class Clipboard { |
mContext = context; |
mClipboardManager = (ClipboardManager) |
context.getSystemService(Context.CLIPBOARD_SERVICE); |
+ mClipboardManager.addPrimaryClipChangedListener(this); |
+ try { |
+ mMd5Hasher = MessageDigest.getInstance("MD5"); |
+ mClipboardMd5 = weakMd5Hash(); |
+ } catch (NoSuchAlgorithmException e) { |
+ Log.e(TAG, |
+ "Unable to construct MD5 MessageDigest: %s; assume " |
+ + "clipboard last update time is start of epoch.", |
+ e); |
+ mMd5Hasher = null; |
+ mClipboardMd5 = new byte[] {}; |
+ } |
+ RecordHistogram.recordBooleanHistogram("Clipboard.ConstructedHasher", mMd5Hasher != null); |
+ mClipboardChangeTime = 0; |
} |
/** |
@@ -97,6 +128,21 @@ public class Clipboard { |
} |
/** |
+ * Gets the time the clipboard content last changed. |
+ * |
+ * This is calculated according to the device's clock. E.g., it continues |
+ * increasing when the device is suspended. Likewise, it can be in the |
+ * future if the user's clock updated after this information was recorded. |
+ * |
+ * @return a Java long recording the last changed time in milliseconds since |
+ * epoch, or 0 if the time could not be determined. |
+ */ |
+ @CalledByNative |
+ public long getClipboardContentChangeTimeInMillis() { |
+ return mClipboardChangeTime; |
+ } |
+ |
+ /** |
* Emulates the behavior of the now-deprecated |
* {@link android.text.ClipboardManager#setText(CharSequence)}, setting the |
* clipboard's current primary clip to a plain-text clip that consists of |
@@ -141,4 +187,37 @@ public class Clipboard { |
Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); |
} |
} |
+ |
+ /** |
+ * Updates mClipboardMd5 and mClipboardChangeTime when the clipboard updates. |
+ * |
+ * Implements OnPrimaryClipChangedListener to listen for clipboard updates. |
+ */ |
+ @Override |
+ public void onPrimaryClipChanged() { |
+ if (mMd5Hasher == null) return; |
+ RecordUserAction.record("MobileOmniboxClipboardChanged"); |
Ted C
2017/03/24 03:26:51
The "Omnibox" portion here seems misleading to me.
Mark P
2017/03/24 05:20:25
I agree. But this is the user action that iOS use
|
+ mClipboardMd5 = weakMd5Hash(); |
+ // Always update the clipboard change time even if the clipboard |
Ted C
2017/03/24 03:26:51
D'oh. I was thinking we were using the hash to de
Mark P
2017/03/24 05:20:25
Acknowledged.
|
+ // content hasn't changed. This is because if the user put something |
+ // in the clipboard recently (even if it was not necessary because it |
+ // was already there), that content should be considered recent. |
+ mClipboardChangeTime = System.currentTimeMillis(); |
+ } |
+ |
+ /** |
+ * Returns a weak hash of getCoercedText(). |
+ * |
+ * @return a Java byte[] with the weak hash. |
+ */ |
+ private byte[] weakMd5Hash() { |
+ if (getCoercedText() == null) { |
+ return new byte[] {}; |
+ } |
+ // Compute a hash consisting of the first 4 bytes of the MD5 hash of |
+ // getCoercedText(). This value is used to detect clipboard content |
+ // change. Keeping only 4 bytes is a privacy requirement to introduce |
+ // collision and allow deniability of having copied a given string. |
+ return Arrays.copyOfRange(mMd5Hasher.digest(getCoercedText().getBytes()), 0, 4); |
+ } |
} |