@ -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> |
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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') |
||||
} |
||||
} |
@ -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 |
@ -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 "$@" |
@ -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 |
@ -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> |
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 567 B |
After Width: | Height: | Size: 262 B |
After Width: | Height: | Size: 389 B |
After Width: | Height: | Size: 667 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 333 B |
After Width: | Height: | Size: 411 B |
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 329 B |
After Width: | Height: | Size: 484 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 307 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 463 B |
After Width: | Height: | Size: 876 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 422 B |
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 288 B |
After Width: | Height: | Size: 625 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 202 B |
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.<tag>} |
||||
*/ |
||||
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); |
||||
} |
||||
} |