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; |
12 import org.chromium.base.annotations.CalledByNative; | 13 import org.chromium.base.annotations.CalledByNative; |
13 import org.chromium.base.annotations.JNINamespace; | 14 import org.chromium.base.annotations.JNINamespace; |
14 import org.chromium.base.annotations.SuppressFBWarnings; | 15 import org.chromium.base.annotations.SuppressFBWarnings; |
| 16 import org.chromium.base.metrics.RecordHistogram; |
| 17 import org.chromium.base.metrics.RecordUserAction; |
15 import org.chromium.ui.R; | 18 import org.chromium.ui.R; |
16 import org.chromium.ui.widget.Toast; | 19 import org.chromium.ui.widget.Toast; |
17 | 20 |
| 21 import java.security.MessageDigest; |
| 22 import java.security.NoSuchAlgorithmException; |
| 23 import java.util.Arrays; |
| 24 |
18 /** | 25 /** |
19 * Simple proxy that provides C++ code with an access pathway to the Android cli
pboard. | 26 * Simple proxy that provides C++ code with an access pathway to the Android cli
pboard. |
20 */ | 27 */ |
21 @JNINamespace("ui") | 28 @JNINamespace("ui") |
22 public class Clipboard { | 29 public class Clipboard implements ClipboardManager.OnPrimaryClipChangedListener
{ |
23 private static Clipboard sInstance; | 30 private static Clipboard sInstance; |
24 | 31 |
| 32 private static final String TAG = "Clipboard"; |
| 33 |
25 // Necessary for coercing clipboard contents to text if they require | 34 // Necessary for coercing clipboard contents to text if they require |
26 // access to network resources, etceteras (e.g., URI in clipboard) | 35 // access to network resources, etceteras (e.g., URI in clipboard) |
27 private final Context mContext; | 36 private final Context mContext; |
28 | 37 |
29 private final ClipboardManager mClipboardManager; | 38 private final ClipboardManager mClipboardManager; |
30 | 39 |
| 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 |
31 /** | 51 /** |
32 * Get the singleton Clipboard instance (creating it if needed). | 52 * Get the singleton Clipboard instance (creating it if needed). |
33 */ | 53 */ |
34 @CalledByNative | 54 @CalledByNative |
35 public static Clipboard getInstance() { | 55 public static Clipboard getInstance() { |
36 if (sInstance == null) { | 56 if (sInstance == null) { |
37 sInstance = new Clipboard(); | 57 sInstance = new Clipboard(); |
38 } | 58 } |
39 return sInstance; | 59 return sInstance; |
40 } | 60 } |
41 | 61 |
42 private Clipboard() { | 62 private Clipboard() { |
43 mContext = ContextUtils.getApplicationContext(); | 63 mContext = ContextUtils.getApplicationContext(); |
44 mClipboardManager = | 64 mClipboardManager = |
45 (ClipboardManager) ContextUtils.getApplicationContext().getSyste
mService( | 65 (ClipboardManager) ContextUtils.getApplicationContext().getSyste
mService( |
46 Context.CLIPBOARD_SERVICE); | 66 Context.CLIPBOARD_SERVICE); |
| 67 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; |
47 } | 81 } |
48 | 82 |
49 /** | 83 /** |
50 * Emulates the behavior of the now-deprecated | 84 * Emulates the behavior of the now-deprecated |
51 * {@link android.text.ClipboardManager#getText()} by invoking | 85 * {@link android.text.ClipboardManager#getText()} by invoking |
52 * {@link android.content.ClipData.Item#coerceToText(Context)} on the first | 86 * {@link android.content.ClipData.Item#coerceToText(Context)} on the first |
53 * item in the clipboard (if any) and returning the result as a string. | 87 * item in the clipboard (if any) and returning the result as a string. |
54 * <p> | 88 * <p> |
55 * This is quite different than simply calling {@link Object#toString()} on | 89 * This is quite different than simply calling {@link Object#toString()} on |
56 * the clip; consumers of this API should familiarize themselves with the | 90 * the clip; consumers of this API should familiarize themselves with the |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 // getPrimaryClip() has been observed to throw unexpected exceptions for
some devices (see | 122 // getPrimaryClip() has been observed to throw unexpected exceptions for
some devices (see |
89 // crbug/654802 and b/31501780) | 123 // crbug/654802 and b/31501780) |
90 try { | 124 try { |
91 return mClipboardManager.getPrimaryClip().getItemAt(0).getHtmlText()
; | 125 return mClipboardManager.getPrimaryClip().getItemAt(0).getHtmlText()
; |
92 } catch (Exception e) { | 126 } catch (Exception e) { |
93 return null; | 127 return null; |
94 } | 128 } |
95 } | 129 } |
96 | 130 |
97 /** | 131 /** |
| 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 /** |
98 * Emulates the behavior of the now-deprecated | 147 * Emulates the behavior of the now-deprecated |
99 * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the | 148 * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the |
100 * clipboard's current primary clip to a plain-text clip that consists of | 149 * clipboard's current primary clip to a plain-text clip that consists of |
101 * the specified string. | 150 * the specified string. |
102 * @param text will become the content of the clipboard's primary clip | 151 * @param text will become the content of the clipboard's primary clip |
103 */ | 152 */ |
104 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD") | 153 @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD") |
105 @CalledByNative | 154 @CalledByNative |
106 public void setText(final String text) { | 155 public void setText(final String text) { |
107 setPrimaryClipNoException(ClipData.newPlainText("text", text)); | 156 setPrimaryClipNoException(ClipData.newPlainText("text", text)); |
(...skipping 24 matching lines...) Expand all Loading... |
132 | 181 |
133 private void setPrimaryClipNoException(ClipData clip) { | 182 private void setPrimaryClipNoException(ClipData clip) { |
134 try { | 183 try { |
135 mClipboardManager.setPrimaryClip(clip); | 184 mClipboardManager.setPrimaryClip(clip); |
136 } catch (Exception ex) { | 185 } catch (Exception ex) { |
137 // Ignore any exceptions here as certain devices have bugs and will
fail. | 186 // Ignore any exceptions here as certain devices have bugs and will
fail. |
138 String text = mContext.getString(R.string.copy_to_clipboard_failure_
message); | 187 String text = mContext.getString(R.string.copy_to_clipboard_failure_
message); |
139 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); | 188 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); |
140 } | 189 } |
141 } | 190 } |
| 191 |
| 192 /** |
| 193 * Updates mClipboardMd5 and mClipboardChangeTime when the clipboard updates
. |
| 194 * |
| 195 * Implements OnPrimaryClipChangedListener to listen for clipboard updates. |
| 196 */ |
| 197 @Override |
| 198 public void onPrimaryClipChanged() { |
| 199 if (mMd5Hasher == null) return; |
| 200 RecordUserAction.record("MobileOmniboxClipboardChanged"); |
| 201 mClipboardMd5 = weakMd5Hash(); |
| 202 // Always update the clipboard change time even if the clipboard |
| 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 } |
| 208 |
| 209 /** |
| 210 * Returns a weak hash of getCoercedText(). |
| 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 } |
142 } | 224 } |
OLD | NEW |