Initial commit

captchafix
Florens Douwes 12 years ago
commit a2983afc1c
  1. 74
      .gitignore
  2. 98
      Chan/AndroidManifest.xml
  3. 1
      Chan/assets/dummy/dummyg0.txt
  4. 63
      Chan/assets/html/licences.html
  5. 42
      Chan/build.gradle
  6. BIN
      Chan/gradle/wrapper/gradle-wrapper.jar
  7. 6
      Chan/gradle/wrapper/gradle-wrapper.properties
  8. 164
      Chan/gradlew
  9. 90
      Chan/gradlew.bat
  10. BIN
      Chan/libs/android-support-v13.jar
  11. BIN
      Chan/libs/httpclientandroidlib-1.1.2.jar
  12. BIN
      Chan/libs/jsoup-1.7.2.jar
  13. BIN
      Chan/libs/sugar-1.2.jar
  14. 6
      Chan/lint.xml
  15. 20
      Chan/proguard-project.txt
  16. 14
      Chan/project.properties
  17. 8
      Chan/res/anim/fade_in.xml
  18. 8
      Chan/res/anim/fade_out.xml
  19. BIN
      Chan/res/drawable-hdpi/drawer_shadow.9.png
  20. BIN
      Chan/res/drawable-hdpi/ic_action_chat.png
  21. BIN
      Chan/res/drawable-hdpi/ic_action_discard.png
  22. BIN
      Chan/res/drawable-hdpi/ic_action_make_available_offline.png
  23. BIN
      Chan/res/drawable-hdpi/ic_action_new.png
  24. BIN
      Chan/res/drawable-hdpi/ic_action_picture.png
  25. BIN
      Chan/res/drawable-hdpi/ic_action_refresh.png
  26. BIN
      Chan/res/drawable-hdpi/ic_drawer.png
  27. BIN
      Chan/res/drawable-hdpi/ic_launcher.png
  28. BIN
      Chan/res/drawable-hdpi/ic_stat_notify.png
  29. BIN
      Chan/res/drawable-mdpi/drawer_shadow.9.png
  30. BIN
      Chan/res/drawable-mdpi/ic_action_chat.png
  31. BIN
      Chan/res/drawable-mdpi/ic_action_discard.png
  32. BIN
      Chan/res/drawable-mdpi/ic_action_make_available_offline.png
  33. BIN
      Chan/res/drawable-mdpi/ic_action_new.png
  34. BIN
      Chan/res/drawable-mdpi/ic_action_picture.png
  35. BIN
      Chan/res/drawable-mdpi/ic_action_refresh.png
  36. BIN
      Chan/res/drawable-mdpi/ic_drawer.png
  37. BIN
      Chan/res/drawable-mdpi/ic_launcher.png
  38. BIN
      Chan/res/drawable-mdpi/ic_stat_notify.png
  39. BIN
      Chan/res/drawable-mdpi/sticky_icon.png
  40. BIN
      Chan/res/drawable-xhdpi/drawer_shadow.9.png
  41. BIN
      Chan/res/drawable-xhdpi/ic_action_chat.png
  42. BIN
      Chan/res/drawable-xhdpi/ic_action_discard.png
  43. BIN
      Chan/res/drawable-xhdpi/ic_action_make_available_offline.png
  44. BIN
      Chan/res/drawable-xhdpi/ic_action_new.png
  45. BIN
      Chan/res/drawable-xhdpi/ic_action_picture.png
  46. BIN
      Chan/res/drawable-xhdpi/ic_action_refresh.png
  47. BIN
      Chan/res/drawable-xhdpi/ic_drawer.png
  48. BIN
      Chan/res/drawable-xhdpi/ic_launcher.png
  49. BIN
      Chan/res/drawable-xhdpi/ic_stat_notify.png
  50. BIN
      Chan/res/drawable-xxhdpi/drawer_shadow.9.png
  51. BIN
      Chan/res/drawable-xxhdpi/ic_action_chat.png
  52. BIN
      Chan/res/drawable-xxhdpi/ic_action_discard.png
  53. BIN
      Chan/res/drawable-xxhdpi/ic_action_make_available_offline.png
  54. BIN
      Chan/res/drawable-xxhdpi/ic_action_new.png
  55. BIN
      Chan/res/drawable-xxhdpi/ic_action_picture.png
  56. BIN
      Chan/res/drawable-xxhdpi/ic_action_refresh.png
  57. BIN
      Chan/res/drawable-xxhdpi/ic_drawer.png
  58. BIN
      Chan/res/drawable-xxhdpi/ic_stat_notify.png
  59. 20
      Chan/res/layout/activity_base.xml
  60. 6
      Chan/res/layout/board_edit.xml
  61. 28
      Chan/res/layout/board_view.xml
  62. 23
      Chan/res/layout/drawer_item.xml
  63. 15
      Chan/res/layout/drawer_item_header.xml
  64. 14
      Chan/res/layout/image_pager.xml
  65. 140
      Chan/res/layout/reply_view.xml
  66. 22
      Chan/res/menu/base.xml
  67. 10
      Chan/res/menu/board_edit.xml
  68. 9
      Chan/res/menu/image_view.xml
  69. 16
      Chan/res/menu/main.xml
  70. 15
      Chan/res/menu/thread.xml
  71. 8
      Chan/res/values-sw600dp/dimens.xml
  72. 8
      Chan/res/values-sw720dp-land/dimens.xml
  73. 4
      Chan/res/values/attrs.xml
  74. 17
      Chan/res/values/dimens.xml
  75. 80
      Chan/res/values/strings.xml
  76. 21
      Chan/res/values/styles.xml
  77. 39
      Chan/res/xml/preference.xml
  78. 58
      Chan/src/com/android/volley/AuthFailureError.java
  79. 97
      Chan/src/com/android/volley/Cache.java
  80. 159
      Chan/src/com/android/volley/CacheDispatcher.java
  81. 98
      Chan/src/com/android/volley/DefaultRetryPolicy.java
  82. 118
      Chan/src/com/android/volley/ExecutorDelivery.java
  83. 30
      Chan/src/com/android/volley/Network.java
  84. 142
      Chan/src/com/android/volley/NetworkDispatcher.java
  85. 36
      Chan/src/com/android/volley/NetworkError.java
  86. 62
      Chan/src/com/android/volley/NetworkResponse.java
  87. 31
      Chan/src/com/android/volley/NoConnectionError.java
  88. 34
      Chan/src/com/android/volley/ParseError.java
  89. 548
      Chan/src/com/android/volley/Request.java
  90. 287
      Chan/src/com/android/volley/RequestQueue.java
  91. 85
      Chan/src/com/android/volley/Response.java
  92. 35
      Chan/src/com/android/volley/ResponseDelivery.java
  93. 41
      Chan/src/com/android/volley/RetryPolicy.java
  94. 32
      Chan/src/com/android/volley/ServerError.java
  95. 23
      Chan/src/com/android/volley/TimeoutError.java
  96. 48
      Chan/src/com/android/volley/VolleyError.java
  97. 176
      Chan/src/com/android/volley/VolleyLog.java
  98. 27
      Chan/src/com/android/volley/extra/BitmapLruImageCache.java
  99. 74
      Chan/src/com/android/volley/extra/JsonReaderRequest.java
  100. 101
      Chan/src/com/android/volley/toolbox/AndroidAuthenticator.java
  101. Some files were not shown because too many files have changed in this diff Show More

74
.gitignore vendored

@ -0,0 +1,74 @@
gps/
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
## Windows detritus
#############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac crap
.DS_Store

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.floens.chan"
android:versionCode="10"
android:versionName="Chan v0.8"
android:installLocation="auto" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" />
<application
android:name="org.floens.chan.ChanApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="org.floens.chan.activity.BoardActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="4chan.org" android:scheme="http" android:pathPrefix="/" />
<data android:host="4chan.org" android:scheme="https" android:pathPrefix="/" />
<data android:host="www.4chan.org" android:scheme="http" android:pathPrefix="/" />
<data android:host="www.4chan.org" android:scheme="https" android:pathPrefix="/" />
<data android:host="boards.4chan.org" android:scheme="http" android:pathPrefix="/" />
<data android:host="boards.4chan.org" android:scheme="https" android:pathPrefix="/" />
</intent-filter>
</activity>
<activity
android:name="org.floens.chan.activity.ThreadActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:parentActivityName="org.floens.chan.activity.BoardActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.floens.chan.activity.BoardActivity" />
</activity>
<activity
android:name="org.floens.chan.activity.CatalogActivity"
android:configChanges="keyboardHidden|orientation|screenSize" >
</activity>
<activity
android:name="org.floens.chan.activity.SettingsActivity"
android:label="@string/action_settings"
android:parentActivityName="org.floens.chan.activity.BoardActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.floens.chan.activity.BoardActivity" />
</activity>
<activity
android:name="org.floens.chan.activity.AboutActivity"
android:label="@string/about"
android:parentActivityName="org.floens.chan.activity.BoardActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.floens.chan.activity.BoardActivity" />
</activity>
<activity
android:name="org.floens.chan.activity.BoardEditor"
android:label="@string/board_edit"
android:parentActivityName="org.floens.chan.activity.BoardActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.floens.chan.activity.BoardActivity" />
</activity>
<activity
android:name="org.floens.chan.imageview.activity.ImageViewActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Theme.ImageList" >
</activity>
<activity
android:name="org.floens.chan.activity.ImagePickActivity" >
</activity>
<service
android:name="org.floens.chan.service.PinnedService"
android:exported="false"
></service>
</application>
</manifest>

File diff suppressed because one or more lines are too long

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>Open Source Licences</title>
<style type="text/css">
body {
margin: 0;
padding: 10px;
font-family: sans-serif;
}
pre {
background-color: #F0F0F0;
padding: 12px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h3>PhotoView</h3>
<a href="https://github.com/chrisbanes/PhotoView">https://github.com/chrisbanes/PhotoView</a>
<pre>
<code>
Copyright 2011, 2012 Chris Banes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</code>
</pre>
<br>
<h3>This software includes several Android classes from the Android Open Source Project.</h3>
<pre>
<code>
Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</code>
</pre>
<br>
</body>
</html>

@ -0,0 +1,42 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.5.+'
}
}
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
}

Binary file not shown.

@ -0,0 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip

164
Chan/gradlew vendored

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
Chan/gradlew.bat vendored

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="Registered">
<ignore path="src/org/floens/chan/activity/BaseActivity.java" />
</issue>
</lint>

@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

@ -0,0 +1,14 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="200" />
</set>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="200" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@ -0,0 +1,20 @@
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
<ListView
android:id="@+id/left_drawer"
android:layout_width="280dp"
android:divider="#333"
android:dividerHeight="1dp"
android:background="#444"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice" />
</android.support.v4.widget.DrawerLayout>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<org.floens.chan.view.DynamicListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/board_edit_list" >
</org.floens.chan.view.DynamicListView>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="@+id/board_view_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:gravity="center_vertical"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:minHeight="60dp"
/>
<Button
android:id="@+id/board_view_delete"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:layout_height="40dp"
android:layout_width="40dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:background="@drawable/ic_action_discard" />
</RelativeLayout>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/drawer_item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="56dp"
android:textSize="19sp"
android:textColor="#fff"
android:gravity="center_vertical"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:orientation="vertical" >
</TextView>
</LinearLayout>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/drawer_item_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="24dp"
android:paddingLeft="16dp"
android:paddingRight="56dp"
android:paddingBottom="8dp"
style="?android:attr/listSeparatorTextViewStyle"
android:orientation="vertical" />
</LinearLayout>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/image_list_background"
android:orientation="vertical" >
<org.floens.chan.view.HackyViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</org.floens.chan.view.HackyViewPager>
</RelativeLayout>

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="500dp"
android:minHeight="400dp"
android:orientation="vertical"
android:divider="?android:attr/dividerHorizontal"
android:showDividers="middle" >
<ViewFlipper
android:id="@+id/reply_flipper"
android:orientation="horizontal"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/reply_data"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
android:id="@+id/reply_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reply_name" />
<EditText
android:id="@+id/reply_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reply_email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/reply_subject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reply_subject" />
<EditText
android:id="@+id/reply_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reply_comment"
android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
android:imeActionLabel="@string/reply_submit"
android:minLines="2" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/reply_file"
android:text="@string/reply_file" />
<Button
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/reply_file_delete"
android:text="@string/reply_file_delete" />
</LinearLayout>
<org.floens.chan.utils.LoadView
android:id="@+id/reply_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:orientation="vertical" >
<org.floens.chan.utils.LoadView
android:id="@+id/reply_captcha_container"
android:layout_width="300dp"
android:layout_height="60dp"
android:layout_gravity="center" />
<EditText
android:id="@+id/reply_captcha"
android:hint="@string/reply_captcha"
android:inputType="textNoSuggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<org.floens.chan.utils.LoadView
android:id="@+id/reply_response"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</org.floens.chan.utils.LoadView>
</ViewFlipper>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/buttonBarStyle" >
<Button
android:id="@+id/reply_cancel"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reply_cancel" />
<Button
android:id="@+id/reply_submit"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reply_submit" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_reload"
android:icon="@drawable/ic_action_refresh"
android:orderInCategory="2"
android:showAsAction="always"
android:title="@string/action_reload" />
<item
android:id="@+id/action_open_browser"
android:orderInCategory="5"
android:showAsAction="never"
android:title="@string/action_open_browser" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings" />
</menu>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_add_board"
android:icon="@drawable/ic_action_new"
android:showAsAction="always"
android:title="@string/board_add" />
</menu>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_image_save"
android:orderInCategory="1"
android:showAsAction="never"
android:title="@string/image_save" />
</menu>

@ -0,0 +1,16 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_catalog"
android:icon="@drawable/ic_action_picture"
android:orderInCategory="3"
android:showAsAction="ifRoom"
android:title="@string/action_catalog" />
<item
android:id="@+id/action_reply"
android:icon="@drawable/ic_action_chat"
android:orderInCategory="4"
android:showAsAction="ifRoom"
android:title="@string/action_reply" />
</menu>

@ -0,0 +1,15 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_pin"
android:icon="@drawable/ic_action_make_available_offline"
android:orderInCategory="1"
android:showAsAction="always"
android:title="@string/action_pin" />
<item
android:id="@+id/action_reply"
android:icon="@drawable/ic_action_chat"
android:orderInCategory="4"
android:showAsAction="ifRoom"
android:title="@string/action_reply" />
</menu>

@ -0,0 +1,8 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw600dp devices (e.g. 7" tablets) here.
-->
</resources>

@ -0,0 +1,8 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
-->
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="image_list_background">#88000000</color>
</resources>

@ -0,0 +1,17 @@
<resources>
<dimen name="general_padding">12dp</dimen>
<dimen name="post_padding">11dp</dimen>
<dimen name="post_text_padding">6dp</dimen>
<dimen name="thumbnail_size">70dp</dimen>
<dimen name="post_icon_width">24dp</dimen>
<dimen name="post_icon_height">14dp</dimen>
<dimen name="post_max_height">200dp</dimen>
<dimen name="post_popup_min_width">200dp</dimen>
<dimen name="post_popup_min_height">100dp</dimen>
<dimen name="about_text_padding">11dp</dimen>
<dimen name="image_popup_padding">8dp</dimen>
</resources>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Chan</string>
<string name="action_settings">Settings</string>
<string name="action_reload">Reload</string>
<string name="action_pin">Pin</string>
<string name="action_reply">Reply</string>
<string name="action_open_browser">Open in browser</string>
<string name="action_catalog">Open catalog</string>
<string name="preference_general">General</string>
<string name="about">About</string>
<string name="about_licenses">Open Source Licenses</string>
<string name="about_licences_summary">Legal information about licenses</string>
<string name="one_reply">reply</string>
<string name="multiple_replies">replies</string>
<string name="image_save">Save image</string>
<string name="image_save_folder_name">Chan</string>
<string name="image_save_succeeded">Saved image to</string>
<string name="image_save_failed">Saving image failed</string>
<string name="image_save_directory_error">Cannot make save directory</string>
<string name="image_save_not_mounted">Cannot write to storage</string>
<string name="image_preview_failed">Failed to show image</string>
<string name="image_open_failed">Failed to open image</string>
<string name="thread_load_failed_network">No network</string>
<string name="thread_load_failed_parsing">Server inaccessible</string>
<string name="thread_load_failed_server">404 not found</string>
<string name="end_of_line">No more posts</string>
<string name="board_edit">Edit my boards</string>
<string name="board_add">Add board</string>
<string name="board_add_confirm">Add</string>
<string name="board_add_cancel">Cancel</string>
<string name="board_add_fail">Unknown board code</string>
<string name="board_add_success">Added</string>
<string name="board_add_duplicate">You already have that board</string>
<string name="drawer_open">Open drawer</string>
<string name="drawer_close">Close drawer</string>
<string name="drawer_pinned">Pinned threads</string>
<string name="reply">Reply</string>
<string name="reply_name">Name</string>
<string name="reply_email">Email</string>
<string name="reply_subject">Subject</string>
<string name="reply_comment">Comment</string>
<string name="reply_file">Pick file</string>
<string name="reply_file_delete">Remove file</string>
<string name="reply_submit">Submit</string>
<string name="reply_back">Back</string>
<string name="reply_cancel">Cancel</string>
<string name="reply_captcha">Enter captcha</string>
<string name="reply_close">Close</string>
<string name="reply_error">Error sending reply</string>
<string name="reply_error_captcha">Wrong captcha</string>
<string name="reply_error_file">No file selected</string>
<string name="reply_success">Post Successful</string>
<string name="reply_captcha_load_error">Failed to load captcha</string>
<string name="open_link_confirmation_preference">Ask before opening links</string>
<string name="open_link_confirmation">Open link?</string>
<string name="open_unknown_title">Unsupported link</string>
<string name="open_unknown">Chan can\'t open this link. Opening it in your browser instead.</string>
<string name="post_info">Info</string>
<string-array name="post_options">
<item>Info</item>
<item>Quote</item>
<item>Copy text</item>
</string-array>
</resources>

@ -0,0 +1,21 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
</style>
<style name="Theme.ImageList" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
</resources>

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory
android:title="@string/preference_general" >
<Preference
android:title="@string/board_edit" >
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="org.floens.chan"
android:targetClass="org.floens.chan.activity.BoardEditor" />
</Preference>
<CheckBoxPreference
android:title="@string/open_link_confirmation_preference"
android:key="preference_open_link_confirmation"
android:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/about" >
<Preference
android:title="@string/about_licenses"
android:summary="@string/about_licences_summary"
android:key="about_licences" />
<Preference
android:title="Chan"
android:key="about_version" />
</PreferenceCategory>
</PreferenceScreen>

@ -0,0 +1,58 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import android.content.Intent;
/**
* Error indicating that there was an authentication failure when performing a Request.
*/
@SuppressWarnings("serial")
public class AuthFailureError extends VolleyError {
/** An intent that can be used to resolve this exception. (Brings up the password dialog.) */
private Intent mResolutionIntent;
public AuthFailureError() { }
public AuthFailureError(Intent intent) {
mResolutionIntent = intent;
}
public AuthFailureError(NetworkResponse response) {
super(response);
}
public AuthFailureError(String message) {
super(message);
}
public AuthFailureError(String message, Exception reason) {
super(message, reason);
}
public Intent getResolutionIntent() {
return mResolutionIntent;
}
@Override
public String getMessage() {
if (mResolutionIntent != null) {
return "User needs to (re)enter credentials.";
}
return super.getMessage();
}
}

@ -0,0 +1,97 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.Collections;
import java.util.Map;
/**
* An interface for a cache keyed by a String with a byte array as data.
*/
public interface Cache {
/**
* Retrieves an entry from the cache.
* @param key Cache key
* @return An {@link Entry} or null in the event of a cache miss
*/
public 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);
/**
* Performs any potentially long-running actions needed to initialize the cache;
* will be called from a worker thread.
*/
public 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);
/**
* Removes an entry from the cache.
* @param key Cache key
*/
public void remove(String key);
/**
* Empties the cache.
*/
public void clear();
/**
* Data and metadata for an entry returned by the cache.
*/
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}

@ -0,0 +1,159 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.concurrent.BlockingQueue;
import android.os.Process;
/**
* Provides a thread for performing cache triage on a queue of requests.
*
* Requests added to the specified cache queue are resolved from cache.
* Any deliverable response is posted back to the caller via a
* {@link ResponseDelivery}. Cache misses and responses that require
* refresh are enqueued on the specified network queue for processing
* by a {@link NetworkDispatcher}.
*/
@SuppressWarnings("rawtypes")
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
/** The queue of requests coming in for triage. */
private final BlockingQueue<Request> mCacheQueue;
/** The queue of requests going out to the network. */
private final BlockingQueue<Request> mNetworkQueue;
/** The cache to read from. */
private final Cache mCache;
/** For posting responses. */
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
/**
* Creates a new cache triage dispatcher thread. You must call {@link #start()}
* in order to begin processing.
*
* @param cacheQueue Queue of incoming requests for triage
* @param networkQueue Queue to post requests that require network to
* @param cache Cache interface to use for resolution
* @param delivery Delivery interface to use for posting responses
*/
public CacheDispatcher(
BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
/**
* Forces this dispatcher to quit immediately. If any requests are still in
* the queue, they are not guaranteed to be processed.
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
}

@ -0,0 +1,98 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Default retry policy for requests.
*/
public class DefaultRetryPolicy implements RetryPolicy {
/** The current timeout in milliseconds. */
private int mCurrentTimeoutMs;
/** The current retry count. */
private int mCurrentRetryCount;
/** The maximum number of attempts. */
private final int mMaxNumRetries;
/** The backoff multiplier for for the policy. */
private final float mBackoffMultiplier;
/** The default socket timeout in milliseconds */
public static final int DEFAULT_TIMEOUT_MS = 2500;
/** The default number of retries */
public static final int DEFAULT_MAX_RETRIES = 1;
/** The default backoff multiplier */
public static final float DEFAULT_BACKOFF_MULT = 1f;
/**
* Constructs a new retry policy using the default timeouts.
*/
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* Constructs a new retry policy.
* @param initialTimeoutMs The initial timeout for the policy.
* @param maxNumRetries The maximum number of retries.
* @param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* Returns the current timeout.
*/
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* Returns the current retry count.
*/
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* Prepares for the next retry by applying a backoff to the timeout.
* @param error The error code of the last attempt.
*/
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
/**
* Returns true if this policy has attempts remaining, false otherwise.
*/
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
}

@ -0,0 +1,118 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.concurrent.Executor;
import android.os.Handler;
/**
* Delivers responses and errors.
*/
public class ExecutorDelivery implements ResponseDelivery {
/** Used for posting responses, typically to the main thread. */
private final Executor mResponsePoster;
/**
* Creates a new response delivery interface.
* @param handler {@link Handler} to post responses on
*/
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
/**
* Creates a new response delivery interface, mockable version
* for testing.
* @param executor For running delivery tasks
*/
public ExecutorDelivery(Executor executor) {
mResponsePoster = executor;
}
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
/**
* A Runnable used for delivering network responses to a listener on the
* main thread.
*/
@SuppressWarnings("rawtypes")
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
}
}

@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* An interface for performing requests.
*/
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

@ -0,0 +1,142 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.concurrent.BlockingQueue;
import android.net.TrafficStats;
import android.os.Build;
import android.os.Process;
/**
* Provides a thread for performing network dispatch from a queue of requests.
*
* Requests added to the specified queue are processed from the network via a
* specified {@link Network} interface. Responses are committed to cache, if
* eligible, using a specified {@link Cache} interface. Valid responses and
* errors are posted back to the caller via a {@link ResponseDelivery}.
*/
@SuppressWarnings("rawtypes")
public class NetworkDispatcher extends Thread {
/** The queue of requests to service. */
private final BlockingQueue<Request> mQueue;
/** The network interface for processing requests. */
private final Network mNetwork;
/** The cache to write to. */
private final Cache mCache;
/** For posting responses and errors. */
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
/**
* Creates a new network dispatcher thread. You must call {@link #start()}
* in order to begin processing.
*
* @param queue Queue of incoming requests for triage
* @param network Network interface to use for performing requests
* @param cache Cache interface to use for writing responses to cache
* @param delivery Delivery interface to use for posting responses
*/
public NetworkDispatcher(BlockingQueue<Request> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
/**
* Forces this dispatcher to quit immediately. If any requests are still in
* the queue, they are not guaranteed to be processed.
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request request;
while (true) {
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
// Tag the request (if API >= 14)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
}
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
error = request.parseNetworkError(error);
mDelivery.postError(request, error);
}
}

@ -0,0 +1,36 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Indicates that there was a network error when performing a Volley request.
*/
@SuppressWarnings("serial")
public class NetworkError extends VolleyError {
public NetworkError() {
super();
}
public NetworkError(Throwable cause) {
super(cause);
}
public NetworkError(NetworkResponse networkResponse) {
super(networkResponse);
}
}

@ -0,0 +1,62 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.Collections;
import java.util.Map;
import org.apache.http.HttpStatus;
/**
* Data and headers returned from {@link Network#performRequest(Request)}.
*/
public class NetworkResponse {
/**
* Creates a new network response.
* @param statusCode the HTTP status code
* @param data Response body
* @param headers Headers returned with this response, or null for none
* @param notModified True if the server returned a 304 and the data was already in cache
*/
public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
boolean notModified) {
this.statusCode = statusCode;
this.data = data;
this.headers = headers;
this.notModified = notModified;
}
public NetworkResponse(byte[] data) {
this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false);
}
public NetworkResponse(byte[] data, Map<String, String> headers) {
this(HttpStatus.SC_OK, data, headers, false);
}
/** The HTTP status code. */
public final int statusCode;
/** Raw data from this response. */
public final byte[] data;
/** Response headers. */
public final Map<String, String> headers;
/** True if the server returned a 304 (Not Modified). */
public final boolean notModified;
}

@ -0,0 +1,31 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Error indicating that no connection could be established when performing a Volley request.
*/
@SuppressWarnings("serial")
public class NoConnectionError extends NetworkError {
public NoConnectionError() {
super();
}
public NoConnectionError(Throwable reason) {
super(reason);
}
}

@ -0,0 +1,34 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Indicates that the server's response could not be parsed.
*/
@SuppressWarnings("serial")
public class ParseError extends VolleyError {
public ParseError() { }
public ParseError(NetworkResponse networkResponse) {
super(networkResponse);
}
public ParseError(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,548 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import com.android.volley.VolleyLog.MarkerLog;
/**
* Base class for all network requests.
*
* @param <T> The type of parsed response this request expects.
*/
public abstract class Request<T> implements Comparable<Request<T>> {
/**
* Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
*/
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
/**
* Supported request methods.
*/
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
}
/** An event log tracing the lifetime of this request; for debugging. */
private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
/** Request method of this request. Currently supports GET, POST, PUT, and DELETE. */
private final int mMethod;
/** URL of this request. */
private final String mUrl;
/** Default tag for {@link TrafficStats}. */
private final int mDefaultTrafficStatsTag;
/** Listener interface for errors. */
private final Response.ErrorListener mErrorListener;
/** Sequence number of this request, used to enforce FIFO ordering. */
private Integer mSequence;
/** The request queue this request is associated with. */
private RequestQueue mRequestQueue;
/** Whether or not responses to this request should be cached. */
private boolean mShouldCache = true;
/** Whether or not this request has been canceled. */
private boolean mCanceled = false;
/** Whether or not a response has been delivered for this request yet. */
private boolean mResponseDelivered = false;
// A cheap variant of request tracing used to dump slow requests.
private long mRequestBirthTime = 0;
/** Threshold at which we should log the request (even when debug logging is not enabled). */
private static final long SLOW_REQUEST_THRESHOLD_MS = 3000;
/** The retry policy for this request. */
private RetryPolicy mRetryPolicy;
/**
* When a request can be retrieved from cache but must be refreshed from
* the network, the cache entry will be stored here so that in the event of
* a "Not Modified" response, we can be sure it hasn't been evicted from cache.
*/
private Cache.Entry mCacheEntry = null;
/** An opaque token tagging this request; used for bulk cancellation. */
private Object mTag;
/**
* Creates a new request with the given URL and error listener. Note that
* the normal response listener is not provided here as delivery of responses
* is provided by subclasses, who have a better idea of how to deliver an
* already-parsed response.
*
* @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
*/
@Deprecated
public Request(String url, Response.ErrorListener listener) {
this(Method.DEPRECATED_GET_OR_POST, url, listener);
}
/**
* Creates a new request with the given method (one of the values from {@link Method}),
* URL, and error listener. Note that the normal response listener is not provided here as
* delivery of responses is provided by subclasses, who have a better idea of how to deliver
* an already-parsed response.
*/
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = TextUtils.isEmpty(url) ? 0: Uri.parse(url).getHost().hashCode();
}
/**
* Return the method for this request. Can be one of the values in {@link Method}.
*/
public int getMethod() {
return mMethod;
}
/**
* Set a tag on this request. Can be used to cancel all requests with this
* tag by {@link RequestQueue#cancelAll(Object)}.
*/
public void setTag(Object tag) {
mTag = tag;
}
/**
* Returns this request's tag.
* @see Request#setTag(Object)
*/
public Object getTag() {
return mTag;
}
/**
* @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)}
*/
public int getTrafficStatsTag() {
return mDefaultTrafficStatsTag;
}
/**
* Sets the retry policy for this request.
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
}
/**
* Adds an event to this request's event log; for debugging.
*/
public void addMarker(String tag) {
if (MarkerLog.ENABLED) {
mEventLog.add(tag, Thread.currentThread().getId());
} else if (mRequestBirthTime == 0) {
mRequestBirthTime = SystemClock.elapsedRealtime();
}
}
/**
* Notifies the request queue that this request has finished (successfully or with error).
*
* <p>Also dumps all events from this request's event log; for debugging.</p>
*/
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
if (MarkerLog.ENABLED) {
final long threadId = Thread.currentThread().getId();
if (Looper.myLooper() != Looper.getMainLooper()) {
// If we finish marking off of the main thread, we need to
// actually do it on the main thread to ensure correct ordering.
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
} else {
long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
VolleyLog.d("%d ms: %s", requestTime, this.toString());
}
}
}
/**
* Associates this request with the given queue. The request queue will be notified when this
* request has finished.
*/
public void setRequestQueue(RequestQueue requestQueue) {
mRequestQueue = requestQueue;
}
/**
* Sets the sequence number of this request. Used by {@link RequestQueue}.
*/
public final void setSequence(int sequence) {
mSequence = sequence;
}
/**
* Returns the sequence number of this request.
*/
public final int getSequence() {
if (mSequence == null) {
throw new IllegalStateException("getSequence called before setSequence");
}
return mSequence;
}
/**
* Returns the URL of this request.
*/
public String getUrl() {
return mUrl;
}
/**
* Returns the cache key for this request. By default, this is the URL.
*/
public String getCacheKey() {
return getUrl();
}
/**
* Annotates this request with an entry retrieved for it from cache.
* Used for cache coherency support.
*/
public void setCacheEntry(Cache.Entry entry) {
mCacheEntry = entry;
}
/**
* Returns the annotated cache entry, or null if there isn't one.
*/
public Cache.Entry getCacheEntry() {
return mCacheEntry;
}
/**
* Mark this request as canceled. No callback will be delivered.
*/
public void cancel() {
mCanceled = true;
}
/**
* Returns true if this request has been canceled.
*/
public boolean isCanceled() {
return mCanceled;
}
/**
* Returns a list of extra HTTP headers to go along with this request. Can
* throw {@link AuthFailureError} as authentication may be required to
* provide these values.
* @throws AuthFailureError In the event of auth failure
*/
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
/**
* Returns a Map of POST parameters to be used for this request, or null if
* a simple GET should be used. Can throw {@link AuthFailureError} as
* authentication may be required to provide these values.
*
* <p>Note that only one of getPostParams() and getPostBody() can return a non-null
* value.</p>
* @throws AuthFailureError In the event of auth failure
*
* @deprecated Use {@link #getParams()} instead.
*/
@Deprecated
protected Map<String, String> getPostParams() throws AuthFailureError {
return getParams();
}
/**
* Returns which encoding should be used when converting POST parameters returned by
* {@link #getPostParams()} into a raw POST body.
*
* <p>This controls both encodings:
* <ol>
* <li>The string encoding used when converting parameter names and values into bytes prior
* to URL encoding them.</li>
* <li>The string encoding used when converting the URL encoded parameters into a raw
* byte array.</li>
* </ol>
*
* @deprecated Use {@link #getParamsEncoding()} instead.
*/
@Deprecated
protected String getPostParamsEncoding() {
return getParamsEncoding();
}
/**
* @deprecated Use {@link #getBodyContentType()} instead.
*/
@Deprecated
public String getPostBodyContentType() {
return getBodyContentType();
}
/**
* Returns the raw POST body to be sent.
*
* @throws AuthFailureError In the event of auth failure
*
* @deprecated Use {@link #getBody()} instead.
*/
@Deprecated
public byte[] getPostBody() throws AuthFailureError {
// Note: For compatibility with legacy clients of volley, this implementation must remain
// here instead of simply calling the getBody() function because this function must
// call getPostParams() and getPostParamsEncoding() since legacy clients would have
// overridden these two member functions for POST requests.
Map<String, String> postParams = getPostParams();
if (postParams != null && postParams.size() > 0) {
return encodeParameters(postParams, getPostParamsEncoding());
}
return null;
}
/**
* Returns a Map of parameters to be used for a POST or PUT request. Can throw
* {@link AuthFailureError} as authentication may be required to provide these values.
*
* <p>Note that you can directly override {@link #getBody()} for custom data.</p>
*
* @throws AuthFailureError in the event of auth failure
*/
protected Map<String, String> getParams() throws AuthFailureError {
return null;
}
/**
* Returns which encoding should be used when converting POST or PUT parameters returned by
* {@link #getParams()} into a raw POST or PUT body.
*
* <p>This controls both encodings:
* <ol>
* <li>The string encoding used when converting parameter names and values into bytes prior
* to URL encoding them.</li>
* <li>The string encoding used when converting the URL encoded parameters into a raw
* byte array.</li>
* </ol>
*/
protected String getParamsEncoding() {
return DEFAULT_PARAMS_ENCODING;
}
public String getBodyContentType() {
return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
}
/**
* Returns the raw POST or PUT body to be sent.
*
* @throws AuthFailureError in the event of auth failure
*/
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
/**
* Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
*/
private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
StringBuilder encodedParams = new StringBuilder();
try {
for (Map.Entry<String, String> entry : params.entrySet()) {
encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
encodedParams.append('=');
encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
encodedParams.append('&');
}
return encodedParams.toString().getBytes(paramsEncoding);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
}
}
/**
* Set whether or not responses to this request should be cached.
*/
public final void setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
}
/**
* Returns true if responses to this request should be cached.
*/
public final boolean shouldCache() {
return mShouldCache;
}
/**
* Priority values. Requests will be processed from higher priorities to
* lower priorities, in FIFO order.
*/
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
/**
* Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
*/
public Priority getPriority() {
return Priority.NORMAL;
}
/**
* Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
* per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
* attempts remaining, this will cause delivery of a {@link TimeoutError} error.
*/
public final int getTimeoutMs() {
return mRetryPolicy.getCurrentTimeout();
}
/**
* Returns the retry policy that should be used for this request.
*/
public RetryPolicy getRetryPolicy() {
return mRetryPolicy;
}
/**
* Mark this request as having a response delivered on it. This can be used
* later in the request's lifetime for suppressing identical responses.
*/
public void markDelivered() {
mResponseDelivered = true;
}
/**
* Returns true if this request has had a response delivered for it.
*/
public boolean hasHadResponseDelivered() {
return mResponseDelivered;
}
/**
* Subclasses must implement this to parse the raw network response
* and return an appropriate response type. This method will be
* called from a worker thread. The response will not be delivered
* if you return null.
* @param response Response from the network
* @return The parsed response, or null in the case of an error
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
/**
* Subclasses can override this method to parse 'networkError' and return a more specific error.
*
* <p>The default implementation just returns the passed 'networkError'.</p>
*
* @param volleyError the error retrieved from the network
* @return an NetworkError augmented with additional information
*/
protected VolleyError parseNetworkError(VolleyError volleyError) {
return volleyError;
}
/**
* Subclasses must implement this to perform delivery of the parsed
* response to their listeners. The given response is guaranteed to
* be non-null; responses that fail to parse are not delivered.
* @param response The parsed response returned by
* {@link #parseNetworkResponse(NetworkResponse)}
*/
abstract protected void deliverResponse(T response);
/**
* Delivers error message to the ErrorListener that the Request was
* initialized with.
*
* @param error Error details
*/
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
/**
* Our comparator sorts from high to low priority, and secondarily by
* sequence number to provide FIFO ordering.
*/
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
@Override
public String toString() {
String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " "
+ getPriority() + " " + mSequence;
}
}

@ -0,0 +1,287 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import android.os.Handler;
import android.os.Looper;
/**
* A request dispatch queue with a thread pool of dispatchers.
*
* Calling {@link #add(Request)} will enqueue the given Request for dispatch,
* resolving from either cache or network on a worker thread, and then delivering
* a parsed response on the main thread.
*/
@SuppressWarnings("rawtypes")
public class RequestQueue {
/** Used for generating monotonically-increasing sequence numbers for requests. */
private AtomicInteger mSequenceGenerator = new AtomicInteger();
/**
* Staging area for requests that already have a duplicate request in flight.
*
* <ul>
* <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
* key.</li>
* <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
* is <em>not</em> contained in that list. Is null if no requests are staged.</li>
* </ul>
*/
private final Map<String, Queue<Request>> mWaitingRequests =
new HashMap<String, Queue<Request>>();
/**
* The set of all requests currently being processed by this RequestQueue. A Request
* will be in this set if it is waiting in any queue or currently being processed by
* any dispatcher.
*/
private final Set<Request> mCurrentRequests = new HashSet<Request>();
/** The cache triage queue. */
private final PriorityBlockingQueue<Request> mCacheQueue =
new PriorityBlockingQueue<Request>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request> mNetworkQueue =
new PriorityBlockingQueue<Request>();
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
/** Cache interface for retrieving and storing respones. */
private final Cache mCache;
/** Network interface for performing requests. */
private final Network mNetwork;
/** Response delivery mechanism. */
private final ResponseDelivery mDelivery;
/** The network dispatchers. */
private NetworkDispatcher[] mDispatchers;
/** The cache dispatcher. */
private CacheDispatcher mCacheDispatcher;
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
* @param delivery A ResponseDelivery interface for posting responses and errors
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
*/
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
/**
* Stops the cache and network dispatchers.
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
/**
* Gets a sequence number.
*/
public int getSequenceNumber() {
return mSequenceGenerator.incrementAndGet();
}
/**
* Gets the {@link Cache} instance being used.
*/
public Cache getCache() {
return mCache;
}
/**
* A simple predicate or filter interface for Requests, for use by
* {@link RequestQueue#cancelAll(RequestFilter)}.
*/
public interface RequestFilter {
public boolean apply(Request<?> request);
}
/**
* Cancels all requests in this queue for which the given filter applies.
* @param filter The filtering function to use
*/
public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}
/**
* Cancels all requests in this queue with the given tag. Tag must be non-null
* and equality is by identity.
*/
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
/**
* Called from {@link Request#finish(String)}, indicating that processing of the given request
* has finished.
*
* <p>Releases waiting requests for <code>request.getCacheKey()</code> if
* <code>request.shouldCache()</code>.</p>
*/
void finish(Request request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
}

@ -0,0 +1,85 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Encapsulates a parsed response for delivery.
*
* @param <T> Parsed type of this response
*/
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);
}
/** Callback interface for delivering error responses. */
public interface ErrorListener {
/**
* Callback method that an error has been occurred with the
* provided error code and optional user-readable message.
*/
public void onErrorResponse(VolleyError error);
}
/** Returns a successful response containing the parsed result. */
public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
return new Response<T>(result, cacheEntry);
}
/**
* Returns a failed response containing the given error code and an optional
* localized message displayed to the user.
*/
public static <T> Response<T> error(VolleyError error) {
return new Response<T>(error);
}
/** Parsed response, or null in the case of error. */
public final T result;
/** Cache metadata for this response, or null in the case of error. */
public final Cache.Entry cacheEntry;
/** Detailed error information if <code>errorCode != OK</code>. */
public final VolleyError error;
/** True if this response was a soft-expired one and a second one MAY be coming. */
public boolean intermediate = false;
/**
* Returns whether this response is considered successful.
*/
public boolean isSuccess() {
return error == null;
}
private Response(T result, Cache.Entry cacheEntry) {
this.result = result;
this.cacheEntry = cacheEntry;
this.error = null;
}
private Response(VolleyError error) {
this.result = null;
this.cacheEntry = null;
this.error = error;
}
}

@ -0,0 +1,35 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
public interface ResponseDelivery {
/**
* Parses a response from the network or cache and delivers it.
*/
public 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);
/**
* Posts an error for the given request.
*/
public void postError(Request<?> request, VolleyError error);
}

@ -0,0 +1,41 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Retry policy for a request.
*/
public interface RetryPolicy {
/**
* Returns the current timeout (used for logging).
*/
public int getCurrentTimeout();
/**
* Returns the current retry count (used for logging).
*/
public int getCurrentRetryCount();
/**
* Prepares for the next retry by applying a backoff to the timeout.
* @param error The error code of the last attempt.
* @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;
}

@ -0,0 +1,32 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Indicates that the error responded with an error response.
*/
@SuppressWarnings("serial")
public class ServerError extends VolleyError {
public ServerError(NetworkResponse networkResponse) {
super(networkResponse);
}
public ServerError() {
super();
}
}

@ -0,0 +1,23 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Indicates that the connection or the socket timed out.
*/
@SuppressWarnings("serial")
public class TimeoutError extends VolleyError { }

@ -0,0 +1,48 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
/**
* Exception style class encapsulating Volley errors
*/
@SuppressWarnings("serial")
public class VolleyError extends Exception {
public final NetworkResponse networkResponse;
public VolleyError() {
networkResponse = null;
}
public VolleyError(NetworkResponse response) {
networkResponse = response;
}
public VolleyError(String exceptionMessage) {
super(exceptionMessage);
networkResponse = null;
}
public VolleyError(String exceptionMessage, Throwable reason) {
super(exceptionMessage, reason);
networkResponse = null;
}
public VolleyError(Throwable cause) {
super(cause);
networkResponse = null;
}
}

@ -0,0 +1,176 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import android.os.SystemClock;
import android.util.Log;
/** Logging helper class. */
public class VolleyLog {
public static String TAG = "Volley";
public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
/**
* Customize the log tag for your application, so that other apps
* using Volley don't mix their logs with yours.
* <br />
* Enable the log property for your tag before starting your app:
* <br />
* {@code adb shell setprop log.tag.&lt;tag&gt;}
*/
public static void setTag(String tag) {
d("Changing log tag to %s", tag);
TAG = tag;
// Reinitialize the DEBUG "constant"
DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
}
public static void v(String format, Object... args) {
if (DEBUG) {
Log.v(TAG, buildMessage(format, args));
}
}
public static void d(String format, Object... args) {
Log.d(TAG, buildMessage(format, args));
}
public static void e(String format, Object... args) {
Log.e(TAG, buildMessage(format, args));
}
public static void e(Throwable tr, String format, Object... args) {
Log.e(TAG, buildMessage(format, args), tr);
}
public static void wtf(String format, Object... args) {
Log.wtf(TAG, buildMessage(format, args));
}
public static void wtf(Throwable tr, String format, Object... args) {
Log.wtf(TAG, buildMessage(format, args), tr);
}
/**
* Formats the caller's provided message and prepends useful info like
* calling thread ID and method name.
*/
private static String buildMessage(String format, Object... args) {
String msg = (args == null) ? format : String.format(Locale.US, format, args);
StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
String caller = "<unknown>";
// Walk up the stack looking for the first caller outside of VolleyLog.
// It will be at least two frames up, so start there.
for (int i = 2; i < trace.length; i++) {
Class<?> clazz = trace[i].getClass();
if (!clazz.equals(VolleyLog.class)) {
String callingClass = trace[i].getClassName();
callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
caller = callingClass + "." + trace[i].getMethodName();
break;
}
}
return String.format(Locale.US, "[%d] %s: %s",
Thread.currentThread().getId(), caller, msg);
}
/**
* A simple event log with records containing a name, thread ID, and timestamp.
*/
static class MarkerLog {
public static final boolean ENABLED = VolleyLog.DEBUG;
/** Minimum duration from first marker to last in an marker log to warrant logging. */
private static final long MIN_DURATION_FOR_LOGGING_MS = 0;
private static class Marker {
public final String name;
public final long thread;
public final long time;
public Marker(String name, long thread, long time) {
this.name = name;
this.thread = thread;
this.time = time;
}
}
private final List<Marker> mMarkers = new ArrayList<Marker>();
private boolean mFinished = false;
/** Adds a marker to this log with the specified name. */
public synchronized void add(String name, long threadId) {
if (mFinished) {
throw new IllegalStateException("Marker added to finished log");
}
mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime()));
}
/**
* Closes the log, dumping it to logcat if the time difference between
* the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}.
* @param header Header string to print above the marker log.
*/
public synchronized void finish(String header) {
mFinished = true;
long duration = getTotalDuration();
if (duration <= MIN_DURATION_FOR_LOGGING_MS) {
return;
}
long prevTime = mMarkers.get(0).time;
d("(%-4d ms) %s", duration, header);
for (Marker marker : mMarkers) {
long thisTime = marker.time;
d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name);
prevTime = thisTime;
}
}
@Override
protected void finalize() throws Throwable {
// Catch requests that have been collected (and hence end-of-lifed)
// but had no debugging output printed for them.
if (!mFinished) {
finish("Request on the loose");
e("Marker log finalized without finish() - uncaught exit point for request");
}
}
/** Returns the time difference between the first and last events in this log. */
private long getTotalDuration() {
if (mMarkers.size() == 0) {
return 0;
}
long first = mMarkers.get(0).time;
long last = mMarkers.get(mMarkers.size() - 1).time;
return last - first;
}
}
}

@ -0,0 +1,27 @@
package com.android.volley.extra;
import android.graphics.Bitmap;
import android.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;
public class BitmapLruImageCache extends LruCache<String, Bitmap> implements ImageCache {
public BitmapLruImageCache(int maxSize) {
super(maxSize);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}

@ -0,0 +1,74 @@
package com.android.volley.extra;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import android.util.JsonReader;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
public abstract class JsonReaderRequest<T> extends Request<T> {
protected final Listener<T> listener;
private VolleyError error;
public JsonReaderRequest(String url, Listener<T> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.listener = listener;
}
@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
public void setError(VolleyError error) {
this.error = error;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
ByteArrayInputStream baos = new ByteArrayInputStream(response.data);
JsonReader reader = new JsonReader(new InputStreamReader(baos, "UTF-8"));
// long start = System.currentTimeMillis();
T read = readJson(reader);
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
// Log.e("Chan", "Total time: " + (System.currentTimeMillis() - start));
if (read == null) {
return Response.error(new VolleyError());
} else if (error != null) {
return Response.error(error);
} else {
return Response.success(read, HttpHeaderParser.parseCacheHeaders(response));
}
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
public abstract T readJson(JsonReader reader);
}

@ -0,0 +1,101 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley.toolbox;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.android.volley.AuthFailureError;
/**
* An Authenticator that uses {@link AccountManager} to get auth
* tokens of a specified type for a specified account.
*/
public class AndroidAuthenticator implements Authenticator {
private final Context mContext;
private final Account mAccount;
private final String mAuthTokenType;
private final boolean mNotifyAuthFailure;
/**
* Creates a new authenticator.
* @param context Context for accessing AccountManager
* @param account Account to authenticate as
* @param authTokenType Auth token type passed to AccountManager
*/
public AndroidAuthenticator(Context context, Account account, String authTokenType) {
this(context, account, authTokenType, false);
}
/**
* Creates a new authenticator.
* @param context Context for accessing AccountManager
* @param account Account to authenticate as
* @param authTokenType Auth token type passed to AccountManager
* @param notifyAuthFailure Whether to raise a notification upon auth failure
*/
public AndroidAuthenticator(Context context, Account account, String authTokenType,
boolean notifyAuthFailure) {
mContext = context;
mAccount = account;
mAuthTokenType = authTokenType;
mNotifyAuthFailure = notifyAuthFailure;
}
/**
* Returns the Account being used by this authenticator.
*/
public Account getAccount() {
return mAccount;
}
@Override
public String getAuthToken() throws AuthFailureError {
final AccountManager accountManager = AccountManager.get(mContext);
@SuppressWarnings("deprecation")
AccountManagerFuture<Bundle> future = accountManager.getAuthToken(mAccount,
mAuthTokenType, mNotifyAuthFailure, null, null);
Bundle result;
try {
result = future.getResult();
} catch (Exception e) {
throw new AuthFailureError("Error while retrieving auth token", e);
}
String authToken = null;
if (future.isDone() && !future.isCancelled()) {
if (result.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
throw new AuthFailureError(intent);
}
authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
}
if (authToken == null) {
throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType);
}
return authToken;
}
@Override
public void invalidateAuthToken(String authToken) {
AccountManager.get(mContext).invalidateAuthToken(mAccount.type, authToken);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save