Merge "Add support for mouse hover and scroll wheel."
diff --git a/api/11.xml b/api/11.xml
index 76669e3..8216491 100644
--- a/api/11.xml
+++ b/api/11.xml
@@ -90722,8 +90722,6 @@
>
<parameter name="surfaceTexture" type="android.graphics.SurfaceTexture">
</parameter>
-<exception name="IOException" type="java.io.IOException">
-</exception>
</method>
<method name="setZoomChangeListener"
return="void"
diff --git a/api/current.xml b/api/current.xml
index ad95c94..90b46ff 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -206575,7 +206575,7 @@
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="protected"
>
<parameter name="key" type="K">
@@ -206583,11 +206583,30 @@
<parameter name="value" type="V">
</parameter>
</method>
+<method name="entryRemoved"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="evicted" type="boolean">
+</parameter>
+<parameter name="key" type="K">
+</parameter>
+<parameter name="oldValue" type="V">
+</parameter>
+<parameter name="newValue" type="V">
+</parameter>
+</method>
<method name="evictAll"
return="void"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
@@ -206609,7 +206628,7 @@
return="V"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
@@ -206655,7 +206674,7 @@
return="V"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
@@ -206681,7 +206700,7 @@
return="V"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 5578e6a..a1501e6 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -26,8 +26,7 @@
* become eligible for garbage collection.
*
* <p>If your cached values hold resources that need to be explicitly released,
- * override {@link #entryEvicted}. This method is only invoked when values are
- * evicted. Values replaced by calls to {@link #put} must be released manually.
+ * override {@link #entryRemoved}.
*
* <p>If a cache miss should be computed on demand for the corresponding keys,
* override {@link #create}. This simplifies the calling code, allowing it to
@@ -88,29 +87,52 @@
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
- public synchronized final V get(K key) {
+ public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
- V result = map.get(key);
- if (result != null) {
- hitCount++;
- return result;
+ V mapValue;
+ synchronized (this) {
+ mapValue = map.get(key);
+ if (mapValue != null) {
+ hitCount++;
+ return mapValue;
+ }
+ missCount++;
}
- missCount++;
+ /*
+ * Attempt to create a value. This may take a long time, and the map
+ * may be different when create() returns. If a conflicting value was
+ * added to the map while create() was working, we leave that value in
+ * the map and release the created value.
+ */
- // TODO: release the lock while calling this potentially slow user code
- result = create(key);
+ V createdValue = create(key);
+ if (createdValue == null) {
+ return null;
+ }
- if (result != null) {
+ synchronized (this) {
createCount++;
- size += safeSizeOf(key, result);
- map.put(key, result);
- trimToSize(maxSize);
+ mapValue = map.put(key, createdValue);
+
+ if (mapValue != null) {
+ // There was a conflict so undo that last put
+ map.put(key, mapValue);
+ } else {
+ size += safeSizeOf(key, createdValue);
+ }
}
- return result;
+
+ if (mapValue != null) {
+ entryRemoved(false, key, createdValue, mapValue);
+ return mapValue;
+ } else {
+ trimToSize(maxSize);
+ return createdValue;
+ }
}
/**
@@ -120,42 +142,61 @@
* @return the previous value mapped by {@code key}. Although that entry is
* no longer cached, it has not been passed to {@link #entryEvicted}.
*/
- public synchronized final V put(K key, V value) {
+ public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
- putCount++;
- size += safeSizeOf(key, value);
- V previous = map.put(key, value);
- if (previous != null) {
- size -= safeSizeOf(key, previous);
+ V previous;
+ synchronized (this) {
+ putCount++;
+ size += safeSizeOf(key, value);
+ previous = map.put(key, value);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
}
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, value);
+ }
+
trimToSize(maxSize);
return previous;
}
+ /**
+ * @param maxSize the maximum size of the cache before returning. May be -1
+ * to evict even 0-sized elements.
+ */
private void trimToSize(int maxSize) {
- while (size > maxSize) {
- Map.Entry<K, V> toEvict = map.eldest(); // equal to map.entrySet().iterator().next();
- if (toEvict == null) {
- break; // map is empty; if size is not 0 then throw an error below
+ while (true) {
+ K key;
+ V value;
+ synchronized (this) {
+ if (size < 0 || (map.isEmpty() && size != 0)) {
+ throw new IllegalStateException(getClass().getName()
+ + ".sizeOf() is reporting inconsistent results!");
+ }
+
+ if (size <= maxSize) {
+ break;
+ }
+
+ Map.Entry<K, V> toEvict = map.eldest();
+ if (toEvict == null) {
+ break;
+ }
+
+ key = toEvict.getKey();
+ value = toEvict.getValue();
+ map.remove(key);
+ size -= safeSizeOf(key, value);
+ evictionCount++;
}
- K key = toEvict.getKey();
- V value = toEvict.getValue();
- map.remove(key);
- size -= safeSizeOf(key, value);
- evictionCount++;
-
- // TODO: release the lock while calling this potentially slow user code
entryEvicted(key, value);
}
-
- if (size < 0 || (map.isEmpty() && size != 0)) {
- throw new IllegalStateException(getClass().getName()
- + ".sizeOf() is reporting inconsistent results!");
- }
}
/**
@@ -164,28 +205,67 @@
* @return the previous value mapped by {@code key}. Although that entry is
* no longer cached, it has not been passed to {@link #entryEvicted}.
*/
- public synchronized final V remove(K key) {
+ public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
- V previous = map.remove(key);
- if (previous != null) {
- size -= safeSizeOf(key, previous);
+ V previous;
+ synchronized (this) {
+ previous = map.remove(key);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
}
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, null);
+ }
+
return previous;
}
/**
- * Called for entries that have reached the tail of the least recently used
- * queue and are be removed. The default implementation does nothing.
+ * Calls {@link #entryRemoved}.
+ *
+ * @deprecated replaced by entryRemoved
*/
- protected void entryEvicted(K key, V value) {}
+ @Deprecated
+ protected void entryEvicted(K key, V value) {
+ entryRemoved(true, key, value, null);
+ }
+
+ /**
+ * Called for entries that have been evicted or removed. This method is
+ * invoked when a value is evicted to make space, removed by a call to
+ * {@link #remove}, or replaced by a call to {@link #put}. The default
+ * implementation does nothing.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * @param evicted true if the entry is being removed to make space, false
+ * if the removal was caused by a {@link #put} or {@link #remove}.
+ * @param newValue the new value for {@code key}, if it exists. If non-null,
+ * this removal was caused by a {@link #put}. Otherwise it was caused by
+ * an eviction or a {@link #remove}.
+ */
+ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * <p>If a value for {@code key} exists in the cache when this method
+ * returns, the created value will be released with {@link #entryRemoved}
+ * and discarded. This can occur when multiple threads request the same key
+ * at the same time (causing multiple values to be created), or when one
+ * thread calls {@link #put} while another is creating a value for the same
+ * key.
*/
protected V create(K key) {
return null;
@@ -213,7 +293,7 @@
/**
* Clear the cache, calling {@link #entryEvicted} on each removed entry.
*/
- public synchronized final void evictAll() {
+ public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
diff --git a/core/tests/coretests/src/android/util/LruCacheTest.java b/core/tests/coretests/src/android/util/LruCacheTest.java
index cf252e6..7e46e26 100644
--- a/core/tests/coretests/src/android/util/LruCacheTest.java
+++ b/core/tests/coretests/src/android/util/LruCacheTest.java
@@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
@@ -166,22 +167,16 @@
}
public void testEntryEvictedWhenFull() {
- List<String> expectedEvictionLog = new ArrayList<String>();
- final List<String> evictionLog = new ArrayList<String>();
- LruCache<String, String> cache = new LruCache<String, String>(3) {
- @Override protected void entryEvicted(String key, String value) {
- evictionLog.add(key + "=" + value);
- }
- };
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
cache.put("a", "A");
cache.put("b", "B");
cache.put("c", "C");
- assertEquals(expectedEvictionLog, evictionLog);
+ assertEquals(Collections.<String>emptyList(), log);
cache.put("d", "D");
- expectedEvictionLog.add("a=A");
- assertEquals(expectedEvictionLog, evictionLog);
+ assertEquals(Arrays.asList("a=A"), log);
}
/**
@@ -309,19 +304,14 @@
}
public void testEvictAll() {
- final List<String> evictionLog = new ArrayList<String>();
- LruCache<String, String> cache = new LruCache<String, String>(10) {
- @Override protected void entryEvicted(String key, String value) {
- evictionLog.add(key + "=" + value);
- }
- };
-
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
cache.put("a", "A");
cache.put("b", "B");
cache.put("c", "C");
cache.evictAll();
assertEquals(0, cache.size());
- assertEquals(Arrays.asList("a=A", "b=B", "c=C"), evictionLog);
+ assertEquals(Arrays.asList("a=A", "b=B", "c=C"), log);
}
public void testEvictAllEvictsSizeZeroElements() {
@@ -337,16 +327,6 @@
assertSnapshot(cache);
}
- public void testRemoveDoesNotCallEntryEvicted() {
- LruCache<String, String> cache = new LruCache<String, String>(10) {
- @Override protected void entryEvicted(String key, String value) {
- fail();
- }
- };
- cache.put("a", "A");
- assertEquals("A", cache.remove("a"));
- }
-
public void testRemoveWithCustomSizes() {
LruCache<String, String> cache = new LruCache<String, String>(10) {
@Override protected int sizeOf(String key, String value) {
@@ -376,6 +356,99 @@
}
}
+ public void testRemoveCallsEntryRemoved() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.remove("a");
+ assertEquals(Arrays.asList("a=A>null"), log);
+ }
+
+ public void testPutCallsEntryRemoved() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.put("a", "A2");
+ assertEquals(Arrays.asList("a=A>A2"), log);
+ }
+
+ public void testEntryRemovedIsCalledWithoutSynchronization() {
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ assertFalse(Thread.holdsLock(this));
+ }
+ };
+
+ cache.put("a", "A");
+ cache.put("a", "A2"); // replaced
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("d", "D"); // single eviction
+ cache.remove("a"); // removed
+ cache.evictAll(); // multiple eviction
+ }
+
+ public void testCreateIsCalledWithoutSynchronization() {
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ assertFalse(Thread.holdsLock(this));
+ return null;
+ }
+ };
+
+ cache.get("a");
+ }
+
+ /**
+ * Test what happens when a value is added to the map while create is
+ * working. The map value should be returned by get(), and the created value
+ * should be released with entryRemoved().
+ */
+ public void testCreateWithConcurrentPut() {
+ final List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ put(key, "B");
+ return "A";
+ }
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ log.add(key + "=" + oldValue + ">" + newValue);
+ }
+ };
+
+ assertEquals("B", cache.get("a"));
+ assertEquals(Arrays.asList("a=A>B"), log);
+ }
+
+ /**
+ * Test what happens when two creates happen concurrently. The result from
+ * the first create to return is returned by both gets. The other created
+ * values should be released with entryRemove().
+ */
+ public void testCreateWithConcurrentCreate() {
+ final List<String> log = new ArrayList<String>();
+ LruCache<String, Integer> cache = new LruCache<String, Integer>(3) {
+ int callCount = 0;
+ @Override protected Integer create(String key) {
+ if (callCount++ == 0) {
+ assertEquals(2, get(key).intValue());
+ return 1;
+ } else {
+ return 2;
+ }
+ }
+ @Override protected void entryRemoved(
+ boolean evicted, String key, Integer oldValue, Integer newValue) {
+ log.add(key + "=" + oldValue + ">" + newValue);
+ }
+ };
+
+ assertEquals(2, cache.get("a").intValue());
+ assertEquals(Arrays.asList("a=1>2"), log);
+ }
+
private LruCache<String, String> newCreatingCache() {
return new LruCache<String, String>(3) {
@Override protected String create(String key) {
@@ -384,6 +457,18 @@
};
}
+ private LruCache<String, String> newRemovalLogCache(final List<String> log) {
+ return new LruCache<String, String>(3) {
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ String message = evicted
+ ? (key + "=" + oldValue)
+ : (key + "=" + oldValue + ">" + newValue);
+ log.add(message);
+ }
+ };
+ }
+
private void assertHit(LruCache<String, String> cache, String key, String value) {
assertEquals(value, cache.get(key));
expectedHitCount++;