OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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 package org.chromium.ui.base; | 5 package org.chromium.ui.base; |
6 | 6 |
7 import android.content.ClipData; | 7 import android.content.ClipData; |
8 import android.content.ClipboardManager; | 8 import android.content.ClipboardManager; |
9 import android.content.Context; | 9 import android.content.Context; |
10 | 10 |
11 import org.chromium.base.Log; | |
11 import org.chromium.base.annotations.CalledByNative; | 12 import org.chromium.base.annotations.CalledByNative; |
12 import org.chromium.base.annotations.JNINamespace; | 13 import org.chromium.base.annotations.JNINamespace; |
13 import org.chromium.base.annotations.SuppressFBWarnings; | 14 import org.chromium.base.annotations.SuppressFBWarnings; |
15 import org.chromium.base.metrics.RecordHistogram; | |
16 import org.chromium.base.metrics.RecordUserAction; | |
14 import org.chromium.ui.R; | 17 import org.chromium.ui.R; |
15 import org.chromium.ui.widget.Toast; | 18 import org.chromium.ui.widget.Toast; |
16 | 19 |
20 import java.security.MessageDigest; | |
21 import java.security.NoSuchAlgorithmException; | |
22 import java.util.Arrays; | |
23 | |
17 /** | 24 /** |
18 * Simple proxy that provides C++ code with an access pathway to the Android | 25 * Simple proxy that provides C++ code with an access pathway to the Android |
19 * clipboard. | 26 * clipboard. |
20 */ | 27 */ |
21 @JNINamespace("ui") | 28 @JNINamespace("ui") |
22 public class Clipboard { | 29 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.
| |
30 private static final String TAG = "Clipboard"; | |
31 | |
23 // Necessary for coercing clipboard contents to text if they require | 32 // Necessary for coercing clipboard contents to text if they require |
24 // access to network resources, etceteras (e.g., URI in clipboard) | 33 // access to network resources, etceteras (e.g., URI in clipboard) |
25 private final Context mContext; | 34 private final Context mContext; |
26 | 35 |
27 private final ClipboardManager mClipboardManager; | 36 private final ClipboardManager mClipboardManager; |
28 | 37 |
38 // A message hasher that's used to hash clipboard contents so we can tell | |
39 // when a clipboard changes without storing the full contents. | |
40 private MessageDigest mMd5Hasher; | |
41 // The hash of the current clipboard. | |
42 private byte[] mClipboardMd5; | |
43 // The time when the clipboard was last updated. Set to 0 if unknown. | |
44 private long mClipboardChangeTime; | |
45 | |
29 /** | 46 /** |
30 * Use the factory constructor instead. | 47 * Use the factory constructor instead. |
31 * | 48 * |
32 * @param context for accessing the clipboard | 49 * @param context for accessing the clipboard |
33 */ | 50 */ |
34 public Clipboard(final Context context) { | 51 public Clipboard(final Context context) { |
35 mContext = context; | 52 mContext = context; |
36 mClipboardManager = (ClipboardManager) | 53 mClipboardManager = (ClipboardManager) |
37 context.getSystemService(Context.CLIPBOARD_SERVICE); | 54 context.getSystemService(Context.CLIPBOARD_SERVICE); |
55 mClipboardManager.addPrimaryClipChangedListener(this); | |
56 try { | |
57 mMd5Hasher = MessageDigest.getInstance("MD5"); | |
58 mClipboardMd5 = weakMd5Hash(); | |
59 } catch (NoSuchAlgorithmException e) { | |
60 Log.e(TAG, | |
61 "Unable to construct MD5 MessageDigest: %s; assume " | |
62 + "clipboard last update time is start of epoch.", | |
63 e); | |
64 mMd5Hasher = null; | |
65 mClipboardMd5 = new byte[] {}; | |
66 } | |
67 RecordHistogram.recordBooleanHistogram("Clipboard.ConstructedHasher", mM d5Hasher != null); | |
68 mClipboardChangeTime = 0; | |
38 } | 69 } |
39 | 70 |
40 /** | 71 /** |
41 * Returns a new Clipboard object bound to the specified context. | 72 * Returns a new Clipboard object bound to the specified context. |
42 * | 73 * |
43 * @param context for accessing the clipboard | 74 * @param context for accessing the clipboard |
44 * @return the new object | 75 * @return the new object |
45 */ | 76 */ |
46 @CalledByNative | 77 @CalledByNative |
47 private static Clipboard create(final Context context) { | 78 private static Clipboard create(final Context context) { |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
90 // getPrimaryClip() has been observed to throw unexpected exceptions for some devices (see | 121 // getPrimaryClip() has been observed to throw unexpected exceptions for some devices (see |
91 // crbug/654802 and b/31501780) | 122 // crbug/654802 and b/31501780) |
92 try { | 123 try { |
93 return mClipboardManager.getPrimaryClip().getItemAt(0).getHtmlText() ; | 124 return mClipboardManager.getPrimaryClip().getItemAt(0).getHtmlText() ; |
94 } catch (Exception e) { | 125 } catch (Exception e) { |
95 return null; | 126 return null; |
96 } | 127 } |
97 } | 128 } |
98 | 129 |
99 /** | 130 /** |
131 * Gets the time the clipboard content last changed. | |
132 * | |
133 * This is calculated according to the device's clock. E.g., it continues | |
134 * increasing when the device is suspended. Likewise, it can be in the | |
135 * future if the user's clock updated after this information was recorded. | |
136 * | |
137 * @return a Java long recording the last changed time in milliseconds since | |
138 * epoch, or 0 if the time could not be determined. | |
139 */ | |
140 @CalledByNative | |
141 public long getClipboardContentChangeTimeInMillis() { | |
142 return mClipboardChangeTime; | |
143 } | |
144 | |
145 /** | |
100 * Emulates the behavior of the now-deprecated | 146 * Emulates the behavior of the now-deprecated |
101 * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the | 147 * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the |
102 * clipboard's current primary clip to a plain-text clip that consists of | 148 * clipboard's current primary clip to a plain-text clip that consists of |
103 * the specified string. | 149 * the specified string. |
104 * @param text will become the content of the clipboard's primary clip | 150 * @param text will become the content of the clipboard's primary clip |
105 */ | 151 */ |
106 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD") | 152 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD") |
107 @CalledByNative | 153 @CalledByNative |
108 public void setText(final String text) { | 154 public void setText(final String text) { |
109 setPrimaryClipNoException(ClipData.newPlainText("text", text)); | 155 setPrimaryClipNoException(ClipData.newPlainText("text", text)); |
(...skipping 24 matching lines...) Expand all Loading... | |
134 | 180 |
135 private void setPrimaryClipNoException(ClipData clip) { | 181 private void setPrimaryClipNoException(ClipData clip) { |
136 try { | 182 try { |
137 mClipboardManager.setPrimaryClip(clip); | 183 mClipboardManager.setPrimaryClip(clip); |
138 } catch (Exception ex) { | 184 } catch (Exception ex) { |
139 // Ignore any exceptions here as certain devices have bugs and will fail. | 185 // Ignore any exceptions here as certain devices have bugs and will fail. |
140 String text = mContext.getString(R.string.copy_to_clipboard_failure_ message); | 186 String text = mContext.getString(R.string.copy_to_clipboard_failure_ message); |
141 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); | 187 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); |
142 } | 188 } |
143 } | 189 } |
190 | |
191 /** | |
192 * Updates mClipboardMd5 and mClipboardChangeTime when the clipboard updates . | |
193 * | |
194 * Implements OnPrimaryClipChangedListener to listen for clipboard updates. | |
195 */ | |
196 @Override | |
197 public void onPrimaryClipChanged() { | |
198 if (mMd5Hasher == null) return; | |
199 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
| |
200 mClipboardMd5 = weakMd5Hash(); | |
201 // 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.
| |
202 // content hasn't changed. This is because if the user put something | |
203 // in the clipboard recently (even if it was not necessary because it | |
204 // was already there), that content should be considered recent. | |
205 mClipboardChangeTime = System.currentTimeMillis(); | |
206 } | |
207 | |
208 /** | |
209 * Returns a weak hash of getCoercedText(). | |
210 * | |
211 * @return a Java byte[] with the weak hash. | |
212 */ | |
213 private byte[] weakMd5Hash() { | |
214 if (getCoercedText() == null) { | |
215 return new byte[] {}; | |
216 } | |
217 // Compute a hash consisting of the first 4 bytes of the MD5 hash of | |
218 // getCoercedText(). This value is used to detect clipboard content | |
219 // change. Keeping only 4 bytes is a privacy requirement to introduce | |
220 // collision and allow deniability of having copied a given string. | |
221 return Arrays.copyOfRange(mMd5Hasher.digest(getCoercedText().getBytes()) , 0, 4); | |
222 } | |
144 } | 223 } |
OLD | NEW |