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