Merge "Add support for all HTTP methods to JsonArrayRequest"
diff --git a/.gitignore b/.gitignore
index 10b5ff5..8889923 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 target
 *.iml
 .idea
+local.properties
diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
index 036b55a..ff687d6 100644
--- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
+++ b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
@@ -62,7 +62,7 @@
     private static final float HYSTERESIS_FACTOR = 0.9f;
 
     /** Magic number for current version of cache file format. */
-    private static final int CACHE_MAGIC = 0x20140623;
+    private static final int CACHE_MAGIC = 0x20150306;
 
     /**
      * Constructs an instance of the DiskBasedCache at the specified directory.
@@ -397,16 +397,11 @@
                 entry.etag = null;
             }
             entry.serverDate = readLong(is);
+            entry.lastModified = readLong(is);
             entry.ttl = readLong(is);
             entry.softTtl = readLong(is);
             entry.responseHeaders = readStringStringMap(is);
 
-            try {
-                entry.lastModified = readLong(is);
-            } catch (EOFException e) {
-                // the old cache entry format doesn't know lastModified
-            }
-
             return entry;
         }
 
@@ -435,10 +430,10 @@
                 writeString(os, key);
                 writeString(os, etag == null ? "" : etag);
                 writeLong(os, serverDate);
+                writeLong(os, lastModified);
                 writeLong(os, ttl);
                 writeLong(os, softTtl);
                 writeStringStringMap(responseHeaders, os);
-                writeLong(os, lastModified);
                 os.flush();
                 return true;
             } catch (IOException e) {
diff --git a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
index 7306052..c3b48d8 100644
--- a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
+++ b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
@@ -49,6 +49,7 @@
         long maxAge = 0;
         long staleWhileRevalidate = 0;
         boolean hasCacheControl = false;
+        boolean mustRevalidate = false;
 
         String serverEtag = null;
         String headerValue;
@@ -77,7 +78,7 @@
                     } catch (Exception e) {
                     }
                 } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
-                    maxAge = 0;
+                    mustRevalidate = true;
                 }
             }
         }
@@ -98,7 +99,9 @@
         // is more restrictive.
         if (hasCacheControl) {
             softExpire = now + maxAge * 1000;
-            finalExpire = softExpire + staleWhileRevalidate * 1000;
+            finalExpire = mustRevalidate
+                    ? softExpire
+                    : softExpire + staleWhileRevalidate * 1000;
         } else if (serverDate > 0 && serverExpires >= serverDate) {
             // Default semantic for Expire header in HTTP specification is softExpire.
             softExpire = now + (serverExpires - serverDate);
diff --git a/src/main/java/com/android/volley/toolbox/ImageLoader.java b/src/main/java/com/android/volley/toolbox/ImageLoader.java
index 995bb48..d5305e3 100644
--- a/src/main/java/com/android/volley/toolbox/ImageLoader.java
+++ b/src/main/java/com/android/volley/toolbox/ImageLoader.java
@@ -21,7 +21,6 @@
 import android.os.Looper;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
-
 import com.android.volley.Request;
 import com.android.volley.RequestQueue;
 import com.android.volley.Response.ErrorListener;
@@ -151,9 +150,22 @@
      * @return True if the item exists in cache, false otherwise.
      */
     public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
+        return isCached(requestUrl, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
+    }
+
+    /**
+     * Checks if the item is available in the cache.
+     *
+     * @param requestUrl The url of the remote image
+     * @param maxWidth   The maximum width of the returned image.
+     * @param maxHeight  The maximum height of the returned image.
+     * @param scaleType  The scaleType of the imageView.
+     * @return True if the item exists in cache, false otherwise.
+     */
+    public boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
         throwIfNotOnMainThread();
 
-        String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
+        String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
         return mCache.getBitmap(cacheKey) != null;
     }
 
@@ -194,11 +206,11 @@
      */
     public ImageContainer get(String requestUrl, ImageListener imageListener,
             int maxWidth, int maxHeight, ScaleType scaleType) {
-        
+
         // only fulfill requests that were initiated from the main thread.
         throwIfNotOnMainThread();
 
-        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
+        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
 
         // Try to look up the request in the cache of remote images.
         Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
@@ -485,9 +497,11 @@
      * @param url The URL of the request.
      * @param maxWidth The max-width of the output.
      * @param maxHeight The max-height of the output.
+     * @param scaleType The scaleType of the imageView.
      */
-    private static String getCacheKey(String url, int maxWidth, int maxHeight) {
+    private static String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
         return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
-                .append("#H").append(maxHeight).append(url).toString();
+                .append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
+                .toString();
     }
 }
diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
index bf1d258..0a8be77 100644
--- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
+++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
@@ -125,45 +125,6 @@
         assertEquals(DiskBasedCache.readStringStringMap(bais), emptyValue);
     }
 
-    /** deserializing the old format into the new one. */
-    @Test public void testCacheHeaderSerializationOldToNewFormat() throws Exception {
-
-        final int CACHE_MAGIC = 0x20140623;
-        final String key = "key";
-        final String etag = "etag";
-        final long serverDate = 1234567890l;
-        final long ttl = 1357924680l;
-        final long softTtl = 2468013579l;
-
-        Map<String, String> responseHeaders = new HashMap<String, String>();
-        responseHeaders.put("first", "thing");
-        responseHeaders.put("second", "item");
-
-        // write old sytle header (without lastModified)
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        DiskBasedCache.writeInt(baos, CACHE_MAGIC);
-        DiskBasedCache.writeString(baos, key);
-        DiskBasedCache.writeString(baos, etag == null ? "" : etag);
-        DiskBasedCache.writeLong(baos, serverDate);
-        DiskBasedCache.writeLong(baos, ttl);
-        DiskBasedCache.writeLong(baos, softTtl);
-        DiskBasedCache.writeStringStringMap(responseHeaders, baos);
-
-        // read / test new style header (with lastModified)
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        CacheHeader cacheHeader = CacheHeader.readHeader(bais);
-
-        assertEquals(cacheHeader.key, key);
-        assertEquals(cacheHeader.etag, etag);
-        assertEquals(cacheHeader.serverDate, serverDate);
-        assertEquals(cacheHeader.ttl, ttl);
-        assertEquals(cacheHeader.softTtl, softTtl);
-        assertEquals(cacheHeader.responseHeaders, responseHeaders);
-
-        // the old format doesn't know lastModified
-        assertEquals(cacheHeader.lastModified, 0);
-    }
-
     @Test
     public void publicMethods() throws Exception {
         // Catch-all test to find API-breaking changes.
diff --git a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
index f9230c6..fd8cf51 100644
--- a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
+++ b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
@@ -41,6 +41,7 @@
     private static long ONE_MINUTE_MILLIS = 1000L * 60;
     private static long ONE_HOUR_MILLIS = 1000L * 60 * 60;
     private static long ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24;
+    private static long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
 
     private NetworkResponse response;
     private Map<String, String> headers;
@@ -135,7 +136,7 @@
 
         assertNotNull(entry);
         assertNull(entry.etag);
-        assertEqualsWithin(now + 24 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+        assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
         assertEquals(entry.softTtl, entry.ttl);
     }
 
@@ -153,8 +154,8 @@
 
         assertNotNull(entry);
         assertNull(entry.etag);
-        assertEqualsWithin(now + 24 * ONE_HOUR_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
-        assertEqualsWithin(now + 8 * 24 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+        assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
+        assertEqualsWithin(now + ONE_DAY_MILLIS + ONE_WEEK_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
     }
 
     @Test public void parseCacheHeaders_cacheControlNoCache() {
@@ -168,20 +169,51 @@
         assertNull(entry);
     }
 
-    @Test public void parseCacheHeaders_cacheControlMustRevalidate() {
+    @Test public void parseCacheHeaders_cacheControlMustRevalidateNoMaxAge() {
         long now = System.currentTimeMillis();
         headers.put("Date", rfc1123Date(now));
         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
         headers.put("Cache-Control", "must-revalidate");
 
         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
-
         assertNotNull(entry);
         assertNull(entry.etag);
         assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
         assertEquals(entry.softTtl, entry.ttl);
     }
 
+    @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAge() {
+        long now = System.currentTimeMillis();
+        headers.put("Date", rfc1123Date(now));
+        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+        headers.put("Cache-Control", "must-revalidate, max-age=3600");
+
+        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+        assertNotNull(entry);
+        assertNull(entry.etag);
+        assertEqualsWithin(now + ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+        assertEquals(entry.softTtl, entry.ttl);
+    }
+
+    @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAgeAndStale() {
+        long now = System.currentTimeMillis();
+        headers.put("Date", rfc1123Date(now));
+        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+
+        // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
+        // - stale-while-revalidate (entry.ttl) indicates that the asset may
+        // continue to be served stale for up to additional 7 days, but this is
+        // ignored in this case because of the must-revalidate header.
+        headers.put("Cache-Control",
+                "must-revalidate, max-age=86400, stale-while-revalidate=604800");
+
+        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+        assertNotNull(entry);
+        assertNull(entry.etag);
+        assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
+        assertEquals(entry.softTtl, entry.ttl);
+    }
+
     private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
         long diff = Math.abs(expected - value);
         assertTrue(diff < fudgeFactor);
@@ -253,7 +285,7 @@
 
         assertNotNull(entry);
         assertEquals("Yow!", entry.etag);
-        assertEqualsWithin(now + 24 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+        assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
         assertEquals(entry.softTtl, entry.ttl);
         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
     }
diff --git a/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java b/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
index 81de6fe..8a19817 100644
--- a/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
+++ b/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
@@ -84,6 +84,8 @@
         assertNotNull(ImageLoader.class.getMethod("getImageListener", ImageView.class,
                 int.class, int.class));
         assertNotNull(ImageLoader.class.getMethod("isCached", String.class, int.class, int.class));
+        assertNotNull(ImageLoader.class.getMethod("isCached", String.class, int.class, int.class,
+                ImageView.ScaleType.class));
         assertNotNull(ImageLoader.class.getMethod("get", String.class,
                 ImageLoader.ImageListener.class));
         assertNotNull(ImageLoader.class.getMethod("get", String.class,