diff options
author | Anonymous <no-reply@google.com> | 2017-08-10 00:23:15 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-08-10 00:23:15 +0000 |
commit | 8b42d211a58fc5b434921a51cae2c99a806e00eb (patch) | |
tree | 840682988d908a367eadc4d2231bde1e43134333 | |
parent | fd51dba5e6a4bb935fb4357a445848aee24eba11 (diff) | |
parent | cc4c86984e7c6d0de90ea4362506fc5ca857bc68 (diff) | |
download | volley-android-o-mr1-preview-2.tar.gz |
Import of Volley from GitHub to AOSP. - 27f4207774f7a0b59168f5b3b53d8c0c3841de64 Merge pull request #71 from google/fix-visibility by Jeff Davidson <jpd236@cornell.edu> - e1d75bc49fff2f43b4304688f22ccb57f0c2782c Merge pull request #66 from joebowbeer/dbc_overflow by Jeff Davidson <jpd236@cornell.edu> - d40b75f52f0f8e3467d63294142610a040ff8cd5 Merge pull request #64 from xyhuangjinfu/master by Jeff Davidson <jpd236@cornell.edu> - aafd2e5d1279d68cd2c39b02a915e501974daf2f Merge pull request #52 from joebowbeer/dbc by Jeff Davidson <jpd236@cornell.edu> - baefc4e4f7ad42ce84972716486928ab77b413fc Merge pull request #23 from google/javadoc-fixes by Jeff Davidson <jpd236@cornell.edu> - dea97629925acea8942c06a7ff52938eba5dc78c Merge pull request #39 from google/publish by Jeff Davidson <jpd236@cornell.edu> - f52e1e0d4a18c8e3e7004a3d60c8cbec9a04f3d4 Merge pull request #38 from SamuelYvon/master by Jeff Davidson <jpd236@cornell.edu> - f0ab9a527d3a525325139026a7da0318b506abb4 Merge pull request #41 from LappleApple/patch-1 by Jeff Davidson <jpd236@cornell.edu> - a444f81d099179ab7c5fa5f460262a206125cd37 Merge pull request #18 from Ericliu001/master by Jeff Davidson <jpd236@cornell.edu>android-o-mr1-preview-2android-o-mr1-preview-1
am: cc4c86984e
Change-Id: I3c1cbe4af3237ac4bf2f54e4276bbc60151af82e
31 files changed, 678 insertions, 531 deletions
diff --git a/.travis.yml b/.travis.yml index 5f6fa73..4c80cfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,3 +25,12 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - $HOME/.android/build-cache + +env: + global: + - secure: pQb1CpD3heCma1831L4rnZGHgn6acFoBMlRrMBWc7nXXM5WwQ4cMSwZ49TVGMMVobA76vKm6dZj4n5ut5/i9amS9Ohlxbpjw5jVp1dqgev6PMupimkYfeTjTVdfGDlian8J7g4kQcVBXghq2WR+rUVh+VlhenE7r7lzEAscWRAKmo0Dzv1E3idI9ik8gkcAdE0ICJmYoq7vxxBhI3XzofHdq0bkzaCzU4P4OSX5HFN8Z4EloTeEeKZj0f02NwS+rrYzhNDs+8619W1gffAjJ9+C0im7iU8axoxMDE9e5dO4PEO03ol/U7F1NNmwFJ+Q78f9qmNw8u4EYnfzUn7nkH+GvYvX9jGlJoNqXPet0eKsJVIShx5eZjpgtUO0ICZKxYuqtKVfj55MPicMLhKFr1VvUTI4noomQVraUAtnI/H0Ia28795WLry4lbMRuYAwCpsmKJKKNQ5sj3dkokpMmn7SH64zNIxKHpAEkrg3UUWh0LUvk7ePn45RAPXS5M0EX0z6LvQEi5dRVfSc2b69CX9vXyqoG/29xwdaYV+BSZjc1zDFN9BXAwDWhfs86ONSmovi5SXoKd/7FWoEAQVX/ZAW93HuScq5pMq7RYbjN4IThqhCC/K6TgwC2PRtPaIK/Rucc9Gvs8Gh5nphabSDzIPoGUEXXPG+lsG10G9qxGKU= + - secure: NhFfhYu6WNuFNpJt3GoGMYpWdYv3xfS7FExeEidvIol+GbuglCv/FOVYVFdHnE52+lvmLz1UTdOSvMhbQe6mhL+ZJQYCILH5i3ion92MlH6YWaJisafzBvSrhUjMFzo0LEJwJ9k3FsAARc0QR+GIsyd0un4MrybeZwFX0VzLElxRl5bEXnxXqYjdw7Mzs3fyROnxees1waz6Ksu3v8NPk45ooy633DGl3txVI/05rT89WwtUnvz0ieAAMiOUTOWs6lAkb45hAzdBng+U5YNL/BiK1y+o4PFYCF7nNJ2A2s6qG/hZYiTa3tLR2tgb5sDHQl4KZ2YEVdEGCZ52nY5vNXAWWZIp1thJYOmaVXFHn/YG8+Qpr2+maUHjzlttIOWl41JSN54emyrK5ZbbAKzMOYKZeRcDP41oP9YlqnsPSL2GBbxqG2GAzv5btHXQPTJVpbqwZUOjYM6jetzRifIWisreN8qQ3cWXLe5l/ra0E6TUbOBH2fC0tZwYe6tkmSGnskA81KwmX7qqKdeJvlAYbGS6MylOSlMmrYqmpKCpofatT1/ZYrnZZSPNRZGQt0L+D1LaYMYlEkSqboxb/OCBL+oD6ncsJWT2YYTQSPdqfmUHF8pNi3hCJe04pqVCbaoEYyMrPlZ+PeZnYDuNSC+Moyu+z7Cqikqg2flzH+F0Az0= + +# Publish a SNAPSHOT build for all commits to master. +after_success: + - ./publish-snapshot-on-commit.sh
\ No newline at end of file @@ -1,7 +1,7 @@ ### Volley -Volley is an HTTP library that makes networking for Android apps easier and most +Volley is an HTTP library that makes networking for Android apps easier and, most importantly, faster. -For more about Volley and how to use it, visit the [Android developer training +For more information about Volley and how to use it, visit the [Android developer training page](https://developer.android.com/training/volley/index.html). diff --git a/bintray-info-template.json b/bintray-info-template.json new file mode 100644 index 0000000..4021964 --- /dev/null +++ b/bintray-info-template.json @@ -0,0 +1,21 @@ +{ + "package": { + "user_org": "android", + "repo": "android-utils", + "group": "com.android.volley", + "name": "com.android.volley.volley", + "desc": "Volley Android library", + "licenses": ["Apache-2.0"], + "website_url": "https://github.com/google/volley", + "issue_tracker_url": "https://github.com/google/volley/issues", + "vcs_url": "https://github.com/google/volley.git", + "labels": ["android", "volley", "network"], + "public_download_numbers": true + }, + "version": { + "name": "$VERSION$", + "desc": "Volley Android library version $VERSION$", + "gpgSign": false + }, + "publish": true +}
\ No newline at end of file diff --git a/bintray.gradle b/bintray.gradle index f07693e..8914c1d 100644 --- a/bintray.gradle +++ b/bintray.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2" + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.0.0" } } @@ -12,13 +12,12 @@ buildscript { // is currently not capable of loading plugins by Id if the dependency is anywhere else than // in the main project build.gradle. This file is "imported" into the project's build.gradle // through a "apply from:". -apply plugin: com.jfrog.bintray.gradle.BintrayPlugin +apply plugin: org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin apply plugin: 'maven-publish' -project.ext.group = 'com.android.volley' -project.ext.archivesBaseName = 'volley' -project.ext.version = '1.0.0' -project.ext.pomDesc = 'Volley Android library' +def bintrayInfoFilePath = "$buildDir/outputs/bintray-descriptor.bintray-info.json" + +project.ext.version = '1.0.1-SNAPSHOT' task sourcesJar(type: Jar) { classifier = 'sources' @@ -35,6 +34,16 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir } +task bintrayInfoFile { + outputs.file(bintrayInfoFilePath) + doLast { + println 'Creating bintray-info.json' + String fileContent = new File("$rootDir/bintray-info-template.json").getText('UTF-8') + fileContent = fileContent.replace('$VERSION$', project.ext.version) + ((new File(bintrayInfoFilePath))).write(fileContent) + } +} + artifacts { archives javadocJar archives sourcesJar @@ -43,45 +52,36 @@ artifacts { publishing { publications { library(MavenPublication) { - groupId project.ext.group - artifactId project.ext.archivesBaseName + groupId 'com.android.volley' + artifactId 'volley' version project.ext.version // Release AAR, Sources, and JavaDoc artifact "$buildDir/outputs/aar/volley-release.aar" artifact sourcesJar artifact javadocJar + artifact(bintrayInfoFilePath) { + builtBy bintrayInfoFile + extension "bintray-info.json" + } } } } -bintray { - user = System.env.BINTRAY_USER - key = System.env.BINTRAY_USER_KEY - - publications = [ 'library' ] - - publish = project.hasProperty("release") - pkg { - userOrg = 'android' - repo = 'android-utils' - group = project.ext.group - name = project.ext.group + '.' + project.ext.archivesBaseName - desc = project.ext.pomDesc - licenses = [ 'Apache-2.0' ] - websiteUrl = 'https://tools.android.com' - issueTrackerUrl = 'https://code.google.com/p/android/' - vcsUrl = 'https://android.googlesource.com/platform/frameworks/volley.git' - labels = ['android', 'volley', 'network'] - publicDownloadNumbers = true - - version { - name = project.ext.version - desc = project.ext.pomDesc + ' version ' + project.ext.version - gpg { - sign = true - passphrase = System.env.GPG_PASSPHRASE - } +artifactory { + contextUrl = "https://oss.jfrog.org" + publish { + repository { + repoKey = 'oss-snapshot-local' + username = System.env.CI_DEPLOY_USERNAME + password = System.env.CI_DEPLOY_PASSWORD + } + defaults { + publications('library') + publishArtifacts = true } } -} + resolve { + repoKey = 'jcenter' + } +}
\ No newline at end of file diff --git a/build.xml b/build.xml deleted file mode 100644 index 219c63c..0000000 --- a/build.xml +++ /dev/null @@ -1,92 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="volley" default="help"> - - <!-- The local.properties file is created and updated by the 'android' tool. - It contains the path to the SDK. It should *NOT* be checked into - Version Control Systems. --> - <property file="local.properties" /> - - <!-- The ant.properties file can be created by you. It is only edited by the - 'android' tool to add properties to it. - This is the place to change some Ant specific build properties. - Here are some properties you may want to change/update: - - source.dir - The name of the source directory. Default is 'src'. - out.dir - The name of the output directory. Default is 'bin'. - - For other overridable properties, look at the beginning of the rules - files in the SDK, at tools/ant/build.xml - - Properties related to the SDK location or the project target should - be updated using the 'android' tool with the 'update' action. - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. - - --> - <property file="ant.properties" /> - - <!-- if sdk.dir was not set from one of the property file, then - get it from the ANDROID_HOME env var. - This must be done before we load project.properties since - the proguard config can use sdk.dir --> - <property environment="env" /> - <condition property="sdk.dir" value="${env.ANDROID_HOME}"> - <isset property="env.ANDROID_HOME" /> - </condition> - - <!-- The project.properties file is created and updated by the 'android' - tool, as well as ADT. - - This contains project specific properties such as project target, and library - dependencies. Lower level build properties are stored in ant.properties - (or in .classpath for Eclipse projects). - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. --> - <loadproperties srcFile="project.properties" /> - - <!-- quick check on sdk.dir --> - <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." - unless="sdk.dir" - /> - - <!-- - Import per project custom build rules if present at the root of the project. - This is the place to put custom intermediary targets such as: - -pre-build - -pre-compile - -post-compile (This is typically used for code obfuscation. - Compiled code location: ${out.classes.absolute.dir} - If this is not done in place, override ${out.dex.input.absolute.dir}) - -post-package - -post-build - -pre-clean - --> - <import file="custom_rules.xml" optional="true" /> - - <!-- Import the actual build file. - - To customize existing targets, there are two options: - - Customize only one target: - - copy/paste the target into this file, *before* the - <import> task. - - customize it to your needs. - - Customize the whole content of build.xml - - copy/paste the content of the rules files (minus the top node) - into this file, replacing the <import> task. - - customize to your needs. - - *********************** - ****** IMPORTANT ****** - *********************** - In all cases you must update the value of version-tag below to read 'custom' instead of an integer, - in order to avoid having your file be overridden by tools such as "android update project" - --> - <!-- version-tag: 1 --> - <import file="${sdk.dir}/tools/ant/build.xml" /> - -</project> diff --git a/custom_rules.xml b/custom_rules.xml deleted file mode 100644 index 1b94e5d..0000000 --- a/custom_rules.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="volley-rules" default="help"> - - <!-- A rule to generate the JAR for inclusion in an Android - application. Output file will be bin/volley.jar --> - <target name="jar" depends="-compile"> - <jar destfile="bin/volley.jar" - basedir="bin/classes" /> - </target> -</project> diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 7c37e0f..0000000 --- a/pom.xml +++ /dev/null @@ -1,168 +0,0 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <groupId>com.android.volley</groupId> - <artifactId>volley</artifactId> - <version>1.0-SNAPSHOT</version> - <packaging>jar</packaging> - - <name>volley</name> - <url>http://android.com</url> - - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - - <java.version>1.6</java.version> - </properties> - - <dependencies> - <dependency> - <groupId>com.google.android</groupId> - <artifactId>android</artifactId> - <version>4.1.1.4</version> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.10</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.robolectric</groupId> - <artifactId>robolectric</artifactId> - <version>3.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>1.9.5</version> - <scope>test</scope> - </dependency> - </dependencies> - - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>com.jayway.maven.plugins.android.generation2</groupId> - <artifactId>android-maven-plugin</artifactId> - <version>3.8.1</version> - <configuration> - <sdk> - <platform>19</platform> - </sdk> - </configuration> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>3.0</version> - <configuration> - <source>${java.version}</source> - <target>${java.version}</target> - </configuration> - </plugin> - </plugins> - </pluginManagement> - </build> - - <profiles> - <profile> - <id>debug</id> - <activation> - <activeByDefault>true</activeByDefault> - <property> - <name>performDebugBuild</name> - <value>true</value> - </property> - </activation> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>2.18.1</version> - <executions> - <execution> - <id>default-test</id> - <configuration> - <argLine>${surefireArgLine}</argLine> - </configuration> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - <!-- don't upgrade the version. newer versions generate different results - see https://github.com/jacoco/jacoco/issues/286 --> - <version>0.7.2.201409121644</version> - <executions> - <execution> - <id>pre-unit-test</id> - <goals> - <goal>prepare-agent</goal> - </goals> - <configuration> - <destFile>${project.build.directory}/surefire-reports/jacoco-ut.exec</destFile> - <propertyName>surefireArgLine</propertyName> - </configuration> - </execution> - <execution> - <id>jacoco-report</id> - <phase>post-integration-test</phase> - <goals> - <goal>report</goal> - <goal>check</goal> - </goals> - <configuration> - <dataFile>${project.build.directory}/surefire-reports/jacoco-ut.exec</dataFile> - <outputDirectory>${project.build.directory}/jacoco-report</outputDirectory> - <rules> - <rule> - <element>BUNDLE</element> - <limits> - <limit> - <counter>INSTRUCTION</counter> - <value>COVEREDRATIO</value> - <minimum>0.40</minimum> - </limit> - <!-- enable this if you want that the build breaks if there is a class without a test --> - <!-- - <limit> - <counter>CLASS</counter> - <value>MISSEDCOUNT</value> - <maximum>0</maximum> - </limit> - --> - </limits> - </rule> - <!-- enable this if you want a limit for each java class --> - <!-- - <rule> - <element>CLASS</element> - <excludes> - <exclude>*Test</exclude> - </excludes> - <limits> - <limit> - <counter>LINE</counter> - <value>COVEREDRATIO</value> - <minimum>0.10</minimum> - </limit> - </limits> - </rule> - --> - </rules> - </configuration> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - </profiles> -</project> diff --git a/publish-snapshot-on-commit.sh b/publish-snapshot-on-commit.sh new file mode 100755 index 0000000..0d0e034 --- /dev/null +++ b/publish-snapshot-on-commit.sh @@ -0,0 +1,13 @@ +set -eu + +if [ "$TRAVIS_REPO_SLUG" == "google/volley" ] && \ + [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ + [ "$TRAVIS_BRANCH" == "master" ]; then + echo -e "Publishing snapshot build to OJO...\n" + + ./gradlew artifactoryPublish + + echo -e "Published snapshot build to OJO" +else + echo -e "Not publishing snapshot" +fi
\ No newline at end of file diff --git a/rules.gradle b/rules.gradle index c7c0f70..af81ac2 100644 --- a/rules.gradle +++ b/rules.gradle @@ -14,17 +14,9 @@ android { // Check if the android plugin version supports unit testing. if (configurations.findByName("testCompile")) { dependencies { - testCompile "junit:junit:4.10" - testCompile "org.mockito:mockito-core:1.9.5" + testCompile "junit:junit:4.12" + testCompile "org.hamcrest:hamcrest-library:1.3" + testCompile "org.mockito:mockito-core:2.2.29" testCompile "org.robolectric:robolectric:3.0" } } - -// TODO(#4): Remove this once Javadoc errors are fixed -if (JavaVersion.current().isJava8Compatible()) { - allprojects { - tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') - } - } -} diff --git a/src/main/java/com/android/volley/Cache.java b/src/main/java/com/android/volley/Cache.java index f1ec757..8482c22 100644 --- a/src/main/java/com/android/volley/Cache.java +++ b/src/main/java/com/android/volley/Cache.java @@ -28,43 +28,43 @@ public interface Cache { * @param key Cache key * @return An {@link Entry} or null in the event of a cache miss */ - public Entry get(String key); + Entry get(String key); /** * Adds or replaces an entry to the cache. * @param key Cache key * @param entry Data to store and metadata for cache coherency, TTL, etc. */ - public void put(String key, Entry entry); + void put(String key, Entry entry); /** * Performs any potentially long-running actions needed to initialize the cache; * will be called from a worker thread. */ - public void initialize(); + void initialize(); /** * Invalidates an entry in the cache. * @param key Cache key * @param fullExpire True to fully expire the entry, false to soft expire */ - public void invalidate(String key, boolean fullExpire); + void invalidate(String key, boolean fullExpire); /** * Removes an entry from the cache. * @param key Cache key */ - public void remove(String key); + void remove(String key); /** * Empties the cache. */ - public void clear(); + void clear(); /** * Data and metadata for an entry returned by the cache. */ - public static class Entry { + class Entry { /** The data returned from cache. */ public byte[] data; diff --git a/src/main/java/com/android/volley/CacheDispatcher.java b/src/main/java/com/android/volley/CacheDispatcher.java index 18d219b..1e7dfc4 100644 --- a/src/main/java/com/android/volley/CacheDispatcher.java +++ b/src/main/java/com/android/volley/CacheDispatcher.java @@ -151,7 +151,6 @@ public class CacheDispatcher extends Thread { if (mQuit) { return; } - continue; } } } diff --git a/src/main/java/com/android/volley/Network.java b/src/main/java/com/android/volley/Network.java index ab45830..1e367c8 100644 --- a/src/main/java/com/android/volley/Network.java +++ b/src/main/java/com/android/volley/Network.java @@ -26,5 +26,5 @@ public interface Network { * @return A {@link NetworkResponse} with data and caching metadata; will never be null * @throws VolleyError on errors */ - public NetworkResponse performRequest(Request<?> request) throws VolleyError; + NetworkResponse performRequest(Request<?> request) throws VolleyError; } diff --git a/src/main/java/com/android/volley/RequestQueue.java b/src/main/java/com/android/volley/RequestQueue.java index 4324590..0f2e756 100644 --- a/src/main/java/com/android/volley/RequestQueue.java +++ b/src/main/java/com/android/volley/RequestQueue.java @@ -40,13 +40,13 @@ import java.util.concurrent.atomic.AtomicInteger; public class RequestQueue { /** Callback interface for completed requests. */ - public static interface RequestFinishedListener<T> { + public interface RequestFinishedListener<T> { /** Called when a request has finished processing. */ - public void onRequestFinished(Request<T> request); + void onRequestFinished(Request<T> request); } /** Used for generating monotonically-increasing sequence numbers for requests. */ - private AtomicInteger mSequenceGenerator = new AtomicInteger(); + private final AtomicInteger mSequenceGenerator = new AtomicInteger(); /** * Staging area for requests that already have a duplicate request in flight. @@ -59,7 +59,7 @@ public class RequestQueue { * </ul> */ private final Map<String, Queue<Request<?>>> mWaitingRequests = - new HashMap<String, Queue<Request<?>>>(); + new HashMap<>(); /** * The set of all requests currently being processed by this RequestQueue. A Request @@ -70,11 +70,11 @@ public class RequestQueue { /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = - new PriorityBlockingQueue<Request<?>>(); + new PriorityBlockingQueue<>(); /** The queue of requests that are actually going out to the network. */ private final PriorityBlockingQueue<Request<?>> mNetworkQueue = - new PriorityBlockingQueue<Request<?>>(); + new PriorityBlockingQueue<>(); /** Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; @@ -89,13 +89,13 @@ public class RequestQueue { private final ResponseDelivery mDelivery; /** The network dispatchers. */ - private NetworkDispatcher[] mDispatchers; + private final NetworkDispatcher[] mDispatchers; /** The cache dispatcher. */ private CacheDispatcher mCacheDispatcher; - private List<RequestFinishedListener> mFinishedListeners = - new ArrayList<RequestFinishedListener>(); + private final List<RequestFinishedListener> mFinishedListeners = + new ArrayList<>(); /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. @@ -160,9 +160,9 @@ public class RequestQueue { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } - for (int i = 0; i < mDispatchers.length; i++) { - if (mDispatchers[i] != null) { - mDispatchers[i].quit(); + for (final NetworkDispatcher mDispatcher : mDispatchers) { + if (mDispatcher != null) { + mDispatcher.quit(); } } } @@ -186,7 +186,7 @@ public class RequestQueue { * {@link RequestQueue#cancelAll(RequestFilter)}. */ public interface RequestFilter { - public boolean apply(Request<?> request); + boolean apply(Request<?> request); } /** @@ -248,7 +248,7 @@ public class RequestQueue { // There is already a request in flight. Queue up. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { - stagedRequests = new LinkedList<Request<?>>(); + stagedRequests = new LinkedList<>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); diff --git a/src/main/java/com/android/volley/Response.java b/src/main/java/com/android/volley/Response.java index 1165595..1fe7215 100644 --- a/src/main/java/com/android/volley/Response.java +++ b/src/main/java/com/android/volley/Response.java @@ -26,7 +26,7 @@ public class Response<T> { /** Callback interface for delivering parsed responses. */ public interface Listener<T> { /** Called when a response is received. */ - public void onResponse(T response); + void onResponse(T response); } /** Callback interface for delivering error responses. */ @@ -35,7 +35,7 @@ public class Response<T> { * Callback method that an error has been occurred with the * provided error code and optional user-readable message. */ - public void onErrorResponse(VolleyError error); + void onErrorResponse(VolleyError error); } /** Returns a successful response containing the parsed result. */ diff --git a/src/main/java/com/android/volley/ResponseDelivery.java b/src/main/java/com/android/volley/ResponseDelivery.java index 87706af..bef3df5 100644 --- a/src/main/java/com/android/volley/ResponseDelivery.java +++ b/src/main/java/com/android/volley/ResponseDelivery.java @@ -20,16 +20,16 @@ public interface ResponseDelivery { /** * Parses a response from the network or cache and delivers it. */ - public void postResponse(Request<?> request, Response<?> response); + void postResponse(Request<?> request, Response<?> response); /** * Parses a response from the network or cache and delivers it. The provided * Runnable will be executed after delivery. */ - public void postResponse(Request<?> request, Response<?> response, Runnable runnable); + void postResponse(Request<?> request, Response<?> response, Runnable runnable); /** * Posts an error for the given request. */ - public void postError(Request<?> request, VolleyError error); + void postError(Request<?> request, VolleyError error); } diff --git a/src/main/java/com/android/volley/RetryPolicy.java b/src/main/java/com/android/volley/RetryPolicy.java index 0dd198b..f58678d 100644 --- a/src/main/java/com/android/volley/RetryPolicy.java +++ b/src/main/java/com/android/volley/RetryPolicy.java @@ -24,12 +24,12 @@ public interface RetryPolicy { /** * Returns the current timeout (used for logging). */ - public int getCurrentTimeout(); + int getCurrentTimeout(); /** * Returns the current retry count (used for logging). */ - public int getCurrentRetryCount(); + int getCurrentRetryCount(); /** * Prepares for the next retry by applying a backoff to the timeout. @@ -37,5 +37,5 @@ public interface RetryPolicy { * @throws VolleyError In the event that the retry could not be performed (for example if we * ran out of attempts), the passed in error is thrown. */ - public void retry(VolleyError error) throws VolleyError; + void retry(VolleyError error) throws VolleyError; } diff --git a/src/main/java/com/android/volley/VolleyLog.java b/src/main/java/com/android/volley/VolleyLog.java index ffe9eb8..fc776e5 100644 --- a/src/main/java/com/android/volley/VolleyLog.java +++ b/src/main/java/com/android/volley/VolleyLog.java @@ -25,8 +25,8 @@ import java.util.Locale; /** * Logging helper class. - * <p/> - * to see Volley logs call:<br/> + * <p> + * to see Volley logs call:<br> * {@code <android-sdk>/platform-tools/adb shell setprop log.tag.Volley VERBOSE} */ public class VolleyLog { @@ -37,9 +37,9 @@ public class VolleyLog { /** * Customize the log tag for your application, so that other apps * using Volley don't mix their logs with yours. - * <br /> + * <br> * Enable the log property for your tag before starting your app: - * <br /> + * <br> * {@code adb shell setprop log.tag.<tag>} */ public static void setTag(String tag) { diff --git a/src/main/java/com/android/volley/toolbox/Authenticator.java b/src/main/java/com/android/volley/toolbox/Authenticator.java index d9f5e3c..adfc996 100644 --- a/src/main/java/com/android/volley/toolbox/Authenticator.java +++ b/src/main/java/com/android/volley/toolbox/Authenticator.java @@ -27,10 +27,10 @@ public interface Authenticator { * * @throws AuthFailureError If authentication did not succeed */ - public String getAuthToken() throws AuthFailureError; + String getAuthToken() throws AuthFailureError; /** * Invalidates the provided auth token. */ - public void invalidateAuthToken(String authToken); + void invalidateAuthToken(String authToken); } diff --git a/src/main/java/com/android/volley/toolbox/BasicNetwork.java b/src/main/java/com/android/volley/toolbox/BasicNetwork.java index 37c35ec..96fb66e 100644 --- a/src/main/java/com/android/volley/toolbox/BasicNetwork.java +++ b/src/main/java/com/android/volley/toolbox/BasicNetwork.java @@ -57,9 +57,9 @@ import java.util.TreeMap; public class BasicNetwork implements Network { protected static final boolean DEBUG = VolleyLog.DEBUG; - private static int SLOW_REQUEST_THRESHOLD_MS = 3000; + private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; - private static int DEFAULT_POOL_SIZE = 4096; + private static final int DEFAULT_POOL_SIZE = 4096; protected final HttpStack mHttpStack; @@ -257,7 +257,7 @@ public class BasicNetwork implements Network { } catch (IOException e) { // This can happen if there was an exception above that left the entity in // an invalid state. - VolleyLog.v("Error occured when calling consumingContent"); + VolleyLog.v("Error occurred when calling consumingContent"); } mPool.returnBuf(buffer); bytes.close(); @@ -265,7 +265,7 @@ public class BasicNetwork implements Network { } /** - * Converts Headers[] to Map<String, String>. + * Converts Headers[] to Map<String, String>. */ protected static Map<String, String> convertHeaders(Header[] headers) { Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); diff --git a/src/main/java/com/android/volley/toolbox/ByteArrayPool.java b/src/main/java/com/android/volley/toolbox/ByteArrayPool.java index af95076..c8ca2c2 100644 --- a/src/main/java/com/android/volley/toolbox/ByteArrayPool.java +++ b/src/main/java/com/android/volley/toolbox/ByteArrayPool.java @@ -53,8 +53,8 @@ import java.util.List; */ public class ByteArrayPool { /** The buffer pool, arranged both by last use and by buffer size */ - private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>(); - private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64); + private final List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>(); + private final List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64); /** The total size of the buffers in the pool */ private int mCurrentSize = 0; diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java index f724d72..0e65183 100644 --- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java +++ b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java @@ -17,15 +17,18 @@ package com.android.volley.toolbox; import android.os.SystemClock; +import android.text.TextUtils; import com.android.volley.Cache; import com.android.volley.VolleyLog; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.IOException; @@ -110,30 +113,31 @@ public class DiskBasedCache implements Cache { if (entry == null) { return null; } - File file = getFileForKey(key); - CountingInputStream cis = null; try { - cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file))); - CacheHeader.readHeader(cis); // eat header - byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); - return entry.toCacheEntry(data); + CountingInputStream cis = new CountingInputStream( + new BufferedInputStream(createInputStream(file)), file.length()); + try { + CacheHeader entryOnDisk = CacheHeader.readHeader(cis); + if (!TextUtils.equals(key, entryOnDisk.key)) { + // File was shared by two keys and now holds data for a different entry! + VolleyLog.d("%s: key=%s, found=%s", + file.getAbsolutePath(), key, entryOnDisk.key); + // Remove key whose contents on disk have been replaced. + removeEntry(key); + return null; + } + byte[] data = streamToBytes(cis, cis.bytesRemaining()); + return entry.toCacheEntry(data); + } finally { + // Any IOException thrown here is handled by the below catch block by design. + //noinspection ThrowFromFinallyBlock + cis.close(); + } } catch (IOException e) { VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); remove(key); return null; - } catch (NegativeArraySizeException e) { - VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); - remove(key); - return null; - } finally { - if (cis != null) { - try { - cis.close(); - } catch (IOException ioe) { - return null; - } - } } } @@ -149,28 +153,29 @@ public class DiskBasedCache implements Cache { } return; } - File[] files = mRootDirectory.listFiles(); if (files == null) { return; } for (File file : files) { - BufferedInputStream fis = null; try { - fis = new BufferedInputStream(new FileInputStream(file)); - CacheHeader entry = CacheHeader.readHeader(fis); - entry.size = file.length(); - putEntry(entry.key, entry); - } catch (IOException e) { - if (file != null) { - file.delete(); - } - } finally { + long entrySize = file.length(); + CountingInputStream cis = new CountingInputStream( + new BufferedInputStream(createInputStream(file)), entrySize); try { - if (fis != null) { - fis.close(); - } - } catch (IOException ignored) { } + CacheHeader entry = CacheHeader.readHeader(cis); + // NOTE: When this entry was put, its size was recorded as data.length, but + // when the entry is initialized below, its size is recorded as file.length() + entry.size = entrySize; + putEntry(entry.key, entry); + } finally { + // Any IOException thrown here is handled by the below catch block by design. + //noinspection ThrowFromFinallyBlock + cis.close(); + } + } catch (IOException e) { + //noinspection ResultOfMethodCallIgnored + file.delete(); } } } @@ -190,7 +195,6 @@ public class DiskBasedCache implements Cache { } put(key, entry); } - } /** @@ -201,7 +205,7 @@ public class DiskBasedCache implements Cache { pruneIfNeeded(entry.data.length); File file = getFileForKey(key); try { - BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); + BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file)); CacheHeader e = new CacheHeader(key, entry); boolean success = e.writeHeader(fos); if (!success) { @@ -313,107 +317,118 @@ public class DiskBasedCache implements Cache { * Removes the entry identified by 'key' from the cache. */ private void removeEntry(String key) { - CacheHeader entry = mEntries.get(key); - if (entry != null) { - mTotalSize -= entry.size; - mEntries.remove(key); + CacheHeader removed = mEntries.remove(key); + if (removed != null) { + mTotalSize -= removed.size; } } /** - * Reads the contents of an InputStream into a byte[]. - * */ - private static byte[] streamToBytes(InputStream in, int length) throws IOException { - byte[] bytes = new byte[length]; - int count; - int pos = 0; - while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { - pos += count; - } - if (pos != length) { - throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); + * Reads length bytes from CountingInputStream into byte array. + * @param cis input stream + * @param length number of bytes to read + * @throws IOException if fails to read all bytes + */ + //VisibleForTesting + static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException { + long maxLength = cis.bytesRemaining(); + // Length cannot be negative or greater than bytes remaining, and must not overflow int. + if (length < 0 || length > maxLength || (int) length != length) { + throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength); } + byte[] bytes = new byte[(int) length]; + new DataInputStream(cis).readFully(bytes); return bytes; } + //VisibleForTesting + InputStream createInputStream(File file) throws FileNotFoundException { + return new FileInputStream(file); + } + + //VisibleForTesting + OutputStream createOutputStream(File file) throws FileNotFoundException { + return new FileOutputStream(file); + } + /** * Handles holding onto the cache headers for an entry. */ - // Visible for testing. + //VisibleForTesting static class CacheHeader { /** The size of the data identified by this CacheHeader. (This is not * serialized to disk. */ - public long size; + long size; /** The key that identifies the cache entry. */ - public String key; + final String key; /** ETag for cache coherence. */ - public String etag; + final String etag; /** Date of this response as reported by the server. */ - public long serverDate; + final long serverDate; /** The last modified date for the requested object. */ - public long lastModified; + final long lastModified; /** TTL for this record. */ - public long ttl; + final long ttl; /** Soft TTL for this record. */ - public long softTtl; + final long softTtl; /** Headers from the response resulting in this cache entry. */ - public Map<String, String> responseHeaders; + final Map<String, String> responseHeaders; - private CacheHeader() { } + private CacheHeader(String key, String etag, long serverDate, long lastModified, long ttl, + long softTtl, Map<String, String> responseHeaders) { + this.key = key; + this.etag = ("".equals(etag)) ? null : etag; + this.serverDate = serverDate; + this.lastModified = lastModified; + this.ttl = ttl; + this.softTtl = softTtl; + this.responseHeaders = responseHeaders; + } /** * Instantiates a new CacheHeader object * @param key The key that identifies the cache entry * @param entry The cache entry. */ - public CacheHeader(String key, Entry entry) { - this.key = key; - this.size = entry.data.length; - this.etag = entry.etag; - this.serverDate = entry.serverDate; - this.lastModified = entry.lastModified; - this.ttl = entry.ttl; - this.softTtl = entry.softTtl; - this.responseHeaders = entry.responseHeaders; + CacheHeader(String key, Entry entry) { + this(key, entry.etag, entry.serverDate, entry.lastModified, entry.ttl, entry.softTtl, + entry.responseHeaders); + size = entry.data.length; } /** - * Reads the header off of an InputStream and returns a CacheHeader object. + * Reads the header from a CountingInputStream and returns a CacheHeader object. * @param is The InputStream to read from. - * @throws IOException + * @throws IOException if fails to read header */ - public static CacheHeader readHeader(InputStream is) throws IOException { - CacheHeader entry = new CacheHeader(); + static CacheHeader readHeader(CountingInputStream is) throws IOException { int magic = readInt(is); if (magic != CACHE_MAGIC) { // don't bother deleting, it'll get pruned eventually throw new IOException(); } - entry.key = readString(is); - entry.etag = readString(is); - if (entry.etag.equals("")) { - entry.etag = null; - } - entry.serverDate = readLong(is); - entry.lastModified = readLong(is); - entry.ttl = readLong(is); - entry.softTtl = readLong(is); - entry.responseHeaders = readStringStringMap(is); - - return entry; + String key = readString(is); + String etag = readString(is); + long serverDate = readLong(is); + long lastModified = readLong(is); + long ttl = readLong(is); + long softTtl = readLong(is); + Map<String, String> responseHeaders = readStringStringMap(is); + return new CacheHeader( + key, etag, serverDate, lastModified, ttl, softTtl, responseHeaders); } /** * Creates a cache entry for the specified data. */ - public Entry toCacheEntry(byte[] data) { + Entry toCacheEntry(byte[] data) { Entry e = new Entry(); e.data = data; e.etag = etag; @@ -429,7 +444,7 @@ public class DiskBasedCache implements Cache { /** * Writes the contents of this CacheHeader to the specified OutputStream. */ - public boolean writeHeader(OutputStream os) { + boolean writeHeader(OutputStream os) { try { writeInt(os, CACHE_MAGIC); writeString(os, key); @@ -446,14 +461,16 @@ public class DiskBasedCache implements Cache { return false; } } - } - private static class CountingInputStream extends FilterInputStream { - private int bytesRead = 0; + //VisibleForTesting + static class CountingInputStream extends FilterInputStream { + private final long length; + private long bytesRead; - private CountingInputStream(InputStream in) { + CountingInputStream(InputStream in, long length) { super(in); + this.length = length; } @Override @@ -473,6 +490,15 @@ public class DiskBasedCache implements Cache { } return result; } + + //VisibleForTesting + long bytesRead() { + return bytesRead; + } + + long bytesRemaining() { + return length - bytesRead; + } } /* @@ -480,6 +506,8 @@ public class DiskBasedCache implements Cache { * headers on disk. Once upon a time, this used the standard Java * Object{Input,Output}Stream, but the default implementation relies heavily * on reflection (even for standard types) and generates a ton of garbage. + * + * TODO: Replace by standard DataInput and DataOutput in next cache version. */ /** @@ -540,9 +568,9 @@ public class DiskBasedCache implements Cache { os.write(b, 0, b.length); } - static String readString(InputStream is) throws IOException { - int n = (int) readLong(is); - byte[] b = streamToBytes(is, n); + static String readString(CountingInputStream cis) throws IOException { + long n = readLong(cis); + byte[] b = streamToBytes(cis, n); return new String(b, "UTF-8"); } @@ -558,18 +586,17 @@ public class DiskBasedCache implements Cache { } } - static Map<String, String> readStringStringMap(InputStream is) throws IOException { - int size = readInt(is); + static Map<String, String> readStringStringMap(CountingInputStream cis) throws IOException { + int size = readInt(cis); Map<String, String> result = (size == 0) ? Collections.<String, String>emptyMap() : new HashMap<String, String>(size); for (int i = 0; i < size; i++) { - String key = readString(is).intern(); - String value = readString(is).intern(); + String key = readString(cis).intern(); + String value = readString(cis).intern(); result.put(key, value); } return result; } - } diff --git a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java index c3b48d8..f53063c 100644 --- a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java +++ b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java @@ -31,7 +31,7 @@ import java.util.Map; public class HttpHeaderParser { /** - * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. + * Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}. * * @param response The network response to parse headers from * @return a cache entry for the given response, or null if the response is not cacheable. diff --git a/src/main/java/com/android/volley/toolbox/HttpStack.java b/src/main/java/com/android/volley/toolbox/HttpStack.java index a52fd06..06f6017 100644 --- a/src/main/java/com/android/volley/toolbox/HttpStack.java +++ b/src/main/java/com/android/volley/toolbox/HttpStack.java @@ -39,7 +39,7 @@ public interface HttpStack { * {@link Request#getHeaders()} * @return the HTTP response */ - public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) + HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; } diff --git a/src/main/java/com/android/volley/toolbox/HurlStack.java b/src/main/java/com/android/volley/toolbox/HurlStack.java index c53d5e0..66f441d 100644 --- a/src/main/java/com/android/volley/toolbox/HurlStack.java +++ b/src/main/java/com/android/volley/toolbox/HurlStack.java @@ -59,7 +59,7 @@ public class HurlStack implements HttpStack { * Returns a URL to use instead of the provided one, or null to indicate * this URL should not be used at all. */ - public String rewriteUrl(String originalUrl); + String rewriteUrl(String originalUrl); } private final UrlRewriter mUrlRewriter; @@ -209,16 +209,8 @@ public class HurlStack implements HttpStack { // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { - // Prepare output. There is no need to set Content-Length explicitly, - // since this is handled by HttpURLConnection using the size of the prepared - // output stream. - connection.setDoOutput(true); connection.setRequestMethod("POST"); - connection.addRequestProperty(HEADER_CONTENT_TYPE, - request.getPostBodyContentType()); - DataOutputStream out = new DataOutputStream(connection.getOutputStream()); - out.write(postBody); - out.close(); + addBody(connection, request, postBody); } break; case Method.GET: @@ -259,11 +251,19 @@ public class HurlStack implements HttpStack { throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { - connection.setDoOutput(true); - connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); - DataOutputStream out = new DataOutputStream(connection.getOutputStream()); - out.write(body); - out.close(); + addBody(connection, request, body); } } + + private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body) + throws IOException, AuthFailureError { + // Prepare output. There is no need to set Content-Length explicitly, + // since this is handled by HttpURLConnection using the size of the prepared + // output stream. + connection.setDoOutput(true); + connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + out.write(body); + out.close(); + } } diff --git a/src/main/java/com/android/volley/toolbox/ImageLoader.java b/src/main/java/com/android/volley/toolbox/ImageLoader.java index d5305e3..33a119b 100644 --- a/src/main/java/com/android/volley/toolbox/ImageLoader.java +++ b/src/main/java/com/android/volley/toolbox/ImageLoader.java @@ -72,8 +72,8 @@ public class ImageLoader { * must not block. Implementation with an LruCache is recommended. */ public interface ImageCache { - public Bitmap getBitmap(String url); - public void putBitmap(String url, Bitmap bitmap); + Bitmap getBitmap(String url); + void putBitmap(String url, Bitmap bitmap); } /** @@ -139,7 +139,7 @@ public class ImageLoader { * image loading in order to, for example, run an animation to fade in network loaded * images. */ - public void onResponse(ImageContainer response, boolean isImmediate); + void onResponse(ImageContainer response, boolean isImmediate); } /** diff --git a/src/main/java/com/android/volley/toolbox/ImageRequest.java b/src/main/java/com/android/volley/toolbox/ImageRequest.java index d663f5f..0f33cd8 100644 --- a/src/main/java/com/android/volley/toolbox/ImageRequest.java +++ b/src/main/java/com/android/volley/toolbox/ImageRequest.java @@ -46,7 +46,7 @@ public class ImageRequest extends Request<Bitmap> { private final Config mDecodeConfig; private final int mMaxWidth; private final int mMaxHeight; - private ScaleType mScaleType; + private final ScaleType mScaleType; /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */ private static final Object sDecodeLock = new Object(); @@ -216,7 +216,9 @@ public class ImageRequest extends Request<Bitmap> { @Override protected void deliverResponse(Bitmap response) { - mListener.onResponse(response); + if (mListener != null) { + mListener.onResponse(response); + } } /** diff --git a/src/main/java/com/android/volley/toolbox/JsonRequest.java b/src/main/java/com/android/volley/toolbox/JsonRequest.java index 2d58f40..40877b1 100644 --- a/src/main/java/com/android/volley/toolbox/JsonRequest.java +++ b/src/main/java/com/android/volley/toolbox/JsonRequest.java @@ -63,7 +63,9 @@ public abstract class JsonRequest<T> extends Request<T> { @Override protected void deliverResponse(T response) { - mListener.onResponse(response); + if (mListener != null) { + mListener.onResponse(response); + } } @Override diff --git a/src/main/java/com/android/volley/toolbox/NetworkImageView.java b/src/main/java/com/android/volley/toolbox/NetworkImageView.java index 324dbc0..60e4815 100644 --- a/src/main/java/com/android/volley/toolbox/NetworkImageView.java +++ b/src/main/java/com/android/volley/toolbox/NetworkImageView.java @@ -147,7 +147,9 @@ public class NetworkImageView extends ImageView { // The pre-existing content of this view didn't match the current URL. Load the new image // from the network. - ImageContainer newContainer = mImageLoader.get(mUrl, + + // update the ImageContainer to be the new bitmap container. + mImageContainer = mImageLoader.get(mUrl, new ImageListener() { @Override public void onErrorResponse(VolleyError error) { @@ -179,9 +181,6 @@ public class NetworkImageView extends ImageView { } } }, maxWidth, maxHeight, scaleType); - - // update the ImageContainer to be the new bitmap container. - mImageContainer = newContainer; } private void setDefaultImageOrNull() { diff --git a/src/main/java/com/android/volley/toolbox/StringRequest.java b/src/main/java/com/android/volley/toolbox/StringRequest.java index 6b3dfcf..05a62f6 100644 --- a/src/main/java/com/android/volley/toolbox/StringRequest.java +++ b/src/main/java/com/android/volley/toolbox/StringRequest.java @@ -57,7 +57,9 @@ public class StringRequest extends Request<String> { @Override protected void deliverResponse(String response) { - mListener.onResponse(response); + if (mListener != null) { + mListener.onResponse(response); + } } @Override diff --git a/src/test/java/com/android/volley/mock/TestRequest.java b/src/test/java/com/android/volley/mock/TestRequest.java index dfc4dc1..16bf79e 100644 --- a/src/test/java/com/android/volley/mock/TestRequest.java +++ b/src/test/java/com/android/volley/mock/TestRequest.java @@ -56,7 +56,7 @@ public class TestRequest { /** Test example of a POST request in the deprecated style. */ public static class DeprecatedPost extends Base { - private Map<String, String> mPostParams; + private final Map<String, String> mPostParams; public DeprecatedPost() { super(TEST_URL, null); @@ -89,7 +89,7 @@ public class TestRequest { /** Test example of a POST request in the new style with a body. */ public static class PostWithBody extends Post { - private Map<String, String> mParams; + private final Map<String, String> mParams; public PostWithBody() { mParams = new HashMap<String, String>(); diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java index 0a8be77..3d8d1f1 100644 --- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java +++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java @@ -18,46 +18,371 @@ package com.android.volley.toolbox; import com.android.volley.Cache; import com.android.volley.toolbox.DiskBasedCache.CacheHeader; +import com.android.volley.toolbox.DiskBasedCache.CountingInputStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.HashMap; import java.util.Map; +import java.util.Random; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.emptyArray; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +@RunWith(RobolectricTestRunner.class) +@Config(manifest="src/main/AndroidManifest.xml", sdk=16) public class DiskBasedCacheTest { - // Simple end-to-end serialize/deserialize test. - @Test public void cacheHeaderSerialization() throws Exception { - Cache.Entry e = new Cache.Entry(); - e.data = new byte[8]; - e.serverDate = 1234567L; - e.lastModified = 13572468L; - e.ttl = 9876543L; - e.softTtl = 8765432L; - e.etag = "etag"; - e.responseHeaders = new HashMap<String, String>(); - e.responseHeaders.put("fruit", "banana"); - - CacheHeader first = new CacheHeader("my-magical-key", e); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - first.writeHeader(baos); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - CacheHeader second = CacheHeader.readHeader(bais); + private static final int MAX_SIZE = 1024 * 1024; + + private Cache cache; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() throws IOException { + // Initialize empty cache + cache = new DiskBasedCache(temporaryFolder.getRoot(), MAX_SIZE); + cache.initialize(); + } + + @After + public void teardown() { + cache = null; + } + + @Test + public void testEmptyInitialize() { + assertThat(cache.get("key"), is(nullValue())); + } + + @Test + public void testPutGetZeroBytes() { + Cache.Entry entry = new Cache.Entry(); + entry.data = new byte[0]; + entry.serverDate = 1234567L; + entry.lastModified = 13572468L; + entry.ttl = 9876543L; + entry.softTtl = 8765432L; + entry.etag = "etag"; + entry.responseHeaders = new HashMap<>(); + entry.responseHeaders.put("fruit", "banana"); + entry.responseHeaders.put("color", "yellow"); + cache.put("my-magical-key", entry); + + assertThatEntriesAreEqual(cache.get("my-magical-key"), entry); + assertThat(cache.get("unknown-key"), is(nullValue())); + } + + @Test + public void testPutRemoveGet() { + Cache.Entry entry = randomData(511); + cache.put("key", entry); + + assertThatEntriesAreEqual(cache.get("key"), entry); + + cache.remove("key"); + assertThat(cache.get("key"), is(nullValue())); + assertThat(listCachedFiles(), is(emptyArray())); + } + + @Test + public void testPutClearGet() { + Cache.Entry entry = randomData(511); + cache.put("key", entry); + + assertThatEntriesAreEqual(cache.get("key"), entry); + + cache.clear(); + assertThat(cache.get("key"), is(nullValue())); + assertThat(listCachedFiles(), is(emptyArray())); + } + + @Test + public void testReinitialize() { + Cache.Entry entry = randomData(1023); + cache.put("key", entry); + + Cache copy = new DiskBasedCache(temporaryFolder.getRoot(), MAX_SIZE); + copy.initialize(); + + assertThatEntriesAreEqual(copy.get("key"), entry); + } + + @Test + public void testInvalidate() { + Cache.Entry entry = randomData(32); + entry.softTtl = 8765432L; + entry.ttl = 9876543L; + cache.put("key", entry); + + cache.invalidate("key", false); + entry.softTtl = 0; // expired + assertThatEntriesAreEqual(cache.get("key"), entry); + } + + @Test + public void testInvalidateFullExpire() { + Cache.Entry entry = randomData(32); + entry.softTtl = 8765432L; + entry.ttl = 9876543L; + cache.put("key", entry); + + cache.invalidate("key", true); + entry.softTtl = 0; // expired + entry.ttl = 0; // expired + assertThatEntriesAreEqual(cache.get("key"), entry); + } + + @Test + public void testTrim() { + Cache.Entry entry = randomData(2 * MAX_SIZE); + cache.put("oversize", entry); + + assertThatEntriesAreEqual(cache.get("oversize"), entry); + + entry = randomData(1024); + cache.put("kilobyte", entry); + + assertThat(cache.get("oversize"), is(nullValue())); + assertThatEntriesAreEqual(cache.get("kilobyte"), entry); + + Cache.Entry entry2 = randomData(1024); + cache.put("kilobyte2", entry2); + Cache.Entry entry3 = randomData(1024); + cache.put("kilobyte3", entry3); + + assertThatEntriesAreEqual(cache.get("kilobyte"), entry); + assertThatEntriesAreEqual(cache.get("kilobyte2"), entry2); + assertThatEntriesAreEqual(cache.get("kilobyte3"), entry3); + + entry = randomData(MAX_SIZE); + cache.put("max", entry); + + assertThat(cache.get("kilobyte"), is(nullValue())); + assertThat(cache.get("kilobyte2"), is(nullValue())); + assertThat(cache.get("kilobyte3"), is(nullValue())); + assertThatEntriesAreEqual(cache.get("max"), entry); + } + + @Test + @SuppressWarnings("TryFinallyCanBeTryWithResources") + public void testGetBadMagic() throws IOException { + // Cache something + Cache.Entry entry = randomData(1023); + cache.put("key", entry); + assertThatEntriesAreEqual(cache.get("key"), entry); + + // Overwrite the magic header + File cacheFolder = temporaryFolder.getRoot(); + File file = cacheFolder.listFiles()[0]; + FileOutputStream fos = new FileOutputStream(file); + try { + DiskBasedCache.writeInt(fos, 0); // overwrite magic + } finally { + //noinspection ThrowFromFinallyBlock + fos.close(); + } + + assertThat(cache.get("key"), is(nullValue())); + assertThat(listCachedFiles(), is(emptyArray())); + } + + @Test + @SuppressWarnings("TryFinallyCanBeTryWithResources") + public void testGetWrongKey() throws IOException { + // Cache something + Cache.Entry entry = randomData(1023); + cache.put("key", entry); + assertThatEntriesAreEqual(cache.get("key"), entry); + + // Access the cached file + File cacheFolder = temporaryFolder.getRoot(); + File file = cacheFolder.listFiles()[0]; + FileOutputStream fos = new FileOutputStream(file); + try { + // Overwrite with a different key + CacheHeader wrongHeader = new CacheHeader("bad", entry); + wrongHeader.writeHeader(fos); + } finally { + //noinspection ThrowFromFinallyBlock + fos.close(); + } + + // key is gone, but file is still there + assertThat(cache.get("key"), is(nullValue())); + assertThat(listCachedFiles(), is(arrayWithSize(1))); + + // Note: file is now a zombie because its key does not map to its name + } + + @Test + public void testStreamToBytesNegativeLength() throws IOException { + byte[] data = new byte[1]; + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(data), data.length); + exception.expect(IOException.class); + DiskBasedCache.streamToBytes(cis, -1); + } - assertEquals(first.key, second.key); - assertEquals(first.serverDate, second.serverDate); - assertEquals(first.lastModified, second.lastModified); - assertEquals(first.ttl, second.ttl); - assertEquals(first.softTtl, second.softTtl); - assertEquals(first.etag, second.etag); - assertEquals(first.responseHeaders, second.responseHeaders); + @Test + public void testStreamToBytesExcessiveLength() throws IOException { + byte[] data = new byte[1]; + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(data), data.length); + exception.expect(IOException.class); + DiskBasedCache.streamToBytes(cis, 2); + } + + @Test + public void testStreamToBytesOverflow() throws IOException { + byte[] data = new byte[0]; + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(data), 0x100000000L); + exception.expect(IOException.class); + DiskBasedCache.streamToBytes(cis, 0x100000000L); // int value is 0 } - @Test public void serializeInt() throws Exception { + @Test + public void testFileIsDeletedWhenWriteHeaderFails() throws IOException { + // Create DataOutputStream that throws IOException + OutputStream mockedOutputStream = spy(OutputStream.class); + doThrow(IOException.class).when(mockedOutputStream).write(anyInt()); + + // Create read-only copy that fails to write anything + DiskBasedCache readonly = spy((DiskBasedCache) cache); + doReturn(mockedOutputStream).when(readonly).createOutputStream(any(File.class)); + + // Attempt to write + readonly.put("key", randomData(1111)); + + // write is called at least once because each linked stream flushes when closed + verify(mockedOutputStream, atLeastOnce()).write(anyInt()); + assertThat(readonly.get("key"), is(nullValue())); + assertThat(listCachedFiles(), is(emptyArray())); + + // Note: original cache will try (without success) to read from file + assertThat(cache.get("key"), is(nullValue())); + } + + @Test + public void testIOExceptionInInitialize() throws IOException { + // Cache a few kilobytes + cache.put("kilobyte", randomData(1024)); + cache.put("kilobyte2", randomData(1024)); + cache.put("kilobyte3", randomData(1024)); + + // Create DataInputStream that throws IOException + InputStream mockedInputStream = spy(InputStream.class); + //noinspection ResultOfMethodCallIgnored + doThrow(IOException.class).when(mockedInputStream).read(); + + // Create broken cache that fails to read anything + DiskBasedCache broken = + spy(new DiskBasedCache(temporaryFolder.getRoot())); + doReturn(mockedInputStream).when(broken).createInputStream(any(File.class)); + + // Attempt to initialize + broken.initialize(); + + // Everything is gone + assertThat(broken.get("kilobyte"), is(nullValue())); + assertThat(broken.get("kilobyte2"), is(nullValue())); + assertThat(broken.get("kilobyte3"), is(nullValue())); + assertThat(listCachedFiles(), is(emptyArray())); + + // Verify that original cache can cope with missing files + assertThat(cache.get("kilobyte"), is(nullValue())); + assertThat(cache.get("kilobyte2"), is(nullValue())); + assertThat(cache.get("kilobyte3"), is(nullValue())); + } + + @Test + public void testManyResponseHeaders() { + Cache.Entry entry = new Cache.Entry(); + entry.data = new byte[0]; + entry.responseHeaders = new HashMap<>(); + for (int i = 0; i < 0xFFFF; i++) { + entry.responseHeaders.put(Integer.toString(i), ""); + } + cache.put("key", entry); + } + + @Test + @SuppressWarnings("TryFinallyCanBeTryWithResources") + public void testCountingInputStreamByteCount() throws IOException { + // Write some bytes + ByteArrayOutputStream out = new ByteArrayOutputStream(); + //noinspection ThrowFromFinallyBlock + try { + DiskBasedCache.writeInt(out, 1); + DiskBasedCache.writeLong(out, -1L); + DiskBasedCache.writeString(out, "hamburger"); + } finally { + //noinspection ThrowFromFinallyBlock + out.close(); + } + long bytesWritten = out.size(); + + // Read the bytes and compare the counts + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(out.toByteArray()), bytesWritten); + try { + assertThat(cis.bytesRemaining(), is(bytesWritten)); + assertThat(cis.bytesRead(), is(0L)); + assertThat(DiskBasedCache.readInt(cis), is(1)); + assertThat(DiskBasedCache.readLong(cis), is(-1L)); + assertThat(DiskBasedCache.readString(cis), is("hamburger")); + assertThat(cis.bytesRead(), is(bytesWritten)); + assertThat(cis.bytesRemaining(), is(0L)); + } finally { + //noinspection ThrowFromFinallyBlock + cis.close(); + } + } + + /* Serialization tests */ + + @Test public void testEmptyReadThrowsEOF() throws IOException { + ByteArrayInputStream empty = new ByteArrayInputStream(new byte[]{}); + exception.expect(EOFException.class); + DiskBasedCache.readInt(empty); + } + + @Test public void serializeInt() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DiskBasedCache.writeInt(baos, 0); DiskBasedCache.writeInt(baos, 19791214); @@ -96,33 +421,35 @@ public class DiskBasedCacheTest { DiskBasedCache.writeString(baos, ""); DiskBasedCache.writeString(baos, "This is a string."); DiskBasedCache.writeString(baos, "ファイカス"); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - assertEquals(DiskBasedCache.readString(bais), ""); - assertEquals(DiskBasedCache.readString(bais), "This is a string."); - assertEquals(DiskBasedCache.readString(bais), "ファイカス"); + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(baos.toByteArray()), baos.size()); + assertEquals(DiskBasedCache.readString(cis), ""); + assertEquals(DiskBasedCache.readString(cis), "This is a string."); + assertEquals(DiskBasedCache.readString(cis), "ファイカス"); } @Test public void serializeMap() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Map<String, String> empty = new HashMap<String, String>(); + Map<String, String> empty = new HashMap<>(); DiskBasedCache.writeStringStringMap(empty, baos); DiskBasedCache.writeStringStringMap(null, baos); - Map<String, String> twoThings = new HashMap<String, String>(); + Map<String, String> twoThings = new HashMap<>(); twoThings.put("first", "thing"); twoThings.put("second", "item"); DiskBasedCache.writeStringStringMap(twoThings, baos); - Map<String, String> emptyKey = new HashMap<String, String>(); + Map<String, String> emptyKey = new HashMap<>(); emptyKey.put("", "value"); DiskBasedCache.writeStringStringMap(emptyKey, baos); - Map<String, String> emptyValue = new HashMap<String, String>(); + Map<String, String> emptyValue = new HashMap<>(); emptyValue.put("key", ""); DiskBasedCache.writeStringStringMap(emptyValue, baos); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - assertEquals(DiskBasedCache.readStringStringMap(bais), empty); - assertEquals(DiskBasedCache.readStringStringMap(bais), empty); // null reads back empty - assertEquals(DiskBasedCache.readStringStringMap(bais), twoThings); - assertEquals(DiskBasedCache.readStringStringMap(bais), emptyKey); - assertEquals(DiskBasedCache.readStringStringMap(bais), emptyValue); + CountingInputStream cis = + new CountingInputStream(new ByteArrayInputStream(baos.toByteArray()), baos.size()); + assertEquals(DiskBasedCache.readStringStringMap(cis), empty); + assertEquals(DiskBasedCache.readStringStringMap(cis), empty); // null reads back empty + assertEquals(DiskBasedCache.readStringStringMap(cis), twoThings); + assertEquals(DiskBasedCache.readStringStringMap(cis), emptyKey); + assertEquals(DiskBasedCache.readStringStringMap(cis), emptyValue); } @Test @@ -133,4 +460,28 @@ public class DiskBasedCacheTest { assertNotNull(DiskBasedCache.class.getMethod("getFileForKey", String.class)); } + + /* Test helpers */ + + private void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) { + assertThat(actual.data, is(equalTo(expected.data))); + assertThat(actual.etag, is(equalTo(expected.etag))); + assertThat(actual.lastModified, is(equalTo(expected.lastModified))); + assertThat(actual.responseHeaders, is(equalTo(expected.responseHeaders))); + assertThat(actual.serverDate, is(equalTo(expected.serverDate))); + assertThat(actual.softTtl, is(equalTo(expected.softTtl))); + assertThat(actual.ttl, is(equalTo(expected.ttl))); + } + + private Cache.Entry randomData(int length) { + Cache.Entry entry = new Cache.Entry(); + byte[] data = new byte[length]; + new Random(42).nextBytes(data); // explicit seed for reproducible results + entry.data = data; + return entry; + } + + private File[] listCachedFiles() { + return temporaryFolder.getRoot().listFiles(); + } } |