Commit 91657defbce09e1d929f26939f3097e5c61b4393
1 parent
724a0c23
init other subproject impl lock control
Showing
91 changed files
with
8513 additions
and
0 deletions
.gitignore
0 → 100644
README.md
0 → 100644
build.gradle
0 → 100644
| 1 | +// Top-level build file where you can add configuration options common to all sub-projects/modules. | ||
| 2 | +buildscript { | ||
| 3 | + repositories { | ||
| 4 | + mavenCentral() | ||
| 5 | + jcenter() | ||
| 6 | + } | ||
| 7 | + | ||
| 8 | + dependencies { | ||
| 9 | + classpath 'com.android.tools.build:gradle:2.1.3' | ||
| 10 | +// classpath 'me.tatarka:gradle-retrolambda:3.2.3' | ||
| 11 | +// classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' | ||
| 12 | + classpath 'com.antfortune.freeline:gradle:0.8.3' | ||
| 13 | + } | ||
| 14 | +// configurations.classpath.exclude group: 'com.android.tools.external.lombok' | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +//repositories { | ||
| 18 | +// mavenCentral() | ||
| 19 | +//} | ||
| 20 | +//buildscript { | ||
| 21 | +// repositories { | ||
| 22 | +// jcenter() | ||
| 23 | +// } | ||
| 24 | +// dependencies { | ||
| 25 | +// classpath 'com.android.tools.build:gradle:2.1.3' | ||
| 26 | +// | ||
| 27 | +// // NOTE: Do not place your application dependencies here; they belong | ||
| 28 | +// // in the individual module build.gradle files | ||
| 29 | +// } | ||
| 30 | +//} | ||
| 31 | +// | ||
| 32 | +allprojects { | ||
| 33 | + repositories { | ||
| 34 | + jcenter() | ||
| 35 | + mavenCentral() | ||
| 36 | + } | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +// Define versions in a single place | ||
| 40 | +ext { | ||
| 41 | + // Sdk and tools | ||
| 42 | + minSdkVersion = 10 | ||
| 43 | + targetSdkVersion = 24 | ||
| 44 | + compileSdkVersion = 24 | ||
| 45 | + buildToolsVersion = '24.0.2' | ||
| 46 | + | ||
| 47 | + // App dependencies | ||
| 48 | + supportLibraryVersion = '24.2.0' | ||
| 49 | + guavaVersion = '18.0' | ||
| 50 | + junitVersion = '4.12' | ||
| 51 | + mockitoVersion = '1.10.19' | ||
| 52 | + powerMockito = '1.6.2' | ||
| 53 | + hamcrestVersion = '1.3' | ||
| 54 | + runnerVersion = '0.5' | ||
| 55 | + rulesVersion = '0.5' | ||
| 56 | + espressoVersion = '2.2.2' | ||
| 57 | +} | ||
| 58 | + |
debug-db/.gitignore
0 → 100644
debug-db/build.gradle
0 → 100644
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +apply plugin: 'com.android.library' | ||
| 21 | + | ||
| 22 | +android { | ||
| 23 | + compileSdkVersion 23 | ||
| 24 | + buildToolsVersion "23.0.2" | ||
| 25 | + | ||
| 26 | + lintOptions { | ||
| 27 | + abortOnError false | ||
| 28 | + checkReleaseBuilds false | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + defaultConfig { | ||
| 32 | + minSdkVersion 11 | ||
| 33 | + targetSdkVersion 23 | ||
| 34 | + versionCode 1 | ||
| 35 | + versionName "1.0" | ||
| 36 | + | ||
| 37 | + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||
| 38 | + | ||
| 39 | + resValue("string", "PORT_NUMBER", "8098") | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + buildTypes { | ||
| 43 | + release { | ||
| 44 | + minifyEnabled false | ||
| 45 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +dependencies { | ||
| 51 | +// compile fileTree(include: ['*.jar'], dir: 'libs') | ||
| 52 | + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { | ||
| 53 | + exclude group: 'com.android.support', module: 'support-annotations' | ||
| 54 | + }) | ||
| 55 | + testCompile 'junit:junit:4.12' | ||
| 56 | + compile 'com.google.code.gson:gson:2.4' | ||
| 57 | + provided files('libs/cinemaLib-1117.jar') | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +//apply from: 'debug-db-upload.gradle' |
debug-db/debug-db-upload.gradle
0 → 100644
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +apply plugin: 'com.github.dcendents.android-maven' | ||
| 21 | +apply plugin: "com.jfrog.bintray" | ||
| 22 | + | ||
| 23 | +def siteUrl = 'https://github.com/amitshekhariitbhu/Android-Debug-Database' | ||
| 24 | +def gitUrl = 'https://github.com/amitshekhariitbhu/Android-Debug-Database.git' | ||
| 25 | + | ||
| 26 | +group = "com.amitshekhar.android" | ||
| 27 | +version = '0.3.0' | ||
| 28 | + | ||
| 29 | +install { | ||
| 30 | + repositories.mavenInstaller { | ||
| 31 | + pom.project { | ||
| 32 | + packaging 'aar' | ||
| 33 | + | ||
| 34 | + name 'Android Debug Database' | ||
| 35 | + description 'Android Debug Database is a powerful library for debugging databases in Android applications' | ||
| 36 | + | ||
| 37 | + url siteUrl | ||
| 38 | + | ||
| 39 | + licenses { | ||
| 40 | + license { | ||
| 41 | + name 'The Apache Software License, Version 2.0' | ||
| 42 | + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + developers { | ||
| 47 | + developer { | ||
| 48 | + id 'amitshekhariitbhu' | ||
| 49 | + name 'Amit Shekhar' | ||
| 50 | + email 'amit.shekhar.iitbhu@gmail.com' | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + scm { | ||
| 55 | + connection gitUrl | ||
| 56 | + developerConnection gitUrl | ||
| 57 | + url siteUrl | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +task sourcesJar(type: Jar) { | ||
| 64 | + from android.sourceSets.main.java.srcDirs | ||
| 65 | + classifier = 'sources' | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +task javadoc(type: Javadoc) { | ||
| 69 | + source = android.sourceSets.main.java.srcDirs | ||
| 70 | + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) | ||
| 71 | + classpath += configurations.compile | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +task javadocJar(type: Jar, dependsOn: javadoc) { | ||
| 75 | + classifier = 'javadoc' | ||
| 76 | + from javadoc.destinationDir | ||
| 77 | +} | ||
| 78 | +artifacts { | ||
| 79 | + archives javadocJar | ||
| 80 | + archives sourcesJar | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +if (project.rootProject.file("local.properties").exists()) { | ||
| 84 | + Properties properties = new Properties() | ||
| 85 | + properties.load(project.rootProject.file('local.properties').newDataInputStream()) | ||
| 86 | + | ||
| 87 | + bintray { | ||
| 88 | + user = properties.getProperty("bintray.user") | ||
| 89 | + key = properties.getProperty("bintray.apikey") | ||
| 90 | + | ||
| 91 | + configurations = ['archives'] | ||
| 92 | + dryRun = false | ||
| 93 | + | ||
| 94 | + pkg { | ||
| 95 | + repo = "maven" | ||
| 96 | + name = "debug-db" | ||
| 97 | + websiteUrl = siteUrl | ||
| 98 | + vcsUrl = gitUrl | ||
| 99 | + licenses = ["Apache-2.0"] | ||
| 100 | + publish = true | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | +} |
debug-db/libs/cinemaLib-1117.jar
0 → 100644
No preview for this file type
debug-db/proguard-rules.pro
0 → 100644
| 1 | +# Add project specific ProGuard rules here. | ||
| 2 | +# By default, the flags in this file are appended to flags specified | ||
| 3 | +# in /Users/amitshekhar/Library/Android/sdk/tools/proguard/proguard-android.txt | ||
| 4 | +# You can edit the include path and order by changing the proguardFiles | ||
| 5 | +# directive in build.gradle. | ||
| 6 | +# | ||
| 7 | +# For more details, see | ||
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | ||
| 9 | + | ||
| 10 | +# Add any project specific keep options here: | ||
| 11 | + | ||
| 12 | +# If your project uses WebView with JS, uncomment the following | ||
| 13 | +# and specify the fully qualified class name to the JavaScript interface | ||
| 14 | +# class: | ||
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| 16 | +# public *; | ||
| 17 | +#} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar; | ||
| 21 | + | ||
| 22 | +import android.content.Context; | ||
| 23 | +import android.support.test.InstrumentationRegistry; | ||
| 24 | +import android.support.test.runner.AndroidJUnit4; | ||
| 25 | + | ||
| 26 | +import org.junit.Test; | ||
| 27 | +import org.junit.runner.RunWith; | ||
| 28 | + | ||
| 29 | +import static org.junit.Assert.*; | ||
| 30 | + | ||
| 31 | +/** | ||
| 32 | + * Instrumentation test, which will execute on an Android device. | ||
| 33 | + * | ||
| 34 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
| 35 | + */ | ||
| 36 | +@RunWith(AndroidJUnit4.class) | ||
| 37 | +public class ExampleInstrumentedTest { | ||
| 38 | + @Test | ||
| 39 | + public void useAppContext() throws Exception { | ||
| 40 | + // Context of the app under test. | ||
| 41 | + Context appContext = InstrumentationRegistry.getTargetContext(); | ||
| 42 | + | ||
| 43 | + assertEquals("com.amitshekhar.test", appContext.getPackageName()); | ||
| 44 | + } | ||
| 45 | +} |
debug-db/src/main/AndroidManifest.xml
0 → 100644
| 1 | +<!-- | ||
| 2 | + ~ /* | ||
| 3 | + ~ * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + ~ * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + ~ * | ||
| 6 | + ~ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + ~ * you may not use this file except in compliance with the License. | ||
| 8 | + ~ * You may obtain a copy of the License at | ||
| 9 | + ~ * | ||
| 10 | + ~ * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + ~ * | ||
| 12 | + ~ * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + ~ * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + ~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + ~ * See the License for the specific language governing permissions and | ||
| 16 | + ~ * limitations under the License. | ||
| 17 | + ~ */ | ||
| 18 | + --> | ||
| 19 | + | ||
| 20 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 21 | + package="com.amitshekhar"> | ||
| 22 | + | ||
| 23 | + <uses-permission android:name="android.permission.INTERNET" /> | ||
| 24 | + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | ||
| 25 | + | ||
| 26 | + <application> | ||
| 27 | + <provider | ||
| 28 | + android:authorities="${applicationId}.DebugDBInitProvider" | ||
| 29 | + android:exported="false" | ||
| 30 | + android:enabled="true" | ||
| 31 | + android:name=".DebugDBInitProvider" /> | ||
| 32 | + </application> | ||
| 33 | + | ||
| 34 | +</manifest> |
debug-db/src/main/assets/app.js
0 → 100644
| 1 | +$( document ).ready(function() { | ||
| 2 | + getDBList(); | ||
| 3 | + $("#query").keypress(function(e){ | ||
| 4 | + if(e.which == 13) { | ||
| 5 | + queryFunction(); | ||
| 6 | + } | ||
| 7 | + }); | ||
| 8 | +}); | ||
| 9 | + | ||
| 10 | +function getData(tableName) { | ||
| 11 | + | ||
| 12 | + $.ajax({url: "getAllDataFromTheTable?tableName="+tableName, success: function(result){ | ||
| 13 | + | ||
| 14 | + result = JSON.parse(result); | ||
| 15 | + inflateData(result); | ||
| 16 | + | ||
| 17 | + }}); | ||
| 18 | + | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +function queryFunction() { | ||
| 22 | + | ||
| 23 | + var query = $('#query').val(); | ||
| 24 | + | ||
| 25 | + $.ajax({url: "query?query="+escape(query), success: function(result){ | ||
| 26 | + | ||
| 27 | + result = JSON.parse(result); | ||
| 28 | + inflateData(result); | ||
| 29 | + | ||
| 30 | + }}); | ||
| 31 | + | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +function resetLedStatus() { | ||
| 36 | + | ||
| 37 | + var cmd = $('#cmd').val(); | ||
| 38 | + | ||
| 39 | + $.ajax({url: "cmd?cmd="+escape(cmd), success: function(result){ | ||
| 40 | + result = JSON.parse(result); | ||
| 41 | + inflateData(result); | ||
| 42 | + | ||
| 43 | + }}); | ||
| 44 | + | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +function getDBList() { | ||
| 49 | + | ||
| 50 | + $.ajax({url: "getDbList", success: function(result){ | ||
| 51 | + | ||
| 52 | + result = JSON.parse(result); | ||
| 53 | + var dbList = result.rows; | ||
| 54 | + $('#db-list').empty(); | ||
| 55 | + var isSelectionDone = false; | ||
| 56 | + for(var count = 0; count < dbList.length; count++){ | ||
| 57 | + if(dbList[count].indexOf("journal") == -1){ | ||
| 58 | + $("#db-list").append("<a href='#' id=" +dbList[count] + " class='list-group-item' onClick='openDatabaseAndGetTableList(\""+ dbList[count] + "\");'>" +dbList[count] + "</a>"); | ||
| 59 | + if(!isSelectionDone){ | ||
| 60 | + isSelectionDone = true; | ||
| 61 | + $('#db-list').find('a').trigger('click'); | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + }}); | ||
| 67 | + | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +function openDatabaseAndGetTableList(db) { | ||
| 71 | + | ||
| 72 | + if("APP_SHARED_PREFERENCES" == db) { | ||
| 73 | + $('#run-query').removeClass('active'); | ||
| 74 | + $('#run-query').addClass('disabled'); | ||
| 75 | + } else { | ||
| 76 | + $('#run-query').removeClass('disabled'); | ||
| 77 | + $('#run-query').addClass('active'); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + $("#selected-db-info").text("Selected Database : "+db); | ||
| 81 | + | ||
| 82 | + $.ajax({url: "getTableList?database="+db, success: function(result){ | ||
| 83 | + | ||
| 84 | + result = JSON.parse(result); | ||
| 85 | + var tableList = result.rows; | ||
| 86 | + $('#table-list').empty() | ||
| 87 | + for(var count = 0; count < tableList.length; count++){ | ||
| 88 | + $("#table-list").append("<a href='#' class='list-group-item' onClick='getData(\""+ tableList[count] + "\");'>" +tableList[count] + "</a>"); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + }}); | ||
| 92 | + | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +function inflateData(result){ | ||
| 96 | + | ||
| 97 | + if(result.isSuccessful){ | ||
| 98 | + showSuccessInfo(); | ||
| 99 | + var columnHeader = result.columns.map(function(columnName) { | ||
| 100 | + return {"title": columnName}; | ||
| 101 | + }); | ||
| 102 | + var columnData = result.rows; | ||
| 103 | + var tableId = "#db-data"; | ||
| 104 | + if ($.fn.DataTable.isDataTable(tableId) ) { | ||
| 105 | + $(tableId).DataTable().destroy(); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + $("#db-data-div").remove(); | ||
| 109 | + $("#parent-data-div").append('<div id="db-data-div"><table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered display" id="db-data"></table></div>'); | ||
| 110 | + | ||
| 111 | + $(tableId).dataTable({ | ||
| 112 | + "data": columnData, | ||
| 113 | + "columns": columnHeader, | ||
| 114 | + 'bPaginate': true, | ||
| 115 | + 'searching': true, | ||
| 116 | + 'bFilter': true, | ||
| 117 | + 'bInfo': true, | ||
| 118 | + "bSort" : true, | ||
| 119 | + "scrollX": true, | ||
| 120 | + "iDisplayLength": 100 | ||
| 121 | + }); | ||
| 122 | + // hack to fix alignment issue when scrollX is enabled | ||
| 123 | + $(".dataTables_scrollHeadInner").css({"width":"100%"}); | ||
| 124 | + $(".table ").css({"width":"100%"}); | ||
| 125 | + }else{ | ||
| 126 | + showErrorInfo(); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | +function showSuccessInfo(){ | ||
| 132 | + $("#success-info").show(); | ||
| 133 | + $("#error-info").hide(); | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +function showErrorInfo(){ | ||
| 137 | + $("#success-info").hide(); | ||
| 138 | + $("#error-info").show(); | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +function hideBothInfo(){ | ||
| 142 | + $("#success-info").hide(); | ||
| 143 | + $("#error-info").hide(); | ||
| 144 | +} |
debug-db/src/main/assets/custom.css
0 → 100644
debug-db/src/main/assets/favicon.ico
0 → 100644
No preview for this file type
debug-db/src/main/assets/index.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<!-- | ||
| 3 | + ~ /* | ||
| 4 | + ~ * Copyright (C) 2016 Amit Shekhar | ||
| 5 | + ~ * Copyright (C) 2011 Android Open Source Project | ||
| 6 | + ~ * | ||
| 7 | + ~ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 8 | + ~ * you may not use this file except in compliance with the License. | ||
| 9 | + ~ * You may obtain a copy of the License at | ||
| 10 | + ~ * | ||
| 11 | + ~ * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 12 | + ~ * | ||
| 13 | + ~ * Unless required by applicable law or agreed to in writing, software | ||
| 14 | + ~ * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 15 | + ~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 16 | + ~ * See the License for the specific language governing permissions and | ||
| 17 | + ~ * limitations under the License. | ||
| 18 | + ~ */ | ||
| 19 | + --> | ||
| 20 | + | ||
| 21 | +<html lang="en"> | ||
| 22 | +<head> | ||
| 23 | + <title>Android Debug Database</title> | ||
| 24 | + <meta charset="utf-8"> | ||
| 25 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 26 | + <link rel='shortcut icon' href='favicon.ico' type='image/x-icon'> | ||
| 27 | + <link rel="stylesheet" | ||
| 28 | + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> | ||
| 29 | + <link rel="stylesheet" href="https://cdn.datatables.net/1.10.12/css/jquery.dataTables.min.css"> | ||
| 30 | + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> | ||
| 31 | + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> | ||
| 32 | + <script src="https://cdn.datatables.net/1.10.12/js/jquery.dataTables.min.js"></script> | ||
| 33 | + | ||
| 34 | + <link href="custom.css" rel="stylesheet"> | ||
| 35 | + | ||
| 36 | +</head> | ||
| 37 | +<body> | ||
| 38 | + | ||
| 39 | +<!-- Navigation --> | ||
| 40 | +<nav class="navbar navbar-default navbar-fixed-top" role="navigation"> | ||
| 41 | + <div class="container"> | ||
| 42 | + <!-- Brand and toggle get grouped for better mobile display --> | ||
| 43 | + <div class="navbar-header"> | ||
| 44 | + <button type="button" class="navbar-toggle" data-toggle="collapse" | ||
| 45 | + data-target="#bs-example-navbar-collapse-1"> | ||
| 46 | + <span class="sr-only">Toggle navigation</span> | ||
| 47 | + <span class="icon-bar"></span> | ||
| 48 | + <span class="icon-bar"></span> | ||
| 49 | + <span class="icon-bar"></span> | ||
| 50 | + </button> | ||
| 51 | + <a class="navbar-brand" href="index.html">DebugDB</a> | ||
| 52 | + </div> | ||
| 53 | + <!-- Collect the nav links, forms, and other content for toggling | ||
| 54 | + <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> | ||
| 55 | + <ul class="nav navbar-nav navbar-right"> | ||
| 56 | + <li> | ||
| 57 | + <a href="https://github.com/amitshekhariitbhu/Android-Debug-Database" | ||
| 58 | + target="_blank">Github</a> | ||
| 59 | + </li> | ||
| 60 | + <li> | ||
| 61 | + <a href="https://twitter.com/amitiitbhu" | ||
| 62 | + target="_blank">Twitter</a> | ||
| 63 | + </li> | ||
| 64 | + <li class="dropdown"> | ||
| 65 | + <a href="#" class="dropdown-toggle" data-toggle="dropdown">Other Projects<b | ||
| 66 | + class="caret"></b></a> | ||
| 67 | + <ul class="dropdown-menu"> | ||
| 68 | + <li> | ||
| 69 | + <a href="https://github.com/amitshekhariitbhu/Fast-Android-Networking" | ||
| 70 | + target="_blank">Fast Android Networking</a> | ||
| 71 | + </li> | ||
| 72 | + <li> | ||
| 73 | + <a href="https://github.com/amitshekhariitbhu/GlideBitmapPool" | ||
| 74 | + target="_blank">Glide Bitmap Pool</a> | ||
| 75 | + </li> | ||
| 76 | + <li> | ||
| 77 | + <a href="https://github.com/amitshekhariitbhu/RxJava2-Android-Samples" | ||
| 78 | + target="_blank">RxJava2 Android Samples</a> | ||
| 79 | + </li> | ||
| 80 | + <li> | ||
| 81 | + <a href="https://github.com/amitshekhariitbhu/awesome-android-complete-reference" | ||
| 82 | + target="_blank">Awesome Android Complete Reference</a> | ||
| 83 | + </li> | ||
| 84 | + <li> | ||
| 85 | + <a href="https://github.com/amitshekhariitbhu/FlatBuffer" | ||
| 86 | + target="_blank">FlatBuffers vs JSON</a> | ||
| 87 | + </li> | ||
| 88 | + </ul> | ||
| 89 | + </li> | ||
| 90 | + </ul> | ||
| 91 | + </div> | ||
| 92 | + /.navbar-collapse --> | ||
| 93 | + </div> | ||
| 94 | + <!-- /.container --> | ||
| 95 | +</nav> | ||
| 96 | + | ||
| 97 | + | ||
| 98 | +<div class="container padding-fifty"> | ||
| 99 | + | ||
| 100 | + <div class="row padding-twenty"> | ||
| 101 | + <div class="col-sm-12"> | ||
| 102 | + <div class="form-group"> | ||
| 103 | + <label for="query">Query</label> | ||
| 104 | + <input class="form-control" id="query"> | ||
| 105 | + </div> | ||
| 106 | + <div class="form-group"> | ||
| 107 | + <label for="query">Command</label> | ||
| 108 | + <input class="form-control" id="cmd"> | ||
| 109 | + </div> | ||
| 110 | + <button id="selected-db-info" type="button" class="btn btn-info">Welcome</button> | ||
| 111 | + <button id="success-info" type="button" class="btn btn-success display-none">Query Executed</button> | ||
| 112 | + <button id="error-info" type="button" class="btn btn-danger display-none">Query Not Executed</button> | ||
| 113 | + <button id="run-query" type="submit" onclick="queryFunction()" class="btn btn-primary pull-right disabled"> | ||
| 114 | + Run | ||
| 115 | + Query | ||
| 116 | + </button> | ||
| 117 | + <button id="reset-led-status" type="submit" onclick="resetLedStatus()" | ||
| 118 | + class="btn btn-primary pull-right">Execute Command | ||
| 119 | + </button> | ||
| 120 | + </div> | ||
| 121 | + </div> | ||
| 122 | + | ||
| 123 | + | ||
| 124 | + <div class="row padding-twenty"> | ||
| 125 | + | ||
| 126 | + <div class="col-sm-2"> | ||
| 127 | + <div class="panel panel-info"> | ||
| 128 | + <div class="panel-heading">Databases</div> | ||
| 129 | + </div> | ||
| 130 | + <div id="db-list" class="list-group"> | ||
| 131 | + </div> | ||
| 132 | + </div> | ||
| 133 | + | ||
| 134 | + <div class="col-sm-2"> | ||
| 135 | + <div class="panel panel-info"> | ||
| 136 | + <div class="panel-heading">Tables</div> | ||
| 137 | + </div> | ||
| 138 | + <div id="table-list" class="list-group"> | ||
| 139 | + </div> | ||
| 140 | + </div> | ||
| 141 | + | ||
| 142 | + <div id="parent-data-div" class="col-sm-8"> | ||
| 143 | + <div class="panel panel-info"> | ||
| 144 | + <div class="panel-heading">Data</div> | ||
| 145 | + </div> | ||
| 146 | + </div> | ||
| 147 | + </div> | ||
| 148 | + | ||
| 149 | +</div> | ||
| 150 | + | ||
| 151 | +<script src="app.js"></script> | ||
| 152 | +</body> | ||
| 153 | +</html> |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar; | ||
| 21 | + | ||
| 22 | +import android.content.Context; | ||
| 23 | +import android.util.Log; | ||
| 24 | + | ||
| 25 | +import com.amitshekhar.server.ClientServer; | ||
| 26 | +import com.amitshekhar.utils.NetworkUtils; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * Created by amitshekhar on 15/11/16. | ||
| 30 | + */ | ||
| 31 | + | ||
| 32 | +public class DebugDB { | ||
| 33 | + | ||
| 34 | + private static final String TAG = DebugDB.class.getSimpleName(); | ||
| 35 | + private static final int DEFAULT_PORT = 8080; | ||
| 36 | + private static ClientServer clientServer; | ||
| 37 | + private static String addressLog = "not available"; | ||
| 38 | + | ||
| 39 | + private DebugDB() { | ||
| 40 | + // This class in not publicly instantiable | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public static void initialize(Context context) { | ||
| 44 | + int portNumber; | ||
| 45 | + | ||
| 46 | + try { | ||
| 47 | + portNumber = Integer.valueOf(context.getString(R.string.PORT_NUMBER)); | ||
| 48 | + } catch (NumberFormatException ex) { | ||
| 49 | + Log.e(TAG, "PORT_NUMBER should be integer", ex); | ||
| 50 | + portNumber = DEFAULT_PORT; | ||
| 51 | + Log.i(TAG, "Using Default port : " + DEFAULT_PORT); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + clientServer = new ClientServer(context, portNumber); | ||
| 55 | + clientServer.start(); | ||
| 56 | + addressLog = NetworkUtils.getAddressLog(context, portNumber); | ||
| 57 | + Log.d(TAG, addressLog); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + public static String getAddressLog() { | ||
| 61 | + Log.d(TAG, addressLog); | ||
| 62 | + return addressLog; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public static void shutDown() { | ||
| 66 | + if (clientServer != null) { | ||
| 67 | + clientServer.stop(); | ||
| 68 | + clientServer = null; | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar; | ||
| 21 | + | ||
| 22 | +import android.content.ContentProvider; | ||
| 23 | +import android.content.ContentValues; | ||
| 24 | +import android.content.Context; | ||
| 25 | +import android.content.pm.ProviderInfo; | ||
| 26 | +import android.database.Cursor; | ||
| 27 | +import android.net.Uri; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Created by amitshekhar on 16/11/16. | ||
| 31 | + */ | ||
| 32 | + | ||
| 33 | +public class DebugDBInitProvider extends ContentProvider { | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + public DebugDBInitProvider() { | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + @Override | ||
| 40 | + public boolean onCreate() { | ||
| 41 | + DebugDB.initialize(getContext()); | ||
| 42 | + return true; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Override | ||
| 46 | + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { | ||
| 47 | + return null; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public String getType(Uri uri) { | ||
| 52 | + return null; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + @Override | ||
| 56 | + public Uri insert(Uri uri, ContentValues values) { | ||
| 57 | + return null; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + @Override | ||
| 61 | + public int delete(Uri uri, String selection, String[] selectionArgs) { | ||
| 62 | + return 0; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + @Override | ||
| 66 | + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { | ||
| 67 | + return 0; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Override | ||
| 71 | + public void attachInfo(Context context, ProviderInfo providerInfo) { | ||
| 72 | + if (providerInfo == null) { | ||
| 73 | + throw new NullPointerException("DebugDBInitProvider ProviderInfo cannot be null."); | ||
| 74 | + } | ||
| 75 | + // So if the authorities equal the library internal ones, the developer forgot to set his applicationId | ||
| 76 | + if ("com.amitshekhar.DebugDBInitProvider".equals(providerInfo.authority)) { | ||
| 77 | + throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " | ||
| 78 | + + "missing applicationId variable in application\'s build.gradle."); | ||
| 79 | + } | ||
| 80 | + super.attachInfo(context, providerInfo); | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar.model; | ||
| 21 | + | ||
| 22 | +import java.util.ArrayList; | ||
| 23 | +import java.util.List; | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + * Created by amitshekhar on 15/11/16. | ||
| 27 | + */ | ||
| 28 | + | ||
| 29 | +public class Response { | ||
| 30 | + | ||
| 31 | + public List rows = new ArrayList(); | ||
| 32 | + public List<String> columns = new ArrayList<>(); | ||
| 33 | + public boolean isSuccessful; | ||
| 34 | + public String error; | ||
| 35 | + | ||
| 36 | + public Response() { | ||
| 37 | + | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar.server; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * Created by amitshekhar on 15/11/16. | ||
| 24 | + */ | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +import android.content.Context; | ||
| 28 | +import android.content.SharedPreferences; | ||
| 29 | +import android.content.res.AssetManager; | ||
| 30 | +import android.database.Cursor; | ||
| 31 | +import android.database.sqlite.SQLiteDatabase; | ||
| 32 | +import android.text.TextUtils; | ||
| 33 | +import android.util.Log; | ||
| 34 | + | ||
| 35 | +import com.amitshekhar.model.Response; | ||
| 36 | +import com.amitshekhar.utils.Constants; | ||
| 37 | +import com.amitshekhar.utils.PrefUtils; | ||
| 38 | +import com.gimi.common.cinema.utils.ShellUtils; | ||
| 39 | +import com.gimi.common.cinema.utils.SystemUtils; | ||
| 40 | +import com.google.gson.Gson; | ||
| 41 | + | ||
| 42 | +import java.io.BufferedReader; | ||
| 43 | +import java.io.ByteArrayOutputStream; | ||
| 44 | +import java.io.File; | ||
| 45 | +import java.io.FileNotFoundException; | ||
| 46 | +import java.io.FileReader; | ||
| 47 | +import java.io.IOException; | ||
| 48 | +import java.io.InputStream; | ||
| 49 | +import java.io.InputStreamReader; | ||
| 50 | +import java.io.PrintStream; | ||
| 51 | +import java.io.UnsupportedEncodingException; | ||
| 52 | +import java.net.InetSocketAddress; | ||
| 53 | +import java.net.ServerSocket; | ||
| 54 | +import java.net.Socket; | ||
| 55 | +import java.net.SocketException; | ||
| 56 | +import java.net.URLDecoder; | ||
| 57 | +import java.util.ArrayList; | ||
| 58 | +import java.util.List; | ||
| 59 | +import java.util.Map; | ||
| 60 | + | ||
| 61 | +public class ClientServer implements Runnable { | ||
| 62 | + | ||
| 63 | + private static final String TAG = "SimpleWebServer"; | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * The port number we listen to | ||
| 67 | + */ | ||
| 68 | + private final int mPort; | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * {@link AssetManager} for loading files to serve. | ||
| 72 | + */ | ||
| 73 | + private final AssetManager mAssets; | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * True if the server is running. | ||
| 77 | + */ | ||
| 78 | + private boolean mIsRunning; | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * The {@link ServerSocket} that we listen to. | ||
| 82 | + */ | ||
| 83 | + private ServerSocket mServerSocket; | ||
| 84 | + | ||
| 85 | + | ||
| 86 | + private Context mContext; | ||
| 87 | + private SQLiteDatabase mDatabase; | ||
| 88 | + private File mDatabaseDir; | ||
| 89 | + private Gson mGson; | ||
| 90 | + private boolean isDbOpenned; | ||
| 91 | + | ||
| 92 | + /** | ||
| 93 | + * WebServer constructor. | ||
| 94 | + */ | ||
| 95 | + public ClientServer(Context context, int port) { | ||
| 96 | + mPort = port; | ||
| 97 | + mAssets = context.getResources().getAssets(); | ||
| 98 | + mContext = context; | ||
| 99 | + mGson = new Gson(); | ||
| 100 | + getDatabaseDir(); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + /** | ||
| 104 | + * This method starts the web server listening to the specified port. | ||
| 105 | + */ | ||
| 106 | + public void start() { | ||
| 107 | + mIsRunning = true; | ||
| 108 | + new Thread(this).start(); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * This method stops the web server | ||
| 113 | + */ | ||
| 114 | + public void stop() { | ||
| 115 | + try { | ||
| 116 | + mIsRunning = false; | ||
| 117 | + if (null != mServerSocket) { | ||
| 118 | + mServerSocket.close(); | ||
| 119 | + mServerSocket = null; | ||
| 120 | + } | ||
| 121 | + } catch (IOException e) { | ||
| 122 | + Log.e(TAG, "Error closing the server socket.", e); | ||
| 123 | + } | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + @Override | ||
| 127 | + public void run() { | ||
| 128 | + try { | ||
| 129 | + mServerSocket = //new ServerSocket(mPort); | ||
| 130 | + new ServerSocket(); | ||
| 131 | + mServerSocket.setReuseAddress(true); | ||
| 132 | + mServerSocket.bind(new InetSocketAddress(mPort)); | ||
| 133 | + while (mIsRunning) { | ||
| 134 | + Socket socket = mServerSocket.accept(); | ||
| 135 | + handle(socket); | ||
| 136 | + socket.close(); | ||
| 137 | + } | ||
| 138 | + } catch (IOException e) { | ||
| 139 | + Log.e(TAG, "Web server error.", e); | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + /** | ||
| 144 | + * Respond to a request from a client. | ||
| 145 | + * | ||
| 146 | + * @param socket The client socket. | ||
| 147 | + * @throws IOException | ||
| 148 | + */ | ||
| 149 | + private void handle(Socket socket) throws IOException { | ||
| 150 | + BufferedReader reader = null; | ||
| 151 | + PrintStream output = null; | ||
| 152 | + try { | ||
| 153 | + String route = null; | ||
| 154 | + | ||
| 155 | + // Read HTTP headers and parse out the route. | ||
| 156 | + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); | ||
| 157 | + String line; | ||
| 158 | + while (!TextUtils.isEmpty(line = reader.readLine())) { | ||
| 159 | + if (line.startsWith("GET /")) { | ||
| 160 | + int start = line.indexOf('/') + 1; | ||
| 161 | + int end = line.indexOf(' ', start); | ||
| 162 | + route = line.substring(start, end); | ||
| 163 | + break; | ||
| 164 | + } | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + // Output stream that we send the response to | ||
| 168 | + output = new PrintStream(socket.getOutputStream()); | ||
| 169 | + | ||
| 170 | + if (route == null || route.isEmpty()) { | ||
| 171 | + route = "index.html"; | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + byte[] bytes; | ||
| 175 | + | ||
| 176 | + if (route.startsWith("getAllDataFromTheTable")) { | ||
| 177 | + String query = null; | ||
| 178 | + | ||
| 179 | + if (route.contains("?tableName=")) { | ||
| 180 | + query = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + Response response; | ||
| 184 | + | ||
| 185 | + if (isDbOpenned) { | ||
| 186 | + String sql = "SELECT * FROM " + query; | ||
| 187 | + response = query(sql); | ||
| 188 | + } else { | ||
| 189 | + response = getAllPrefData(query); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + String data = mGson.toJson(response); | ||
| 193 | + bytes = data.getBytes(); | ||
| 194 | + | ||
| 195 | + } else if (route.startsWith("query")) { | ||
| 196 | + String query = null; | ||
| 197 | + if (route.contains("?query=")) { | ||
| 198 | + query = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + Response response; | ||
| 202 | + | ||
| 203 | + try { | ||
| 204 | + query = java.net.URLDecoder.decode(query, "UTF-8"); | ||
| 205 | + } catch (Exception e) { | ||
| 206 | + e.printStackTrace(); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + String first = query.split(" ")[0].toLowerCase(); | ||
| 210 | + | ||
| 211 | + if (first.equals("select")) { | ||
| 212 | + response = query(query); | ||
| 213 | + } else { | ||
| 214 | + response = exec(query); | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + String data = mGson.toJson(response); | ||
| 218 | + bytes = data.getBytes(); | ||
| 219 | + | ||
| 220 | + } else if (route.startsWith("getDbList")) { | ||
| 221 | + Response response = getDBList(); | ||
| 222 | + String data = mGson.toJson(response); | ||
| 223 | + bytes = data.getBytes(); | ||
| 224 | + } else if (route.startsWith("cmd")) { | ||
| 225 | + String cmd = null; | ||
| 226 | + if (route.contains("?cmd=")) { | ||
| 227 | + cmd = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 228 | + } | ||
| 229 | + Response response = getExecCmd(cmd); | ||
| 230 | + String data = mGson.toJson(response); | ||
| 231 | + bytes = data.getBytes(); | ||
| 232 | + } else if (route.startsWith("getTableList")) { | ||
| 233 | + String database = null; | ||
| 234 | + if (route.contains("?database=")) { | ||
| 235 | + database = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + Response response; | ||
| 239 | + | ||
| 240 | + if (Constants.APP_SHARED_PREFERENCES.equals(database)) { | ||
| 241 | + response = getAllPrefTableName(); | ||
| 242 | + closeDatabase(); | ||
| 243 | + } else { | ||
| 244 | + openDatabase(database); | ||
| 245 | + response = getAllTableName(); | ||
| 246 | + } | ||
| 247 | + | ||
| 248 | + String data = mGson.toJson(response); | ||
| 249 | + bytes = data.getBytes(); | ||
| 250 | + } else if (route.startsWith("test_on_line")) { | ||
| 251 | + String database = null; | ||
| 252 | + if (route.contains("?database=")) { | ||
| 253 | + database = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + Response response; | ||
| 257 | + | ||
| 258 | + if (Constants.APP_SHARED_PREFERENCES.equals(database)) { | ||
| 259 | + response = getAllPrefTableName(); | ||
| 260 | + closeDatabase(); | ||
| 261 | + } else { | ||
| 262 | + openDatabase(database); | ||
| 263 | + response = getAllTableName(); | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + String data = mGson.toJson(response); | ||
| 267 | + bytes = data.getBytes(); | ||
| 268 | + } else if (route.startsWith("test_offline")) { | ||
| 269 | + String database = null; | ||
| 270 | + if (route.contains("?database=")) { | ||
| 271 | + database = route.substring(route.indexOf("=") + 1, route.length()); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + Response response; | ||
| 275 | + | ||
| 276 | + if (Constants.APP_SHARED_PREFERENCES.equals(database)) { | ||
| 277 | + response = getAllPrefTableName(); | ||
| 278 | + closeDatabase(); | ||
| 279 | + } else { | ||
| 280 | + openDatabase(database); | ||
| 281 | + response = getAllTableName(); | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + String data = mGson.toJson(response); | ||
| 285 | + bytes = data.getBytes(); | ||
| 286 | + } else { | ||
| 287 | + bytes = loadContent(route); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + | ||
| 291 | + if (null == bytes) { | ||
| 292 | + writeServerError(output); | ||
| 293 | + return; | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + // Send out the content. | ||
| 297 | + output.println("HTTP/1.0 200 OK"); | ||
| 298 | + output.println("Content-Type: " + detectMimeType(route)); | ||
| 299 | + output.println("Content-Length: " + bytes.length); | ||
| 300 | + output.println(); | ||
| 301 | + output.write(bytes); | ||
| 302 | + output.flush(); | ||
| 303 | + } finally { | ||
| 304 | + if (null != output) { | ||
| 305 | + output.close(); | ||
| 306 | + } | ||
| 307 | + if (null != reader) { | ||
| 308 | + reader.close(); | ||
| 309 | + } | ||
| 310 | + } | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + /** | ||
| 314 | + * Writes a server error response (HTTP/1.0 500) to the given output stream. | ||
| 315 | + * | ||
| 316 | + * @param output The output stream. | ||
| 317 | + */ | ||
| 318 | + private void writeServerError(PrintStream output) { | ||
| 319 | + output.println("HTTP/1.0 500 Internal Server Error"); | ||
| 320 | + output.flush(); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + /** | ||
| 324 | + * Loads all the content of {@code fileName}. | ||
| 325 | + * | ||
| 326 | + * @param fileName The name of the file. | ||
| 327 | + * @return The content of the file. | ||
| 328 | + * @throws IOException | ||
| 329 | + */ | ||
| 330 | + private byte[] loadContent(String fileName) throws IOException { | ||
| 331 | + InputStream input = null; | ||
| 332 | + try { | ||
| 333 | + ByteArrayOutputStream output = new ByteArrayOutputStream(); | ||
| 334 | + input = mAssets.open(fileName); | ||
| 335 | + byte[] buffer = new byte[1024]; | ||
| 336 | + int size; | ||
| 337 | + while (-1 != (size = input.read(buffer))) { | ||
| 338 | + output.write(buffer, 0, size); | ||
| 339 | + } | ||
| 340 | + output.flush(); | ||
| 341 | + return output.toByteArray(); | ||
| 342 | + } catch (FileNotFoundException e) { | ||
| 343 | + return null; | ||
| 344 | + } finally { | ||
| 345 | + if (null != input) { | ||
| 346 | + input.close(); | ||
| 347 | + } | ||
| 348 | + } | ||
| 349 | + } | ||
| 350 | + | ||
| 351 | + /** | ||
| 352 | + * Detects the MIME type from the {@code fileName}. | ||
| 353 | + * | ||
| 354 | + * @param fileName The name of the file. | ||
| 355 | + * @return A MIME type. | ||
| 356 | + */ | ||
| 357 | + private String detectMimeType(String fileName) { | ||
| 358 | + if (TextUtils.isEmpty(fileName)) { | ||
| 359 | + return null; | ||
| 360 | + } else if (fileName.endsWith(".html")) { | ||
| 361 | + return "text/html"; | ||
| 362 | + } else if (fileName.endsWith(".js")) { | ||
| 363 | + return "application/javascript"; | ||
| 364 | + } else if (fileName.endsWith(".css")) { | ||
| 365 | + return "text/css"; | ||
| 366 | + } else { | ||
| 367 | + return "application/octet-stream"; | ||
| 368 | + } | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + private void getDatabaseDir() { | ||
| 372 | + File root = mContext.getFilesDir().getParentFile(); | ||
| 373 | + File dbRoot = new File(root, "/databases"); | ||
| 374 | + mDatabaseDir = dbRoot; | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + private void openDatabase(String database) { | ||
| 378 | + mDatabase = mContext.openOrCreateDatabase(database, 0, null); | ||
| 379 | + isDbOpenned = true; | ||
| 380 | + } | ||
| 381 | + | ||
| 382 | + private void closeDatabase() { | ||
| 383 | + mDatabase = null; | ||
| 384 | + isDbOpenned = false; | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + private Response exec(String sql) { | ||
| 388 | + Response response = new Response(); | ||
| 389 | + try { | ||
| 390 | + mDatabase.execSQL(sql); | ||
| 391 | + } catch (Exception e) { | ||
| 392 | + e.printStackTrace(); | ||
| 393 | + response.isSuccessful = false; | ||
| 394 | + response.error = e.getMessage(); | ||
| 395 | + return response; | ||
| 396 | + } | ||
| 397 | + response.isSuccessful = true; | ||
| 398 | + return response; | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + private Response query(String sql) { | ||
| 402 | + Cursor cursor; | ||
| 403 | + try { | ||
| 404 | + cursor = mDatabase.rawQuery(sql, null); | ||
| 405 | + } catch (Exception e) { | ||
| 406 | + e.printStackTrace(); | ||
| 407 | + Response msg = new Response(); | ||
| 408 | + msg.isSuccessful = false; | ||
| 409 | + msg.error = e.getMessage(); | ||
| 410 | + return msg; | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + if (cursor != null) { | ||
| 414 | + cursor.moveToFirst(); | ||
| 415 | + Response response = new Response(); | ||
| 416 | + response.isSuccessful = true; | ||
| 417 | + List<String> columns = new ArrayList<>(); | ||
| 418 | + for (int i = 0; i < cursor.getColumnCount(); i++) { | ||
| 419 | + String name = cursor.getColumnName(i); | ||
| 420 | + columns.add(name); | ||
| 421 | + } | ||
| 422 | + response.columns = columns; | ||
| 423 | + | ||
| 424 | + if (cursor.getCount() > 0) { | ||
| 425 | + do { | ||
| 426 | + List row = new ArrayList(); | ||
| 427 | + for (int i = 0; i < cursor.getColumnCount(); i++) { | ||
| 428 | + switch (cursor.getType(i)) { | ||
| 429 | + case Cursor.FIELD_TYPE_BLOB: | ||
| 430 | + row.add(cursor.getBlob(i)); | ||
| 431 | + break; | ||
| 432 | + case Cursor.FIELD_TYPE_FLOAT: | ||
| 433 | + row.add(Float.valueOf(cursor.getFloat(i))); | ||
| 434 | + break; | ||
| 435 | + case Cursor.FIELD_TYPE_INTEGER: | ||
| 436 | + row.add(Integer.valueOf(cursor.getInt(i))); | ||
| 437 | + break; | ||
| 438 | + case Cursor.FIELD_TYPE_STRING: | ||
| 439 | + row.add(cursor.getString(i)); | ||
| 440 | + break; | ||
| 441 | + default: | ||
| 442 | + row.add(""); | ||
| 443 | + } | ||
| 444 | + } | ||
| 445 | + response.rows.add(row); | ||
| 446 | + | ||
| 447 | + } while (cursor.moveToNext()); | ||
| 448 | + } | ||
| 449 | + | ||
| 450 | + return response; | ||
| 451 | + } else { | ||
| 452 | + Response response = new Response(); | ||
| 453 | + response.isSuccessful = false; | ||
| 454 | + response.error = "Cursor is null"; | ||
| 455 | + return response; | ||
| 456 | + } | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + public Response getDBList() { | ||
| 460 | + Response response = new Response(); | ||
| 461 | + if (mDatabaseDir != null) { | ||
| 462 | + for (String name : mDatabaseDir.list()) { | ||
| 463 | + response.rows.add(name); | ||
| 464 | + } | ||
| 465 | + } | ||
| 466 | + response.rows.add(Constants.APP_SHARED_PREFERENCES); | ||
| 467 | + response.isSuccessful = true; | ||
| 468 | + return response; | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + public Response getAllTableName() { | ||
| 472 | + Response response = new Response(); | ||
| 473 | + Cursor c = mDatabase.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null); | ||
| 474 | + | ||
| 475 | + if (c.moveToFirst()) { | ||
| 476 | + while (!c.isAfterLast()) { | ||
| 477 | + response.rows.add(c.getString(0)); | ||
| 478 | + c.moveToNext(); | ||
| 479 | + } | ||
| 480 | + } | ||
| 481 | + response.isSuccessful = true; | ||
| 482 | + return response; | ||
| 483 | + } | ||
| 484 | + | ||
| 485 | + public Response getAllPrefTableName() { | ||
| 486 | + Response response = new Response(); | ||
| 487 | + List<String> prefTags = PrefUtils.getSharedPreferenceTags(mContext); | ||
| 488 | + | ||
| 489 | + for (String tag : prefTags) { | ||
| 490 | + response.rows.add(tag); | ||
| 491 | + } | ||
| 492 | + response.isSuccessful = true; | ||
| 493 | + return response; | ||
| 494 | + } | ||
| 495 | + | ||
| 496 | + public Response getExecCmd(String cmd) { | ||
| 497 | + SystemUtils systemUtils = new SystemUtils(); | ||
| 498 | + String data = null; | ||
| 499 | + try { | ||
| 500 | + //decode 3A%->: 20%->' ' ,etc. | ||
| 501 | + cmd = URLDecoder.decode(cmd, "utf-8"); | ||
| 502 | + } catch (UnsupportedEncodingException e) { | ||
| 503 | + e.printStackTrace(); | ||
| 504 | + } | ||
| 505 | + if (Constants.APP_SHARED_CLOSE_LED.equals(cmd)) { | ||
| 506 | + systemUtils.setLedStatus(false); | ||
| 507 | + data = "close led success"; | ||
| 508 | + } else if (Constants.APP_SHARED_OPEN_LED.equals(cmd)) { | ||
| 509 | + systemUtils.openLed(mContext); | ||
| 510 | + data = "open led success"; | ||
| 511 | + } else if (Constants.APP_SHARED_LOGCAT.equals(cmd)) { | ||
| 512 | + new Thread() { | ||
| 513 | + @Override | ||
| 514 | + public void run() { | ||
| 515 | + super.run(); | ||
| 516 | + ShellUtils.execCommand("logcat -c;logcat -v time > /sdcard/debug.log&", false); | ||
| 517 | + } | ||
| 518 | + }.start(); | ||
| 519 | + | ||
| 520 | + data = "save log to sdcard"; | ||
| 521 | + } else if (Constants.APP_SHARED_SHOW_LOGCAT.equals(cmd)) { | ||
| 522 | + new Thread() { | ||
| 523 | + @Override | ||
| 524 | + public void run() { | ||
| 525 | + super.run(); | ||
| 526 | + ShellUtils.execCommand("ps | grep logcat | busybox awk '{print $2}'|busybox xargs kill -9", false); | ||
| 527 | + } | ||
| 528 | + }.start(); | ||
| 529 | +// data = "save log to sdcard"; | ||
| 530 | + } else if (Constants.APP_SHARED_TEST_ONLINE.equals(cmd)) { | ||
| 531 | + saveTest(false); | ||
| 532 | + data = "set test on line please reboot"; | ||
| 533 | + } else if (Constants.APP_SHARED_TEST_OFFLINE.equals(cmd)) { | ||
| 534 | + saveTest(true); | ||
| 535 | + data = "set test off line please reboot"; | ||
| 536 | + } else if (Constants.APP_START_ADB.equals(cmd)) { | ||
| 537 | + ShellUtils.execCommand(new String[]{"su", "start adbd"}, false); | ||
| 538 | + data = "open adb success,please connect by pc"; | ||
| 539 | + } else if (!TextUtils.isEmpty(cmd) && cmd.startsWith(Constants.APP_SHARED_SET_STRING)) { | ||
| 540 | + String[] split = cmd.split(":"); | ||
| 541 | + if (split.length >= 3 && !TextUtils.isEmpty(split[1]) && !TextUtils.isEmpty(split[2])) { | ||
| 542 | + saveString(split[1], split[2]); | ||
| 543 | + data = "set " + split[1] + ":" + split[2] + " perhaps success,please check!"; | ||
| 544 | + } else { | ||
| 545 | + data = "set string value error:" + cmd; | ||
| 546 | + } | ||
| 547 | + } else { | ||
| 548 | + data = "unknown command"; | ||
| 549 | + } | ||
| 550 | + | ||
| 551 | + Response response = new Response(); | ||
| 552 | + response.isSuccessful = true; | ||
| 553 | + response.columns.add("Command"); | ||
| 554 | + response.columns.add("Result"); | ||
| 555 | + if (!TextUtils.isEmpty(data)) { | ||
| 556 | + List row = new ArrayList(); | ||
| 557 | + row.add(cmd); | ||
| 558 | + row.add(data); | ||
| 559 | + response.rows.add(row); | ||
| 560 | + } else if (Constants.APP_SHARED_SHOW_LOGCAT.equals(cmd)) { | ||
| 561 | + ArrayList<String> read = read("/sdcard/debug.log"); | ||
| 562 | + boolean hasAdd = false; | ||
| 563 | + for (String s : read) { | ||
| 564 | + ArrayList row = new ArrayList(); | ||
| 565 | + if (!hasAdd) { | ||
| 566 | + row.add(cmd); | ||
| 567 | + } else { | ||
| 568 | + row.add(""); | ||
| 569 | + } | ||
| 570 | + row.add(s); | ||
| 571 | + response.rows.add(row); | ||
| 572 | + hasAdd = true; | ||
| 573 | + } | ||
| 574 | + } else { | ||
| 575 | + List row = new ArrayList(); | ||
| 576 | + row.add(cmd); | ||
| 577 | + row.add(data); | ||
| 578 | + response.rows.add(row); | ||
| 579 | + } | ||
| 580 | + return response; | ||
| 581 | + } | ||
| 582 | + | ||
| 583 | + private void saveTest(boolean bool) { | ||
| 584 | + saveBoolean("test", bool); | ||
| 585 | + } | ||
| 586 | + | ||
| 587 | + | ||
| 588 | + private void saveBoolean(String key, boolean bool) { | ||
| 589 | + SharedPreferences sharedPreferences = mContext.getSharedPreferences("gimi-cinema-pref", Context.MODE_PRIVATE); | ||
| 590 | + SharedPreferences.Editor edit = sharedPreferences.edit(); | ||
| 591 | + edit.putBoolean(key, bool); | ||
| 592 | + edit.apply(); | ||
| 593 | + } | ||
| 594 | + | ||
| 595 | + private void saveString(String key, String value) { | ||
| 596 | + SharedPreferences sharedPreferences = mContext.getSharedPreferences("gimi-cinema-pref", Context.MODE_PRIVATE); | ||
| 597 | + SharedPreferences.Editor edit = sharedPreferences.edit(); | ||
| 598 | + edit.putString(key, value); | ||
| 599 | + edit.apply(); | ||
| 600 | + } | ||
| 601 | + | ||
| 602 | + /** | ||
| 603 | + * 以行为单位读取文件,常用于读面向行的格式化文件 | ||
| 604 | + */ | ||
| 605 | + private ArrayList<String> read(String fileName) { | ||
| 606 | + ArrayList<String> result = new ArrayList<String>(); | ||
| 607 | + File file = new File(fileName); | ||
| 608 | + if (!file.exists()) { | ||
| 609 | + result.add("文件不存在"); | ||
| 610 | + return result; | ||
| 611 | + } | ||
| 612 | + BufferedReader reader = null; | ||
| 613 | + try { | ||
| 614 | + reader = new BufferedReader(new FileReader(file)); | ||
| 615 | + String tempString = null; | ||
| 616 | + while ((tempString = reader.readLine()) != null) { | ||
| 617 | + result.add(tempString.trim()); | ||
| 618 | + } | ||
| 619 | + reader.close(); | ||
| 620 | + } catch (IOException e) { | ||
| 621 | + e.printStackTrace(); | ||
| 622 | + result.add(e.getMessage()); | ||
| 623 | + } finally { | ||
| 624 | + if (reader != null) { | ||
| 625 | + try { | ||
| 626 | + reader.close(); | ||
| 627 | + } catch (IOException e1) { | ||
| 628 | + e1.printStackTrace(); | ||
| 629 | + result.add(e1.getMessage()); | ||
| 630 | + } | ||
| 631 | + } | ||
| 632 | + } | ||
| 633 | + | ||
| 634 | + return result; | ||
| 635 | + } | ||
| 636 | + | ||
| 637 | + public Response getAllPrefData(String tag) { | ||
| 638 | + Response response = new Response(); | ||
| 639 | + response.isSuccessful = true; | ||
| 640 | + response.columns.add("Key"); | ||
| 641 | + response.columns.add("Value"); | ||
| 642 | + SharedPreferences preferences = mContext.getSharedPreferences(tag, Context.MODE_PRIVATE); | ||
| 643 | + Map<String, ?> allEntries = preferences.getAll(); | ||
| 644 | + for (Map.Entry<String, ?> entry : allEntries.entrySet()) { | ||
| 645 | + List row = new ArrayList(); | ||
| 646 | + row.add(entry.getKey()); | ||
| 647 | + row.add(entry.getValue().toString()); | ||
| 648 | + response.rows.add(row); | ||
| 649 | + } | ||
| 650 | + return response; | ||
| 651 | + } | ||
| 652 | + | ||
| 653 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar.utils; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * Created by amitshekhar on 16/11/16. | ||
| 24 | + */ | ||
| 25 | + | ||
| 26 | +public final class Constants { | ||
| 27 | + | ||
| 28 | + private Constants() { | ||
| 29 | + // This class in not publicly instantiable | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public static final String APP_SHARED_PREFERENCES = "APP_SHARED_PREFERENCES"; | ||
| 33 | + public static final String APP_SHARED_CLOSE_LED = "close_led"; | ||
| 34 | + public static final String APP_SHARED_OPEN_LED = "open_led"; | ||
| 35 | + public static final String APP_SHARED_LOGCAT = "logcat"; | ||
| 36 | + public static final String APP_SHARED_SHOW_LOGCAT = "show_logcat"; | ||
| 37 | + public static final String APP_SHARED_TEST_ONLINE = "test_online"; | ||
| 38 | + public static final String APP_SHARED_TEST_OFFLINE = "test_offline"; | ||
| 39 | + public static final String APP_SHARED_SET_STRING = "wugian_set_string"; | ||
| 40 | + public static final String APP_START_ADB = "wugian_open_adb"; | ||
| 41 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar.utils; | ||
| 21 | + | ||
| 22 | +import android.content.Context; | ||
| 23 | +import android.net.wifi.WifiManager; | ||
| 24 | + | ||
| 25 | +import java.net.InetAddress; | ||
| 26 | +import java.net.NetworkInterface; | ||
| 27 | +import java.net.SocketException; | ||
| 28 | +import java.util.Enumeration; | ||
| 29 | + | ||
| 30 | +/** | ||
| 31 | + * Created by amitshekhar on 15/11/16. | ||
| 32 | + */ | ||
| 33 | + | ||
| 34 | +public final class NetworkUtils { | ||
| 35 | + | ||
| 36 | + private NetworkUtils() { | ||
| 37 | + // This class in not publicly instantiable | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public static String getAddressLog(Context context, int port) { | ||
| 41 | +// WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); | ||
| 42 | +// int ipAddress = wifiManager.getConnectionInfo().getIpAddress(); | ||
| 43 | +// final String formatedIpAddress = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff)); | ||
| 44 | + try { | ||
| 45 | + return "Open http://" + getIp() + ":" + port + " in your browser"; | ||
| 46 | + } catch (SocketException e) { | ||
| 47 | + e.printStackTrace(); | ||
| 48 | + } | ||
| 49 | + return ""; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public static String getIp() throws SocketException { | ||
| 53 | + String ipAddress = ""; | ||
| 54 | + Enumeration<NetworkInterface> netInterfaces = null; | ||
| 55 | + netInterfaces = NetworkInterface.getNetworkInterfaces(); | ||
| 56 | + if (netInterfaces == null) { | ||
| 57 | + return ipAddress; | ||
| 58 | + } | ||
| 59 | + while (netInterfaces.hasMoreElements()) { | ||
| 60 | + NetworkInterface intf = netInterfaces.nextElement(); | ||
| 61 | + if (intf.getName().toLowerCase().equals("eth0") || intf.getName().toLowerCase().equals("wlan0")) { | ||
| 62 | + for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { | ||
| 63 | + InetAddress inetAddress = enumIpAddr.nextElement(); | ||
| 64 | + if (!inetAddress.isLoopbackAddress()) { | ||
| 65 | + String curIpAddress = inetAddress.getHostAddress(); | ||
| 66 | + if (!curIpAddress.contains("::")) { // 过滤ipV6的地址 | ||
| 67 | + ipAddress = curIpAddress; | ||
| 68 | + if (intf.getName().toLowerCase().equals("eth0")) { | ||
| 69 | + return ipAddress; | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + return ipAddress; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | +} |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar.utils; | ||
| 21 | + | ||
| 22 | +import android.content.Context; | ||
| 23 | + | ||
| 24 | +import java.io.File; | ||
| 25 | +import java.util.ArrayList; | ||
| 26 | +import java.util.Collections; | ||
| 27 | +import java.util.List; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Created by amitshekhar on 16/11/16. | ||
| 31 | + */ | ||
| 32 | + | ||
| 33 | +public final class PrefUtils { | ||
| 34 | + | ||
| 35 | + private static final String PREFS_SUFFIX = ".xml"; | ||
| 36 | + | ||
| 37 | + private PrefUtils() { | ||
| 38 | + // This class in not publicly instantiable | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public static List<String> getSharedPreferenceTags(Context context) { | ||
| 42 | + | ||
| 43 | + ArrayList<String> tags = new ArrayList<>(); | ||
| 44 | + | ||
| 45 | + String rootPath = context.getApplicationInfo().dataDir + "/shared_prefs"; | ||
| 46 | + File root = new File(rootPath); | ||
| 47 | + if (root.exists()) { | ||
| 48 | + for (File file : root.listFiles()) { | ||
| 49 | + String fileName = file.getName(); | ||
| 50 | + if (fileName.endsWith(PREFS_SUFFIX)) { | ||
| 51 | + tags.add(fileName.substring(0, fileName.length() - PREFS_SUFFIX.length())); | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + Collections.sort(tags); | ||
| 57 | + | ||
| 58 | + return tags; | ||
| 59 | + } | ||
| 60 | +} |
debug-db/src/main/res/values/strings.xml
0 → 100644
| 1 | +<!-- | ||
| 2 | + ~ /* | ||
| 3 | + ~ * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + ~ * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + ~ * | ||
| 6 | + ~ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + ~ * you may not use this file except in compliance with the License. | ||
| 8 | + ~ * You may obtain a copy of the License at | ||
| 9 | + ~ * | ||
| 10 | + ~ * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + ~ * | ||
| 12 | + ~ * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + ~ * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + ~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + ~ * See the License for the specific language governing permissions and | ||
| 16 | + ~ * limitations under the License. | ||
| 17 | + ~ */ | ||
| 18 | + --> | ||
| 19 | + | ||
| 20 | +<resources> | ||
| 21 | + <string name="app_name">Debug-DB</string> | ||
| 22 | +</resources> |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * * Copyright (C) 2016 Amit Shekhar | ||
| 4 | + * * Copyright (C) 2011 Android Open Source Project | ||
| 5 | + * * | ||
| 6 | + * * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | + * * you may not use this file except in compliance with the License. | ||
| 8 | + * * You may obtain a copy of the License at | ||
| 9 | + * * | ||
| 10 | + * * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | + * * | ||
| 12 | + * * Unless required by applicable law or agreed to in writing, software | ||
| 13 | + * * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | + * * See the License for the specific language governing permissions and | ||
| 16 | + * * limitations under the License. | ||
| 17 | + * | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +package com.amitshekhar; | ||
| 21 | + | ||
| 22 | +import org.junit.Test; | ||
| 23 | + | ||
| 24 | +import static org.junit.Assert.*; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * Example local unit test, which will execute on the development machine (host). | ||
| 28 | + * | ||
| 29 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
| 30 | + */ | ||
| 31 | +public class ExampleUnitTest { | ||
| 32 | + @Test | ||
| 33 | + public void addition_isCorrect() throws Exception { | ||
| 34 | + assertEquals(4, 2 + 2); | ||
| 35 | + } | ||
| 36 | +} |
| @@ -12,6 +12,9 @@ | @@ -12,6 +12,9 @@ | ||
| 12 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> | 12 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> |
| 13 | <uses-permission android:name="android.permission.RESTART_PACKAGES"/> | 13 | <uses-permission android:name="android.permission.RESTART_PACKAGES"/> |
| 14 | <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/> | 14 | <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/> |
| 15 | + <uses-permission android:name="android.permission.BLUETOOTH"/> | ||
| 16 | + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> | ||
| 17 | + | ||
| 15 | <uses-permission | 18 | <uses-permission |
| 16 | android:name="android.permission.FORCE_STOP_PACKAGES" | 19 | android:name="android.permission.FORCE_STOP_PACKAGES" |
| 17 | tools:ignore="ProtectedPermissions"/> | 20 | tools:ignore="ProtectedPermissions"/> |
gradle.properties
0 → 100644
| 1 | +## Project-wide Gradle settings. | ||
| 2 | +# | ||
| 3 | +# For more details on how to configure your build environment visit | ||
| 4 | +# http://www.gradle.org/docs/current/userguide/build_environment.html | ||
| 5 | +# | ||
| 6 | +# Specifies the JVM arguments used for the daemon process. | ||
| 7 | +# The setting is particularly useful for tweaking memory settings. | ||
| 8 | +# Default value: -Xmx1024m -XX:MaxPermSize=256m | ||
| 9 | +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 | ||
| 10 | +# | ||
| 11 | +# When configured, Gradle will run in incubating parallel mode. | ||
| 12 | +# This option should only be used with decoupled projects. More details, visit | ||
| 13 | +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||
| 14 | +# org.gradle.parallel=true | ||
| 15 | +#Fri Oct 28 11:43:32 CST 2016 | ||
| 16 | +ANDROID_MIN_SDK_VERSION=18 | ||
| 17 | +ANDROID_BUILD_COMPILE_SDK_VERSION=18 | ||
| 18 | +ANDROID_TARGET_SDK_VERSION=18 | ||
| 19 | +ANDROID_BUILD_TOOLS_VERSION=24.0.2 | ||
| 20 | +android.useDeprecatedNdk=true | ||
| 21 | + |
gradlew
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +############################################################################## | ||
| 4 | +## | ||
| 5 | +## Gradle start up script for UN*X | ||
| 6 | +## | ||
| 7 | +############################################################################## | ||
| 8 | + | ||
| 9 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 10 | +DEFAULT_JVM_OPTS="" | ||
| 11 | + | ||
| 12 | +APP_NAME="Gradle" | ||
| 13 | +APP_BASE_NAME=`basename "$0"` | ||
| 14 | + | ||
| 15 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| 16 | +MAX_FD="maximum" | ||
| 17 | + | ||
| 18 | +warn ( ) { | ||
| 19 | + echo "$*" | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +die ( ) { | ||
| 23 | + echo | ||
| 24 | + echo "$*" | ||
| 25 | + echo | ||
| 26 | + exit 1 | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +# OS specific support (must be 'true' or 'false'). | ||
| 30 | +cygwin=false | ||
| 31 | +msys=false | ||
| 32 | +darwin=false | ||
| 33 | +case "`uname`" in | ||
| 34 | + CYGWIN* ) | ||
| 35 | + cygwin=true | ||
| 36 | + ;; | ||
| 37 | + Darwin* ) | ||
| 38 | + darwin=true | ||
| 39 | + ;; | ||
| 40 | + MINGW* ) | ||
| 41 | + msys=true | ||
| 42 | + ;; | ||
| 43 | +esac | ||
| 44 | + | ||
| 45 | +# For Cygwin, ensure paths are in UNIX format before anything is touched. | ||
| 46 | +if $cygwin ; then | ||
| 47 | + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` | ||
| 48 | +fi | ||
| 49 | + | ||
| 50 | +# Attempt to set APP_HOME | ||
| 51 | +# Resolve links: $0 may be a link | ||
| 52 | +PRG="$0" | ||
| 53 | +# Need this for relative symlinks. | ||
| 54 | +while [ -h "$PRG" ] ; do | ||
| 55 | + ls=`ls -ld "$PRG"` | ||
| 56 | + link=`expr "$ls" : '.*-> \(.*\)$'` | ||
| 57 | + if expr "$link" : '/.*' > /dev/null; then | ||
| 58 | + PRG="$link" | ||
| 59 | + else | ||
| 60 | + PRG=`dirname "$PRG"`"/$link" | ||
| 61 | + fi | ||
| 62 | +done | ||
| 63 | +SAVED="`pwd`" | ||
| 64 | +cd "`dirname \"$PRG\"`/" >&- | ||
| 65 | +APP_HOME="`pwd -P`" | ||
| 66 | +cd "$SAVED" >&- | ||
| 67 | + | ||
| 68 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
| 69 | + | ||
| 70 | +# Determine the Java command to use to start the JVM. | ||
| 71 | +if [ -n "$JAVA_HOME" ] ; then | ||
| 72 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| 73 | + # IBM's JDK on AIX uses strange locations for the executables | ||
| 74 | + JAVACMD="$JAVA_HOME/jre/sh/java" | ||
| 75 | + else | ||
| 76 | + JAVACMD="$JAVA_HOME/bin/java" | ||
| 77 | + fi | ||
| 78 | + if [ ! -x "$JAVACMD" ] ; then | ||
| 79 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
| 80 | + | ||
| 81 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 82 | +location of your Java installation." | ||
| 83 | + fi | ||
| 84 | +else | ||
| 85 | + JAVACMD="java" | ||
| 86 | + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 87 | + | ||
| 88 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 89 | +location of your Java installation." | ||
| 90 | +fi | ||
| 91 | + | ||
| 92 | +# Increase the maximum file descriptors if we can. | ||
| 93 | +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then | ||
| 94 | + MAX_FD_LIMIT=`ulimit -H -n` | ||
| 95 | + if [ $? -eq 0 ] ; then | ||
| 96 | + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
| 97 | + MAX_FD="$MAX_FD_LIMIT" | ||
| 98 | + fi | ||
| 99 | + ulimit -n $MAX_FD | ||
| 100 | + if [ $? -ne 0 ] ; then | ||
| 101 | + warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
| 102 | + fi | ||
| 103 | + else | ||
| 104 | + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
| 105 | + fi | ||
| 106 | +fi | ||
| 107 | + | ||
| 108 | +# For Darwin, add options to specify how the application appears in the dock | ||
| 109 | +if $darwin; then | ||
| 110 | + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
| 111 | +fi | ||
| 112 | + | ||
| 113 | +# For Cygwin, switch paths to Windows format before running java | ||
| 114 | +if $cygwin ; then | ||
| 115 | + APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
| 116 | + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
| 117 | + | ||
| 118 | + # We build the pattern for arguments to be converted via cygpath | ||
| 119 | + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
| 120 | + SEP="" | ||
| 121 | + for dir in $ROOTDIRSRAW ; do | ||
| 122 | + ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
| 123 | + SEP="|" | ||
| 124 | + done | ||
| 125 | + OURCYGPATTERN="(^($ROOTDIRS))" | ||
| 126 | + # Add a user-defined pattern to the cygpath arguments | ||
| 127 | + if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
| 128 | + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
| 129 | + fi | ||
| 130 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| 131 | + i=0 | ||
| 132 | + for arg in "$@" ; do | ||
| 133 | + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
| 134 | + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
| 135 | + | ||
| 136 | + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
| 137 | + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
| 138 | + else | ||
| 139 | + eval `echo args$i`="\"$arg\"" | ||
| 140 | + fi | ||
| 141 | + i=$((i+1)) | ||
| 142 | + done | ||
| 143 | + case $i in | ||
| 144 | + (0) set -- ;; | ||
| 145 | + (1) set -- "$args0" ;; | ||
| 146 | + (2) set -- "$args0" "$args1" ;; | ||
| 147 | + (3) set -- "$args0" "$args1" "$args2" ;; | ||
| 148 | + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
| 149 | + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
| 150 | + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
| 151 | + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
| 152 | + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
| 153 | + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
| 154 | + esac | ||
| 155 | +fi | ||
| 156 | + | ||
| 157 | +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules | ||
| 158 | +function splitJvmOpts() { | ||
| 159 | + JVM_OPTS=("$@") | ||
| 160 | +} | ||
| 161 | +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS | ||
| 162 | +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" | ||
| 163 | + | ||
| 164 | +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
gradlew.bat
0 → 100644
| 1 | +@if "%DEBUG%" == "" @echo off | ||
| 2 | +@rem ########################################################################## | ||
| 3 | +@rem | ||
| 4 | +@rem Gradle startup script for Windows | ||
| 5 | +@rem | ||
| 6 | +@rem ########################################################################## | ||
| 7 | + | ||
| 8 | +@rem Set local scope for the variables with windows NT shell | ||
| 9 | +if "%OS%"=="Windows_NT" setlocal | ||
| 10 | + | ||
| 11 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 12 | +set DEFAULT_JVM_OPTS= | ||
| 13 | + | ||
| 14 | +set DIRNAME=%~dp0 | ||
| 15 | +if "%DIRNAME%" == "" set DIRNAME=. | ||
| 16 | +set APP_BASE_NAME=%~n0 | ||
| 17 | +set APP_HOME=%DIRNAME% | ||
| 18 | + | ||
| 19 | +@rem Find java.exe | ||
| 20 | +if defined JAVA_HOME goto findJavaFromJavaHome | ||
| 21 | + | ||
| 22 | +set JAVA_EXE=java.exe | ||
| 23 | +%JAVA_EXE% -version >NUL 2>&1 | ||
| 24 | +if "%ERRORLEVEL%" == "0" goto init | ||
| 25 | + | ||
| 26 | +echo. | ||
| 27 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 28 | +echo. | ||
| 29 | +echo Please set the JAVA_HOME variable in your environment to match the | ||
| 30 | +echo location of your Java installation. | ||
| 31 | + | ||
| 32 | +goto fail | ||
| 33 | + | ||
| 34 | +:findJavaFromJavaHome | ||
| 35 | +set JAVA_HOME=%JAVA_HOME:"=% | ||
| 36 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||
| 37 | + | ||
| 38 | +if exist "%JAVA_EXE%" goto init | ||
| 39 | + | ||
| 40 | +echo. | ||
| 41 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||
| 42 | +echo. | ||
| 43 | +echo Please set the JAVA_HOME variable in your environment to match the | ||
| 44 | +echo location of your Java installation. | ||
| 45 | + | ||
| 46 | +goto fail | ||
| 47 | + | ||
| 48 | +:init | ||
| 49 | +@rem Get command-line arguments, handling Windowz variants | ||
| 50 | + | ||
| 51 | +if not "%OS%" == "Windows_NT" goto win9xME_args | ||
| 52 | +if "%@eval[2+2]" == "4" goto 4NT_args | ||
| 53 | + | ||
| 54 | +:win9xME_args | ||
| 55 | +@rem Slurp the command line arguments. | ||
| 56 | +set CMD_LINE_ARGS= | ||
| 57 | +set _SKIP=2 | ||
| 58 | + | ||
| 59 | +:win9xME_args_slurp | ||
| 60 | +if "x%~1" == "x" goto execute | ||
| 61 | + | ||
| 62 | +set CMD_LINE_ARGS=%* | ||
| 63 | +goto execute | ||
| 64 | + | ||
| 65 | +:4NT_args | ||
| 66 | +@rem Get arguments from the 4NT Shell from JP Software | ||
| 67 | +set CMD_LINE_ARGS=%$ | ||
| 68 | + | ||
| 69 | +:execute | ||
| 70 | +@rem Setup the command line | ||
| 71 | + | ||
| 72 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||
| 73 | + | ||
| 74 | +@rem Execute Gradle | ||
| 75 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | ||
| 76 | + | ||
| 77 | +:end | ||
| 78 | +@rem End local scope for the variables with windows NT shell | ||
| 79 | +if "%ERRORLEVEL%"=="0" goto mainEnd | ||
| 80 | + | ||
| 81 | +:fail | ||
| 82 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||
| 83 | +rem the _cmd.exe /c_ return code! | ||
| 84 | +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||
| 85 | +exit /b 1 | ||
| 86 | + | ||
| 87 | +:mainEnd | ||
| 88 | +if "%OS%"=="Windows_NT" endlocal | ||
| 89 | + | ||
| 90 | +:omega |
settings.gradle
0 → 100644
transitionhelper/.gitignore
0 → 100644
transitionhelper/build.gradle
0 → 100644
| 1 | +apply plugin: 'com.android.library' | ||
| 2 | +//apply plugin: 'com.github.dcendents.android-maven' | ||
| 3 | +//apply plugin: 'com.jfrog.bintray' | ||
| 4 | +// This is the library version used when deploying the artifact | ||
| 5 | +version = "1.0.4" | ||
| 6 | +android { | ||
| 7 | + compileSdkVersion 24 | ||
| 8 | + buildToolsVersion "24.0.2" | ||
| 9 | + resourcePrefix "transitionhelper" | ||
| 10 | + defaultConfig { | ||
| 11 | + minSdkVersion 15 | ||
| 12 | + targetSdkVersion 24 | ||
| 13 | + versionCode 1 | ||
| 14 | + versionName version | ||
| 15 | + } | ||
| 16 | + buildTypes { | ||
| 17 | + release { | ||
| 18 | + minifyEnabled false | ||
| 19 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | + lintOptions { | ||
| 23 | + abortOnError false | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +dependencies { | ||
| 31 | + compile fileTree(dir: 'libs', include: ['*.jar']) | ||
| 32 | +} | ||
| 33 | +// | ||
| 34 | +//def siteUrl = 'https://github.com/immortalz' | ||
| 35 | +//def gitUrl = 'https://github.com/ImmortalZ/TransitionHelper' | ||
| 36 | +//group = "me.immortalz" // Maven Group ID for the artifact,一般填你唯一的包名 | ||
| 37 | +//install { | ||
| 38 | +// repositories.mavenInstaller { | ||
| 39 | +// // This generates POM.xml with proper parameters | ||
| 40 | +// pom { | ||
| 41 | +// project { | ||
| 42 | +// packaging 'aar' | ||
| 43 | +// // Add your description here | ||
| 44 | +// name 'Android TransitionHelper' | ||
| 45 | +// url siteUrl | ||
| 46 | +// // Set your license | ||
| 47 | +// licenses { | ||
| 48 | +// license { | ||
| 49 | +// name 'The Apache Software License, Version 2.0' | ||
| 50 | +// url 'http://www.apache.org/licenses/LICENSE-2.0.txt' | ||
| 51 | +// } | ||
| 52 | +// } | ||
| 53 | +// developers { | ||
| 54 | +// developer { | ||
| 55 | +// id 'immortalz' | ||
| 56 | +// name 'immortalz' | ||
| 57 | +// email 'immortalz.cn@gmail.com' | ||
| 58 | +// } | ||
| 59 | +// } | ||
| 60 | +// scm { | ||
| 61 | +// connection gitUrl | ||
| 62 | +// developerConnection gitUrl | ||
| 63 | +// url siteUrl | ||
| 64 | +// } | ||
| 65 | +// } | ||
| 66 | +// } | ||
| 67 | +// } | ||
| 68 | +//} | ||
| 69 | +//task sourcesJar(type: Jar) { | ||
| 70 | +// from android.sourceSets.main.java.srcDirs | ||
| 71 | +// classifier = 'sources' | ||
| 72 | +//} | ||
| 73 | +//task javadoc(type: Javadoc) { | ||
| 74 | +// source = android.sourceSets.main.java.srcDirs | ||
| 75 | +// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) | ||
| 76 | +//} | ||
| 77 | +//task javadocJar(type: Jar, dependsOn: javadoc) { | ||
| 78 | +// classifier = 'javadoc' | ||
| 79 | +// from javadoc.destinationDir | ||
| 80 | +//} | ||
| 81 | +//artifacts { | ||
| 82 | +// archives javadocJar | ||
| 83 | +// archives sourcesJar | ||
| 84 | +//} | ||
| 85 | +//Properties properties = new Properties() | ||
| 86 | +//properties.load(project.rootProject.file('local.properties').newDataInputStream()) | ||
| 87 | +//bintray { | ||
| 88 | +// user = properties.getProperty("bintray.user") | ||
| 89 | +// key = properties.getProperty("bintray.apikey") | ||
| 90 | +// configurations = ['archives'] | ||
| 91 | +// pkg { | ||
| 92 | +// userOrg = 'immortalzme' | ||
| 93 | +// repo = "maven" | ||
| 94 | +// name = "transitionhelper" | ||
| 95 | +// websiteUrl = siteUrl | ||
| 96 | +// vcsUrl = gitUrl | ||
| 97 | +// licenses = ["Apache-2.0"] | ||
| 98 | +// publish = true | ||
| 99 | +// } | ||
| 100 | +//} |
transitionhelper/proguard-rules.pro
0 → 100644
| 1 | +# Add project specific ProGuard rules here. | ||
| 2 | +# By default, the flags in this file are appended to flags specified | ||
| 3 | +# in H:\android_sdk/tools/proguard/proguard-android.txt | ||
| 4 | +# You can edit the include path and order by changing the proguardFiles | ||
| 5 | +# directive in build.gradle. | ||
| 6 | +# | ||
| 7 | +# For more details, see | ||
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | ||
| 9 | + | ||
| 10 | +# Add any project specific keep options here: | ||
| 11 | + | ||
| 12 | +# If your project uses WebView with JS, uncomment the following | ||
| 13 | +# and specify the fully qualified class name to the JavaScript interface | ||
| 14 | +# class: | ||
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| 16 | +# public *; | ||
| 17 | +#} |
| 1 | +package immortalz.me.library; | ||
| 2 | + | ||
| 3 | +import android.animation.Animator; | ||
| 4 | +import android.animation.AnimatorListenerAdapter; | ||
| 5 | +import android.app.Activity; | ||
| 6 | +import android.content.Intent; | ||
| 7 | +import android.graphics.Bitmap; | ||
| 8 | +import android.graphics.drawable.BitmapDrawable; | ||
| 9 | +import android.os.Build; | ||
| 10 | +import android.text.TextUtils; | ||
| 11 | +import android.view.View; | ||
| 12 | +import android.view.ViewGroup; | ||
| 13 | +import android.view.Window; | ||
| 14 | +import android.view.WindowManager; | ||
| 15 | +import android.widget.ImageView; | ||
| 16 | +import android.widget.RelativeLayout; | ||
| 17 | +import immortalz.me.library.bean.InfoBean; | ||
| 18 | +import immortalz.me.library.method.InflateShowMethod; | ||
| 19 | +import immortalz.me.library.method.NoneShowMethod; | ||
| 20 | +import immortalz.me.library.method.ShowMethod; | ||
| 21 | +import immortalz.me.library.view.CircleAnimView; | ||
| 22 | + | ||
| 23 | +import java.util.HashMap; | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + * Created by Mr_immortalZ on 2016/10/18. | ||
| 27 | + * email : mr_immortalz@qq.com | ||
| 28 | + */ | ||
| 29 | + | ||
| 30 | +public class TransitionsHelper { | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + private static TransitionsHelper INSTANCE; | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + private static HashMap<String, InfoBean> staticMap = new HashMap<>(); | ||
| 37 | + | ||
| 38 | + private static ShowMethod showMethod = new NoneShowMethod(); | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + private TransitionsHelper() { | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public static TransitionsHelper getInstance() { | ||
| 45 | + if (INSTANCE == null) { | ||
| 46 | + synchronized (TransitionsHelper.class) { | ||
| 47 | + if (INSTANCE == null) { | ||
| 48 | + INSTANCE = new TransitionsHelper(); | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + return INSTANCE; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + public static void startActivity(final Activity activity, final Class<?> cls, final View view) { | ||
| 56 | + startActivity(activity, cls, view, 0); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + public static void startActivity(final Activity activity, final Class<?> cls, final View view, final Integer imgId) { | ||
| 60 | + final Intent intent = new Intent(activity, cls); | ||
| 61 | + final InfoBean bean = new InfoBean(); | ||
| 62 | + view.post(new Runnable() { | ||
| 63 | + @Override | ||
| 64 | + public void run() { | ||
| 65 | + //get statusBar height | ||
| 66 | + view.getWindowVisibleDisplayFrame(bean.originRect); | ||
| 67 | + bean.statusBarHeight = bean.originRect.top; | ||
| 68 | + //get Origin View's rect | ||
| 69 | + view.getGlobalVisibleRect(bean.originRect); | ||
| 70 | + bean.originWidth = bean.originRect.right - bean.originRect.left; | ||
| 71 | + bean.originHeight = bean.originRect.bottom - bean.originRect.top; | ||
| 72 | + if (imgId == 0) { | ||
| 73 | + bean.bitmap = createBitmap(view, bean.originWidth, bean.originHeight); | ||
| 74 | + } else { | ||
| 75 | + bean.setImgId(imgId); | ||
| 76 | + } | ||
| 77 | + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); | ||
| 78 | + staticMap.put(cls.getName(), bean); | ||
| 79 | + activity.startActivity(intent); | ||
| 80 | + activity.overridePendingTransition(0, 0); | ||
| 81 | + } | ||
| 82 | + }); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + public static void startActivityForResult(final Activity activity, final Intent intent, | ||
| 86 | + final Class<?> cls, final View view, final int requestCode) { | ||
| 87 | + final InfoBean bean = new InfoBean(); | ||
| 88 | + view.post(new Runnable() { | ||
| 89 | + @Override | ||
| 90 | + public void run() { | ||
| 91 | + //get statusBar height | ||
| 92 | + view.getWindowVisibleDisplayFrame(bean.originRect); | ||
| 93 | + bean.statusBarHeight = bean.originRect.top; | ||
| 94 | + //get Origin View's rect | ||
| 95 | + view.getGlobalVisibleRect(bean.originRect); | ||
| 96 | + bean.originWidth = bean.originRect.right - bean.originRect.left; | ||
| 97 | + bean.originHeight = bean.originRect.bottom - bean.originRect.top; | ||
| 98 | + bean.bitmap = createBitmap(view, bean.originWidth, bean.originHeight); | ||
| 99 | + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); | ||
| 100 | + staticMap.put(cls.getName(), bean); | ||
| 101 | + activity.startActivityForResult(intent, requestCode); | ||
| 102 | + activity.overridePendingTransition(0, 0); | ||
| 103 | + } | ||
| 104 | + }); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + public static void startActivity(final Activity activity, final Class<?> cls, | ||
| 108 | + final View view, final String imgUrl) { | ||
| 109 | + final Intent intent = new Intent(activity, cls); | ||
| 110 | + final InfoBean bean = new InfoBean(); | ||
| 111 | + view.post(new Runnable() { | ||
| 112 | + @Override | ||
| 113 | + public void run() { | ||
| 114 | + //get statusBar height | ||
| 115 | + view.getWindowVisibleDisplayFrame(bean.originRect); | ||
| 116 | + bean.statusBarHeight = bean.originRect.top; | ||
| 117 | + //get Origin View's rect | ||
| 118 | + view.getGlobalVisibleRect(bean.originRect); | ||
| 119 | + bean.originWidth = bean.originRect.right - bean.originRect.left; | ||
| 120 | + bean.originHeight = bean.originRect.bottom - bean.originRect.top; | ||
| 121 | + if (TextUtils.isEmpty(imgUrl)) { | ||
| 122 | + bean.bitmap = createBitmap(view, bean.originWidth, bean.originHeight); | ||
| 123 | + } else { | ||
| 124 | + bean.setImgUrl(imgUrl); | ||
| 125 | + } | ||
| 126 | + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); | ||
| 127 | + staticMap.put(cls.getName(), bean); | ||
| 128 | + activity.startActivity(intent); | ||
| 129 | + activity.overridePendingTransition(0, 0); | ||
| 130 | + } | ||
| 131 | + }); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + public static void startActivityForResult(final Activity context, final Intent intent, | ||
| 135 | + final Class<?> cls, final int requestCode, | ||
| 136 | + final View view, final String imgUrl) { | ||
| 137 | + final InfoBean bean = new InfoBean(); | ||
| 138 | + view.post(new Runnable() { | ||
| 139 | + @Override | ||
| 140 | + public void run() { | ||
| 141 | + //get statusBar height | ||
| 142 | + view.getWindowVisibleDisplayFrame(bean.originRect); | ||
| 143 | + bean.statusBarHeight = bean.originRect.top; | ||
| 144 | + //get Origin View's rect | ||
| 145 | + view.getGlobalVisibleRect(bean.originRect); | ||
| 146 | + bean.originWidth = bean.originRect.right - bean.originRect.left; | ||
| 147 | + bean.originHeight = bean.originRect.bottom - bean.originRect.top; | ||
| 148 | + if (TextUtils.isEmpty(imgUrl)) { | ||
| 149 | + bean.bitmap = createBitmap(view, bean.originWidth, bean.originHeight); | ||
| 150 | + } else { | ||
| 151 | + bean.setImgUrl(imgUrl); | ||
| 152 | + } | ||
| 153 | + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); | ||
| 154 | + staticMap.put(cls.getName(), bean); | ||
| 155 | + context.startActivityForResult(intent, requestCode); | ||
| 156 | + context.overridePendingTransition(0, 0); | ||
| 157 | + } | ||
| 158 | + }); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + | ||
| 162 | + public void show(final Activity activity, final ImageView targetView) { | ||
| 163 | + final InfoBean bean = staticMap.get(activity.getClass().getName()); | ||
| 164 | + if (bean == null) { | ||
| 165 | + return; | ||
| 166 | + } | ||
| 167 | + final ViewGroup parent = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); | ||
| 168 | + //if TranslucentStatus is true , statusBarHeight = 0 | ||
| 169 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
| 170 | + if (activity.getWindow().getStatusBarColor() == 0 || | ||
| 171 | + (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS & activity.getWindow().getAttributes().flags) | ||
| 172 | + == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) { | ||
| 173 | + bean.statusBarHeight = 0; | ||
| 174 | + } | ||
| 175 | + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
| 176 | + if ((WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS & activity.getWindow().getAttributes().flags) | ||
| 177 | + == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) { | ||
| 178 | + bean.statusBarHeight = 0; | ||
| 179 | + } | ||
| 180 | + } | ||
| 181 | + parent.post(new Runnable() { | ||
| 182 | + @Override | ||
| 183 | + public void run() { | ||
| 184 | + bean.windowWidth = parent.getWidth(); | ||
| 185 | + bean.windowHeight = parent.getHeight(); | ||
| 186 | + bean.titleHeight = parent.getTop(); | ||
| 187 | + final CircleAnimView circleAnimView; | ||
| 188 | + if (showMethod instanceof InflateShowMethod) { | ||
| 189 | + circleAnimView = new CircleAnimView(activity, | ||
| 190 | + createBitmap(((InflateShowMethod) showMethod).inflateView, bean.windowWidth, bean.windowHeight)); | ||
| 191 | + } else { | ||
| 192 | + circleAnimView = new CircleAnimView(activity); | ||
| 193 | + } | ||
| 194 | + final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, | ||
| 195 | + ViewGroup.LayoutParams.MATCH_PARENT); | ||
| 196 | + parent.addView(circleAnimView, params); | ||
| 197 | + | ||
| 198 | + | ||
| 199 | + if (targetView != null) { | ||
| 200 | + //get Target View's position | ||
| 201 | + targetView.getGlobalVisibleRect(bean.targetRect); | ||
| 202 | + bean.targetWidth = bean.targetRect.right - bean.targetRect.left; | ||
| 203 | + bean.targetHeight = bean.targetRect.bottom - bean.targetRect.top; | ||
| 204 | + bean.translationX = bean.originRect.left + bean.originWidth / 2 - bean.targetRect.left - bean.targetWidth / 2; | ||
| 205 | + bean.translationY = bean.originRect.top + bean.originHeight / 2 - bean.targetRect.top - bean.targetHeight / 2; | ||
| 206 | + } else { | ||
| 207 | + bean.targetRect.left = bean.originRect.left; | ||
| 208 | + bean.targetRect.top = bean.originRect.top; | ||
| 209 | + bean.targetWidth = bean.originWidth; | ||
| 210 | + bean.targetHeight = bean.originHeight; | ||
| 211 | + bean.translationX = 0; | ||
| 212 | + bean.translationY = 0; | ||
| 213 | + } | ||
| 214 | + //create a temp ImageView to replace origin view | ||
| 215 | + final ImageView ivTemp = new ImageView(activity); | ||
| 216 | + if (bean.bitmap != null) { | ||
| 217 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | ||
| 218 | + ivTemp.setImageDrawable(new BitmapDrawable(bean.bitmap)); | ||
| 219 | + } else { | ||
| 220 | + ivTemp.setBackgroundDrawable(new BitmapDrawable(bean.bitmap)); | ||
| 221 | + } | ||
| 222 | + } | ||
| 223 | + RelativeLayout.LayoutParams ivTempParams = new RelativeLayout.LayoutParams(bean.targetWidth, | ||
| 224 | + bean.targetHeight); | ||
| 225 | + | ||
| 226 | + ivTempParams.setMargins(bean.originRect.left - (bean.targetWidth / 2 - bean.originWidth / 2) | ||
| 227 | + , bean.originRect.top - (parent.getTop() + bean.statusBarHeight) - (bean.targetHeight / 2 - bean.originHeight / 2), 0, 0); | ||
| 228 | + circleAnimView.addView(ivTemp, ivTempParams); | ||
| 229 | + ivTemp.setScaleX((float) bean.originHeight / bean.targetHeight); | ||
| 230 | + ivTemp.setScaleY((float) bean.originWidth / bean.targetWidth); | ||
| 231 | + showMethod.translate(bean, circleAnimView, ivTemp); | ||
| 232 | + showMethod.loadCopyView(bean, ivTemp); | ||
| 233 | + showMethod.set.addListener(new AnimatorListenerAdapter() { | ||
| 234 | + @Override | ||
| 235 | + public void onAnimationEnd(Animator animation) { | ||
| 236 | + circleAnimView.startCircleAnim(bean); | ||
| 237 | + showMethod.loadTargetView(bean, targetView); | ||
| 238 | + } | ||
| 239 | + }); | ||
| 240 | + | ||
| 241 | + } | ||
| 242 | + }); | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + | ||
| 246 | + public void showSelf(final Activity activity, final ImageView targetView, final Class<?> cls) { | ||
| 247 | + final InfoBean beanAdd = new InfoBean(); | ||
| 248 | + activity.getCurrentFocus().post(new Runnable() { | ||
| 249 | + @Override | ||
| 250 | + public void run() { | ||
| 251 | + //get statusBar height | ||
| 252 | + activity.getCurrentFocus().getWindowVisibleDisplayFrame(beanAdd.originRect); | ||
| 253 | + beanAdd.statusBarHeight = beanAdd.originRect.top; | ||
| 254 | + //get Origin View's rect | ||
| 255 | + activity.getCurrentFocus().getGlobalVisibleRect(beanAdd.originRect); | ||
| 256 | + beanAdd.originWidth = beanAdd.originRect.right - beanAdd.originRect.left; | ||
| 257 | + beanAdd.originHeight = beanAdd.originRect.bottom - beanAdd.originRect.top; | ||
| 258 | + beanAdd.bitmap = createBitmap(activity.getCurrentFocus(), beanAdd.originWidth, beanAdd.originHeight); | ||
| 259 | + staticMap.put(cls.getName(), beanAdd); | ||
| 260 | + } | ||
| 261 | + }); | ||
| 262 | + | ||
| 263 | + | ||
| 264 | + final InfoBean bean = staticMap.get(activity.getClass().getName()); | ||
| 265 | + if (bean == null) { | ||
| 266 | + return; | ||
| 267 | + } | ||
| 268 | + final ViewGroup parent = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); | ||
| 269 | + //if TranslucentStatus is true , statusBarHeight = 0 | ||
| 270 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
| 271 | + if (activity.getWindow().getStatusBarColor() == 0 || | ||
| 272 | + (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS & activity.getWindow().getAttributes().flags) | ||
| 273 | + == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) { | ||
| 274 | + bean.statusBarHeight = 0; | ||
| 275 | + } | ||
| 276 | + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
| 277 | + if ((WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS & activity.getWindow().getAttributes().flags) | ||
| 278 | + == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) { | ||
| 279 | + bean.statusBarHeight = 0; | ||
| 280 | + } | ||
| 281 | + } | ||
| 282 | + parent.post(new Runnable() { | ||
| 283 | + @Override | ||
| 284 | + public void run() { | ||
| 285 | + bean.windowWidth = parent.getWidth(); | ||
| 286 | + bean.windowHeight = parent.getHeight(); | ||
| 287 | + bean.titleHeight = parent.getTop(); | ||
| 288 | + final CircleAnimView circleAnimView; | ||
| 289 | + if (showMethod instanceof InflateShowMethod) { | ||
| 290 | + circleAnimView = new CircleAnimView(activity, | ||
| 291 | + createBitmap(((InflateShowMethod) showMethod).inflateView, bean.windowWidth, bean.windowHeight)); | ||
| 292 | + } else { | ||
| 293 | + circleAnimView = new CircleAnimView(activity); | ||
| 294 | + } | ||
| 295 | + final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, | ||
| 296 | + ViewGroup.LayoutParams.MATCH_PARENT); | ||
| 297 | + parent.addView(circleAnimView, params); | ||
| 298 | + | ||
| 299 | + | ||
| 300 | + if (targetView != null) { | ||
| 301 | + //get Target View's position | ||
| 302 | + targetView.getGlobalVisibleRect(bean.targetRect); | ||
| 303 | + bean.targetWidth = bean.targetRect.right - bean.targetRect.left; | ||
| 304 | + bean.targetHeight = bean.targetRect.bottom - bean.targetRect.top; | ||
| 305 | + bean.translationX = bean.originRect.left + bean.originWidth / 2 - bean.targetRect.left - bean.targetWidth / 2; | ||
| 306 | + bean.translationY = bean.originRect.top + bean.originHeight / 2 - bean.targetRect.top - bean.targetHeight / 2; | ||
| 307 | + } else { | ||
| 308 | + bean.targetRect.left = bean.originRect.left; | ||
| 309 | + bean.targetRect.top = bean.originRect.top; | ||
| 310 | + bean.targetWidth = bean.originWidth; | ||
| 311 | + bean.targetHeight = bean.originHeight; | ||
| 312 | + bean.translationX = 0; | ||
| 313 | + bean.translationY = 0; | ||
| 314 | + } | ||
| 315 | + //create a temp ImageView to replace origin view | ||
| 316 | + final ImageView ivTemp = new ImageView(activity); | ||
| 317 | + if (bean.bitmap != null) { | ||
| 318 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | ||
| 319 | + ivTemp.setImageDrawable(new BitmapDrawable(bean.bitmap)); | ||
| 320 | + } else { | ||
| 321 | + ivTemp.setBackgroundDrawable(new BitmapDrawable(bean.bitmap)); | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + RelativeLayout.LayoutParams ivTempParams = new RelativeLayout.LayoutParams(bean.targetWidth, | ||
| 325 | + bean.targetHeight); | ||
| 326 | + | ||
| 327 | + ivTempParams.setMargins(bean.originRect.left - (bean.targetWidth / 2 - bean.originWidth / 2) | ||
| 328 | + , bean.originRect.top - (parent.getTop() + bean.statusBarHeight) - (bean.targetHeight / 2 - bean.originHeight / 2), 0, 0); | ||
| 329 | + circleAnimView.addView(ivTemp, ivTempParams); | ||
| 330 | + ivTemp.setScaleX((float) bean.originHeight / bean.targetHeight); | ||
| 331 | + ivTemp.setScaleY((float) bean.originWidth / bean.targetWidth); | ||
| 332 | + showMethod.translate(bean, circleAnimView, ivTemp); | ||
| 333 | + showMethod.loadCopyView(bean, ivTemp); | ||
| 334 | + showMethod.set.addListener(new AnimatorListenerAdapter() { | ||
| 335 | + @Override | ||
| 336 | + public void onAnimationEnd(Animator animation) { | ||
| 337 | + circleAnimView.startCircleAnim(bean); | ||
| 338 | + showMethod.loadTargetView(bean, targetView); | ||
| 339 | + } | ||
| 340 | + }); | ||
| 341 | + | ||
| 342 | + } | ||
| 343 | + }); | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + | ||
| 347 | + public TransitionsHelper setShowMethod(ShowMethod showMethod) { | ||
| 348 | + TransitionsHelper.showMethod = showMethod; | ||
| 349 | + return this; | ||
| 350 | + } | ||
| 351 | + | ||
| 352 | + public static void unBind(Activity activity) { | ||
| 353 | + if (staticMap.get(activity.getClass().getName()) != null) { | ||
| 354 | + InfoBean bean = staticMap.get(activity.getClass().getName()); | ||
| 355 | + if (bean.bitmap != null) { | ||
| 356 | + bean.bitmap = null; | ||
| 357 | + } | ||
| 358 | + staticMap.remove(activity.getClass().getName()); | ||
| 359 | + } | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + private static Bitmap createBitmap(View view, int width, int height) { | ||
| 363 | + view.setDrawingCacheEnabled(true); | ||
| 364 | + view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), | ||
| 365 | + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); | ||
| 366 | + view.buildDrawingCache(); | ||
| 367 | + return view.getDrawingCache(); | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | +} |
| 1 | +package immortalz.me.library.bean; | ||
| 2 | + | ||
| 3 | +import android.graphics.Bitmap; | ||
| 4 | +import android.graphics.Rect; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * Created by Mr_immortalZ on 2016/10/18. | ||
| 8 | + * email : mr_immortalz@qq.com | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +public class InfoBean { | ||
| 12 | + public int statusBarHeight; | ||
| 13 | + public int titleHeight; | ||
| 14 | + //image's url or resource id | ||
| 15 | + private String imgUrl; | ||
| 16 | + private int imgId; | ||
| 17 | + public Bitmap bitmap; | ||
| 18 | + | ||
| 19 | + public int translationY; | ||
| 20 | + public int translationX; | ||
| 21 | + //origin view | ||
| 22 | + public Rect originRect = new Rect(); | ||
| 23 | + public int originWidth; | ||
| 24 | + public int originHeight; | ||
| 25 | + //target view | ||
| 26 | + public Rect targetRect = new Rect(); | ||
| 27 | + public int targetWidth; | ||
| 28 | + public int targetHeight; | ||
| 29 | + //Content Window's size | ||
| 30 | + public int windowWidth; | ||
| 31 | + public int windowHeight; | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + public String getImgUrl() { | ||
| 35 | + return imgUrl; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + public void setImgUrl(String imgUrl) { | ||
| 39 | + this.imgUrl = imgUrl; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + public int getImgId() { | ||
| 43 | + return imgId; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + public void setImgId(int imgId) { | ||
| 47 | + this.imgId = imgId; | ||
| 48 | + } | ||
| 49 | +} |
| 1 | +package immortalz.me.library.method; | ||
| 2 | + | ||
| 3 | +import android.animation.ArgbEvaluator; | ||
| 4 | +import android.animation.ObjectAnimator; | ||
| 5 | +import android.view.View; | ||
| 6 | +import android.view.animation.AccelerateInterpolator; | ||
| 7 | + | ||
| 8 | +import immortalz.me.library.R; | ||
| 9 | +import immortalz.me.library.bean.InfoBean; | ||
| 10 | +import immortalz.me.library.view.RenderView; | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Created by Mr_immortalZ on 2016/10/24. | ||
| 15 | + * email : mr_immortalz@qq.com | ||
| 16 | + */ | ||
| 17 | + | ||
| 18 | +public abstract class ColorShowMethod extends ShowMethod { | ||
| 19 | + | ||
| 20 | + protected int startColor; | ||
| 21 | + public int endColor; | ||
| 22 | + | ||
| 23 | + public ColorShowMethod(int startColor, int endColor) { | ||
| 24 | + this.startColor = startColor; | ||
| 25 | + this.endColor = endColor; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public void translate(InfoBean bean, RenderView parent, View child) { | ||
| 30 | + if (startColor != 0) { | ||
| 31 | + startColor = parent.getResources().getColor(startColor); | ||
| 32 | + } else { | ||
| 33 | + startColor = parent.getResources().getColor(R.color.showmethod_start_color); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + if (endColor != 0) { | ||
| 37 | + endColor = parent.getResources().getColor(endColor); | ||
| 38 | + } else { | ||
| 39 | + endColor = parent.getResources().getColor(R.color.showmethod_end_color); | ||
| 40 | + } | ||
| 41 | + parent.setPaintColor(endColor); | ||
| 42 | + ObjectAnimator colorAnimator = ObjectAnimator.ofInt(parent, "backgroundColor", startColor, endColor); | ||
| 43 | + colorAnimator.setEvaluator(new ArgbEvaluator()); | ||
| 44 | + set.playTogether( | ||
| 45 | + ObjectAnimator.ofFloat(child, "translationX", 0, -bean.translationX), | ||
| 46 | + ObjectAnimator.ofFloat(child, "translationY", 0, -bean.translationY), | ||
| 47 | + ObjectAnimator.ofFloat(child, "scaleX", 1), | ||
| 48 | + ObjectAnimator.ofFloat(child, "scaleY", 1), | ||
| 49 | + colorAnimator | ||
| 50 | + ); | ||
| 51 | + set.setInterpolator(new AccelerateInterpolator()); | ||
| 52 | + set.setDuration(duration).start(); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + | ||
| 56 | +} |
| 1 | +package immortalz.me.library.method; | ||
| 2 | + | ||
| 3 | +import android.animation.ObjectAnimator; | ||
| 4 | +import android.app.Activity; | ||
| 5 | +import android.view.LayoutInflater; | ||
| 6 | +import android.view.View; | ||
| 7 | +import android.view.animation.AccelerateInterpolator; | ||
| 8 | + | ||
| 9 | +import immortalz.me.library.bean.InfoBean; | ||
| 10 | +import immortalz.me.library.view.RenderView; | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Created by Mr_immortalZ on 2016/10/24. | ||
| 15 | + * email : mr_immortalz@qq.com | ||
| 16 | + */ | ||
| 17 | + | ||
| 18 | +public abstract class InflateShowMethod extends ShowMethod { | ||
| 19 | + | ||
| 20 | + public View inflateView; | ||
| 21 | + | ||
| 22 | + public InflateShowMethod(Activity activity, int layoutId) { | ||
| 23 | + this.inflateView = LayoutInflater.from(activity).inflate(layoutId, null); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + @Override | ||
| 27 | + public void translate(InfoBean bean, RenderView parent, View child) { | ||
| 28 | + set.playTogether( | ||
| 29 | + ObjectAnimator.ofFloat(child, "translationX", 0, -bean.translationX), | ||
| 30 | + ObjectAnimator.ofFloat(child, "translationY", 0, -bean.translationY), | ||
| 31 | + ObjectAnimator.ofFloat(child, "scaleX", 1), | ||
| 32 | + ObjectAnimator.ofFloat(child, "scaleY", 1) | ||
| 33 | + ); | ||
| 34 | + set.setInterpolator(new AccelerateInterpolator()); | ||
| 35 | + set.setDuration(duration).start(); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | +} |
| 1 | +package immortalz.me.library.method; | ||
| 2 | + | ||
| 3 | +import android.animation.AnimatorSet; | ||
| 4 | +import android.animation.ArgbEvaluator; | ||
| 5 | +import android.animation.ObjectAnimator; | ||
| 6 | +import android.view.View; | ||
| 7 | +import android.view.animation.AccelerateInterpolator; | ||
| 8 | +import android.widget.ImageView; | ||
| 9 | + | ||
| 10 | +import immortalz.me.library.R; | ||
| 11 | +import immortalz.me.library.bean.InfoBean; | ||
| 12 | +import immortalz.me.library.view.RenderView; | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * Created by Mr_immortalZ on 2016/10/24. | ||
| 17 | + * email : mr_immortalz@qq.com | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | +public class NoneShowMethod extends ShowMethod { | ||
| 21 | + | ||
| 22 | + protected int startColor; | ||
| 23 | + protected int endColor; | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + public void translate(InfoBean bean, RenderView parent, View child) { | ||
| 27 | + startColor = parent.getResources().getColor(R.color.showmethod_start_color); | ||
| 28 | + endColor = parent.getResources().getColor(R.color.showmethod_end_color); | ||
| 29 | + ObjectAnimator colorAnimator = ObjectAnimator.ofInt(parent, "backgroundColor", startColor, endColor); | ||
| 30 | + colorAnimator.setEvaluator(new ArgbEvaluator()); | ||
| 31 | + set.setInterpolator(new AccelerateInterpolator()); | ||
| 32 | + set.setDuration(duration).start(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public void loadCopyView(InfoBean bean, ImageView copyView) { | ||
| 37 | + AnimatorSet set = new AnimatorSet(); | ||
| 38 | + set.playTogether( | ||
| 39 | + ObjectAnimator.ofFloat(copyView,"rotation",0,90), | ||
| 40 | + ObjectAnimator.ofFloat(copyView, "scaleX", 1, 0), | ||
| 41 | + ObjectAnimator.ofFloat(copyView, "scaleY", 1, 0) | ||
| 42 | + ); | ||
| 43 | + set.setInterpolator(new AccelerateInterpolator()); | ||
| 44 | + set.setDuration(duration / 4 * 5).start(); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + @Override | ||
| 48 | + public void loadTargetView(InfoBean bean, ImageView targetView) { | ||
| 49 | + | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + | ||
| 53 | +} |
| 1 | +package immortalz.me.library.method; | ||
| 2 | + | ||
| 3 | +import android.animation.AnimatorSet; | ||
| 4 | +import android.view.View; | ||
| 5 | +import android.widget.ImageView; | ||
| 6 | + | ||
| 7 | +import immortalz.me.library.bean.InfoBean; | ||
| 8 | +import immortalz.me.library.view.RenderView; | ||
| 9 | + | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Created by Mr_immortalZ on 2016/10/24. | ||
| 13 | + * email : mr_immortalz@qq.com | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +public abstract class ShowMethod { | ||
| 17 | + | ||
| 18 | + public AnimatorSet set = new AnimatorSet(); | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + protected int duration = 500; | ||
| 22 | + | ||
| 23 | + public abstract void translate(InfoBean bean, RenderView parent, View child); | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * load copyView which just a temp view. | ||
| 27 | + * the copyView is show when it's translating. | ||
| 28 | + */ | ||
| 29 | + public abstract void loadCopyView(InfoBean bean,ImageView copyView); | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * load targetView | ||
| 33 | + */ | ||
| 34 | + public void loadTargetView(InfoBean bean, ImageView targetView){ | ||
| 35 | + | ||
| 36 | + } | ||
| 37 | +} |
| 1 | +package immortalz.me.library.view; | ||
| 2 | + | ||
| 3 | +import android.content.Context; | ||
| 4 | +import android.graphics.Bitmap; | ||
| 5 | +import android.graphics.Canvas; | ||
| 6 | +import android.graphics.Paint; | ||
| 7 | +import android.graphics.PorterDuff; | ||
| 8 | +import android.graphics.PorterDuffXfermode; | ||
| 9 | +import android.graphics.RectF; | ||
| 10 | +import android.graphics.Xfermode; | ||
| 11 | +import android.util.AttributeSet; | ||
| 12 | + | ||
| 13 | +import immortalz.me.library.R; | ||
| 14 | +import immortalz.me.library.bean.InfoBean; | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * Created by Mr_immortalZ on 2016/10/13. | ||
| 19 | + * email : mr_immortalz@qq.com | ||
| 20 | + */ | ||
| 21 | + | ||
| 22 | +public class CircleAnimView extends RenderView{ | ||
| 23 | + | ||
| 24 | + private Canvas mCanvas; | ||
| 25 | + private Bitmap originalBitmap; // 画布bitmap | ||
| 26 | + private Bitmap drawBitmap; | ||
| 27 | + private Xfermode xfermode; | ||
| 28 | + //画布宽度 | ||
| 29 | + private int canvaswWidth = 1080; | ||
| 30 | + //画布高度 | ||
| 31 | + private int canvasHeight = 1920; | ||
| 32 | + //画圆 | ||
| 33 | + private int cX; | ||
| 34 | + private int cY; | ||
| 35 | + private int radius; | ||
| 36 | + private int maxRadius; | ||
| 37 | + private int increaseSpeed = 5; | ||
| 38 | + boolean startCircleAnim = false; | ||
| 39 | + | ||
| 40 | + public CircleAnimView(Context context) { | ||
| 41 | + super(context); | ||
| 42 | + init(context); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public CircleAnimView(Context context, Bitmap bitmap) { | ||
| 46 | + super(context); | ||
| 47 | + drawBitmap = bitmap; | ||
| 48 | + init(context); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + public CircleAnimView(Context context, AttributeSet attrs) { | ||
| 52 | + super(context, attrs); | ||
| 53 | + init(context); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public CircleAnimView(Context context, AttributeSet attrs, int defStyleAttr) { | ||
| 57 | + super(context, attrs, defStyleAttr); | ||
| 58 | + init(context); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + private void init(Context context) { | ||
| 62 | + mPaint = new Paint(); | ||
| 63 | + mPaint.setStyle(Paint.Style.FILL); | ||
| 64 | + mPaint.setAntiAlias(true); | ||
| 65 | + mPaint.setColor(getResources().getColor(R.color.showmethod_end_color)); | ||
| 66 | + | ||
| 67 | + xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); | ||
| 68 | + | ||
| 69 | + setWillNotDraw(false); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + @Override | ||
| 73 | + public void startCircleAnim(InfoBean bean) { | ||
| 74 | + originalBitmap = Bitmap.createBitmap(bean.windowWidth, bean.windowHeight, Bitmap.Config.ARGB_4444); | ||
| 75 | + canvaswWidth = bean.windowWidth; | ||
| 76 | + canvasHeight = bean.windowHeight; | ||
| 77 | + mCanvas = new Canvas(originalBitmap); | ||
| 78 | + startCircleAnim = true; | ||
| 79 | + cX = bean.targetWidth / 2 + bean.targetRect.left; | ||
| 80 | + cY = bean.targetHeight / 2 + bean.targetRect.top - bean.statusBarHeight - bean.titleHeight; | ||
| 81 | + radius = (int) Math.hypot(bean.targetWidth / 2, bean.targetHeight / 2) / 4; | ||
| 82 | + maxRadius = (int) Math.hypot(canvaswWidth, canvasHeight); | ||
| 83 | + setBackgroundColor(getResources().getColor(android.R.color.transparent)); | ||
| 84 | + invalidate(); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Override | ||
| 88 | + protected void onDraw(Canvas canvas) { | ||
| 89 | + if (startCircleAnim) { | ||
| 90 | + zoomDraw(canvas); | ||
| 91 | + } else if (drawBitmap != null) { | ||
| 92 | + canvas.drawBitmap(drawBitmap, 0, 0, null); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + private void zoomDraw(Canvas canvas) { | ||
| 97 | + if (radius < maxRadius) { | ||
| 98 | + if (drawBitmap != null) { | ||
| 99 | + mCanvas.drawBitmap(drawBitmap, null, new RectF(0, 0, canvaswWidth, canvasHeight), null); | ||
| 100 | + } else { | ||
| 101 | + mCanvas.drawRect(0, 0, canvaswWidth, canvasHeight, mPaint); | ||
| 102 | + } | ||
| 103 | + mPaint.setXfermode(xfermode); | ||
| 104 | + mCanvas.drawCircle(cX, cY, radius, mPaint); | ||
| 105 | + radius += increaseSpeed; | ||
| 106 | + increaseSpeed += 6; | ||
| 107 | + mPaint.setXfermode(null); | ||
| 108 | + canvas.drawBitmap(originalBitmap, 0, 0, null); | ||
| 109 | + invalidate(); | ||
| 110 | + } else { | ||
| 111 | + startCircleAnim = false; | ||
| 112 | + setVisibility(GONE); | ||
| 113 | + //recycle | ||
| 114 | + originalBitmap = null; | ||
| 115 | + drawBitmap = null; | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | +} |
| 1 | +package immortalz.me.library.view; | ||
| 2 | + | ||
| 3 | +import android.content.Context; | ||
| 4 | +import android.graphics.Paint; | ||
| 5 | +import android.util.AttributeSet; | ||
| 6 | +import android.widget.RelativeLayout; | ||
| 7 | + | ||
| 8 | +import immortalz.me.library.bean.InfoBean; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * Created by Mr_immortalZ on 2016/10/29. | ||
| 12 | + * email : mr_immortalz@qq.com | ||
| 13 | + */ | ||
| 14 | + | ||
| 15 | +public abstract class RenderView extends RelativeLayout { | ||
| 16 | + protected int paintColor; | ||
| 17 | + protected Paint mPaint; | ||
| 18 | + | ||
| 19 | + public RenderView(Context context) { | ||
| 20 | + super(context); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public RenderView(Context context, AttributeSet attrs) { | ||
| 24 | + super(context, attrs); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + public RenderView(Context context, AttributeSet attrs, int defStyleAttr) { | ||
| 28 | + super(context, attrs, defStyleAttr); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public abstract void startCircleAnim(InfoBean bean); | ||
| 32 | + | ||
| 33 | + public void setPaintColor(int paintColor) { | ||
| 34 | + this.paintColor = paintColor; | ||
| 35 | + mPaint.setColor(paintColor); | ||
| 36 | + } | ||
| 37 | +} |
volley/.gitignore
0 → 100644
volley/build.gradle
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (c) 2016. wugian | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + * | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +apply plugin: 'com.android.library' | ||
| 18 | + | ||
| 19 | +android { | ||
| 20 | + compileSdkVersion 18 | ||
| 21 | + buildToolsVersion "25.0.0" | ||
| 22 | + | ||
| 23 | + defaultConfig { | ||
| 24 | + minSdkVersion 18 | ||
| 25 | + targetSdkVersion 25 | ||
| 26 | + versionCode 1 | ||
| 27 | + versionName "1.0" | ||
| 28 | + | ||
| 29 | + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||
| 30 | + | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + lintOptions { | ||
| 34 | + abortOnError false | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + buildTypes { | ||
| 38 | + release { | ||
| 39 | + minifyEnabled false | ||
| 40 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +dependencies { | ||
| 46 | + compile fileTree(include: ['*.jar'], dir: 'libs') | ||
| 47 | + provided 'com.google.code.gson:gson:2.4' | ||
| 48 | +} |
volley/proguard-rules.pro
0 → 100644
| 1 | +# Add project specific ProGuard rules here. | ||
| 2 | +# By default, the flags in this file are appended to flags specified | ||
| 3 | +# in D:\develope-file\android-sdk/tools/proguard/proguard-android.txt | ||
| 4 | +# You can edit the include path and order by changing the proguardFiles | ||
| 5 | +# directive in build.gradle. | ||
| 6 | +# | ||
| 7 | +# For more details, see | ||
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | ||
| 9 | + | ||
| 10 | +# Add any project specific keep options here: | ||
| 11 | + | ||
| 12 | +# If your project uses WebView with JS, uncomment the following | ||
| 13 | +# and specify the fully qualified class name to the JavaScript interface | ||
| 14 | +# class: | ||
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| 16 | +# public *; | ||
| 17 | +#} |
volley/src/main/AndroidManifest.xml
0 → 100644
| 1 | +<!-- | ||
| 2 | + ~ Copyright (c) 2016. wugian | ||
| 3 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + ~ you may not use this file except in compliance with the License. | ||
| 5 | + ~ You may obtain a copy of the License at | ||
| 6 | + ~ | ||
| 7 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + ~ | ||
| 9 | + ~ Unless required by applicable law or agreed to in writing, software | ||
| 10 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + ~ See the License for the specific language governing permissions and | ||
| 13 | + ~ limitations under the License. | ||
| 14 | + ~ | ||
| 15 | + --> | ||
| 16 | + | ||
| 17 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | + package="com.android.volley"> | ||
| 19 | + | ||
| 20 | + <application android:allowBackup="true" | ||
| 21 | + android:supportsRtl="true" | ||
| 22 | + > | ||
| 23 | + | ||
| 24 | + </application> | ||
| 25 | + | ||
| 26 | +</manifest> |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.content.Intent; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * Error indicating that there was an authentication failure when performing a Request. | ||
| 23 | + */ | ||
| 24 | +@SuppressWarnings("serial") | ||
| 25 | +public class AuthFailureError extends VolleyError { | ||
| 26 | + /** An intent that can be used to resolve this exception. (Brings up the password dialog.) */ | ||
| 27 | + private Intent mResolutionIntent; | ||
| 28 | + | ||
| 29 | + public AuthFailureError() { } | ||
| 30 | + | ||
| 31 | + public AuthFailureError(Intent intent) { | ||
| 32 | + mResolutionIntent = intent; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public AuthFailureError(NetworkResponse response) { | ||
| 36 | + super(response); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public AuthFailureError(String message) { | ||
| 40 | + super(message); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public AuthFailureError(String message, Exception reason) { | ||
| 44 | + super(message, reason); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + public Intent getResolutionIntent() { | ||
| 48 | + return mResolutionIntent; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + @Override | ||
| 52 | + public String getMessage() { | ||
| 53 | + if (mResolutionIntent != null) { | ||
| 54 | + return "User needs to (re)enter credentials."; | ||
| 55 | + } | ||
| 56 | + return super.getMessage(); | ||
| 57 | + } | ||
| 58 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import java.util.Collections; | ||
| 20 | +import java.util.Map; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * An interface for a cache keyed by a String with a byte array as data. | ||
| 24 | + */ | ||
| 25 | +public interface Cache { | ||
| 26 | + /** | ||
| 27 | + * Retrieves an entry from the cache. | ||
| 28 | + * @param key Cache key | ||
| 29 | + * @return An {@link Entry} or null in the event of a cache miss | ||
| 30 | + */ | ||
| 31 | + public Entry get(String key); | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Adds or replaces an entry to the cache. | ||
| 35 | + * @param key Cache key | ||
| 36 | + * @param entry Data to store and metadata for cache coherency, TTL, etc. | ||
| 37 | + */ | ||
| 38 | + public void put(String key, Entry entry); | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * Performs any potentially long-running actions needed to initialize the cache; | ||
| 42 | + * will be called from a worker thread. | ||
| 43 | + */ | ||
| 44 | + public void initialize(); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Invalidates an entry in the cache. | ||
| 48 | + * @param key Cache key | ||
| 49 | + * @param fullExpire True to fully expire the entry, false to soft expire | ||
| 50 | + */ | ||
| 51 | + public void invalidate(String key, boolean fullExpire); | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * Removes an entry from the cache. | ||
| 55 | + * @param key Cache key | ||
| 56 | + */ | ||
| 57 | + public void remove(String key); | ||
| 58 | + | ||
| 59 | + /** | ||
| 60 | + * Empties the cache. | ||
| 61 | + */ | ||
| 62 | + public void clear(); | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Data and metadata for an entry returned by the cache. | ||
| 66 | + */ | ||
| 67 | + public static class Entry { | ||
| 68 | + /** The data returned from cache. */ | ||
| 69 | + public byte[] data; | ||
| 70 | + | ||
| 71 | + /** ETag for cache coherency. */ | ||
| 72 | + public String etag; | ||
| 73 | + | ||
| 74 | + /** Date of this response as reported by the server. */ | ||
| 75 | + public long serverDate; | ||
| 76 | + | ||
| 77 | + /** TTL for this record. */ | ||
| 78 | + public long ttl; | ||
| 79 | + | ||
| 80 | + /** Soft TTL for this record. */ | ||
| 81 | + public long softTtl; | ||
| 82 | + | ||
| 83 | + /** Immutable response headers as received from server; must be non-null. */ | ||
| 84 | + public Map<String, String> responseHeaders = Collections.emptyMap(); | ||
| 85 | + | ||
| 86 | + /** True if the entry is expired. */ | ||
| 87 | + public boolean isExpired() { | ||
| 88 | + return this.ttl < System.currentTimeMillis(); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + /** True if a refresh is needed from the original data source. */ | ||
| 92 | + public boolean refreshNeeded() { | ||
| 93 | + return this.softTtl < System.currentTimeMillis(); | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.os.Process; | ||
| 20 | + | ||
| 21 | +import java.util.concurrent.BlockingQueue; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * Provides a thread for performing cache triage on a queue of requests. | ||
| 25 | + * | ||
| 26 | + * Requests added to the specified cache queue are resolved from cache. | ||
| 27 | + * Any deliverable response is posted back to the caller via a | ||
| 28 | + * {@link ResponseDelivery}. Cache misses and responses that require | ||
| 29 | + * refresh are enqueued on the specified network queue for processing | ||
| 30 | + * by a {@link NetworkDispatcher}. | ||
| 31 | + */ | ||
| 32 | +@SuppressWarnings("rawtypes") | ||
| 33 | +public class CacheDispatcher extends Thread { | ||
| 34 | + | ||
| 35 | + private static final boolean DEBUG = VolleyLog.DEBUG; | ||
| 36 | + | ||
| 37 | + /** The queue of requests coming in for triage. */ | ||
| 38 | + private final BlockingQueue<Request> mCacheQueue; | ||
| 39 | + | ||
| 40 | + /** The queue of requests going out to the network. */ | ||
| 41 | + private final BlockingQueue<Request> mNetworkQueue; | ||
| 42 | + | ||
| 43 | + /** The cache to read from. */ | ||
| 44 | + private final Cache mCache; | ||
| 45 | + | ||
| 46 | + /** For posting responses. */ | ||
| 47 | + private final ResponseDelivery mDelivery; | ||
| 48 | + | ||
| 49 | + /** Used for telling us to die. */ | ||
| 50 | + private volatile boolean mQuit = false; | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * Creates a new cache triage dispatcher thread. You must call {@link #start()} | ||
| 54 | + * in order to begin processing. | ||
| 55 | + * | ||
| 56 | + * @param cacheQueue Queue of incoming requests for triage | ||
| 57 | + * @param networkQueue Queue to post requests that require network to | ||
| 58 | + * @param cache Cache interface to use for resolution | ||
| 59 | + * @param delivery Delivery interface to use for posting responses | ||
| 60 | + */ | ||
| 61 | + public CacheDispatcher( | ||
| 62 | + BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue, | ||
| 63 | + Cache cache, ResponseDelivery delivery) { | ||
| 64 | + mCacheQueue = cacheQueue; | ||
| 65 | + mNetworkQueue = networkQueue; | ||
| 66 | + mCache = cache; | ||
| 67 | + mDelivery = delivery; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * Forces this dispatcher to quit immediately. If any requests are still in | ||
| 72 | + * the queue, they are not guaranteed to be processed. | ||
| 73 | + */ | ||
| 74 | + public void quit() { | ||
| 75 | + mQuit = true; | ||
| 76 | + interrupt(); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + @Override | ||
| 80 | + public void run() { | ||
| 81 | + if (DEBUG) VolleyLog.v("start new dispatcher"); | ||
| 82 | + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); | ||
| 83 | + | ||
| 84 | + // Make a blocking call to initialize the cache. | ||
| 85 | + mCache.initialize(); | ||
| 86 | + | ||
| 87 | + while (true) { | ||
| 88 | + try { | ||
| 89 | + // Get a request from the cache triage queue, blocking until | ||
| 90 | + // at least one is available. | ||
| 91 | + final Request request = mCacheQueue.take(); | ||
| 92 | + request.addMarker("cache-queue-take"); | ||
| 93 | + | ||
| 94 | + // If the request has been canceled, don't bother dispatching it. | ||
| 95 | + if (request.isCanceled()) { | ||
| 96 | + request.finish("cache-discard-canceled"); | ||
| 97 | + continue; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + // Attempt to retrieve this item from cache. | ||
| 101 | + Cache.Entry entry = mCache.get(request.getCacheKey()); | ||
| 102 | + if (entry == null) { | ||
| 103 | + request.addMarker("cache-miss"); | ||
| 104 | + // Cache miss; send off to the network dispatcher. | ||
| 105 | + mNetworkQueue.put(request); | ||
| 106 | + continue; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // If it is completely expired, just send it to the network. | ||
| 110 | + if (entry.isExpired()) { | ||
| 111 | + request.addMarker("cache-hit-expired"); | ||
| 112 | + request.setCacheEntry(entry); | ||
| 113 | + mNetworkQueue.put(request); | ||
| 114 | + continue; | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + // We have a cache hit; parse its data for delivery back to the request. | ||
| 118 | + request.addMarker("cache-hit"); | ||
| 119 | + Response<?> response = request.parseNetworkResponse( | ||
| 120 | + new NetworkResponse(entry.data, entry.responseHeaders)); | ||
| 121 | + request.addMarker("cache-hit-parsed"); | ||
| 122 | + | ||
| 123 | + if (!entry.refreshNeeded()) { | ||
| 124 | + // Completely unexpired cache hit. Just deliver the response. | ||
| 125 | + mDelivery.postResponse(request, response); | ||
| 126 | + } else { | ||
| 127 | + // Soft-expired cache hit. We can deliver the cached response, | ||
| 128 | + // but we need to also send the request to the network for | ||
| 129 | + // refreshing. | ||
| 130 | + request.addMarker("cache-hit-refresh-needed"); | ||
| 131 | + request.setCacheEntry(entry); | ||
| 132 | + | ||
| 133 | + // Mark the response as intermediate. | ||
| 134 | + response.intermediate = true; | ||
| 135 | + | ||
| 136 | + // Post the intermediate response back to the user and have | ||
| 137 | + // the delivery then forward the request along to the network. | ||
| 138 | + mDelivery.postResponse(request, response, new Runnable() { | ||
| 139 | + @Override | ||
| 140 | + public void run() { | ||
| 141 | + try { | ||
| 142 | + mNetworkQueue.put(request); | ||
| 143 | + } catch (InterruptedException e) { | ||
| 144 | + // Not much we can do about this. | ||
| 145 | + } | ||
| 146 | + } | ||
| 147 | + }); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + } catch (InterruptedException e) { | ||
| 151 | + // We may have been interrupted because it was time to quit. | ||
| 152 | + if (mQuit) { | ||
| 153 | + return; | ||
| 154 | + } | ||
| 155 | + continue; | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Default retry policy for requests. | ||
| 21 | + */ | ||
| 22 | +public class DefaultRetryPolicy implements RetryPolicy { | ||
| 23 | + /** The current timeout in milliseconds. */ | ||
| 24 | + private int mCurrentTimeoutMs; | ||
| 25 | + | ||
| 26 | + /** The current retry count. */ | ||
| 27 | + private int mCurrentRetryCount; | ||
| 28 | + | ||
| 29 | + /** The maximum number of attempts. */ | ||
| 30 | + private final int mMaxNumRetries; | ||
| 31 | + | ||
| 32 | + /** The backoff multiplier for for the policy. */ | ||
| 33 | + private final float mBackoffMultiplier; | ||
| 34 | + | ||
| 35 | + /** The default socket timeout in milliseconds */ | ||
| 36 | + public static final int DEFAULT_TIMEOUT_MS = 2500; | ||
| 37 | + | ||
| 38 | + /** The default number of retries */ | ||
| 39 | + public static final int DEFAULT_MAX_RETRIES = 1; | ||
| 40 | + | ||
| 41 | + /** The default backoff multiplier */ | ||
| 42 | + public static final float DEFAULT_BACKOFF_MULT = 1f; | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Constructs a new retry policy using the default timeouts. | ||
| 46 | + */ | ||
| 47 | + public DefaultRetryPolicy() { | ||
| 48 | + this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * Constructs a new retry policy. | ||
| 53 | + * @param initialTimeoutMs The initial timeout for the policy. | ||
| 54 | + * @param maxNumRetries The maximum number of retries. | ||
| 55 | + * @param backoffMultiplier Backoff multiplier for the policy. | ||
| 56 | + */ | ||
| 57 | + public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { | ||
| 58 | + mCurrentTimeoutMs = initialTimeoutMs; | ||
| 59 | + mMaxNumRetries = maxNumRetries; | ||
| 60 | + mBackoffMultiplier = backoffMultiplier; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * Returns the current timeout. | ||
| 65 | + */ | ||
| 66 | + @Override | ||
| 67 | + public int getCurrentTimeout() { | ||
| 68 | + return mCurrentTimeoutMs; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * Returns the current retry count. | ||
| 73 | + */ | ||
| 74 | + @Override | ||
| 75 | + public int getCurrentRetryCount() { | ||
| 76 | + return mCurrentRetryCount; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Prepares for the next retry by applying a backoff to the timeout. | ||
| 81 | + * @param error The error code of the last attempt. | ||
| 82 | + */ | ||
| 83 | + @Override | ||
| 84 | + public void retry(VolleyError error) throws VolleyError { | ||
| 85 | + mCurrentRetryCount++; | ||
| 86 | + mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); | ||
| 87 | + if (!hasAttemptRemaining()) { | ||
| 88 | + throw error; | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + /** | ||
| 93 | + * Returns true if this policy has attempts remaining, false otherwise. | ||
| 94 | + */ | ||
| 95 | + protected boolean hasAttemptRemaining() { | ||
| 96 | + return mCurrentRetryCount <= mMaxNumRetries; | ||
| 97 | + } | ||
| 98 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.os.Handler; | ||
| 20 | + | ||
| 21 | +import java.util.concurrent.Executor; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * Delivers responses and errors. | ||
| 25 | + */ | ||
| 26 | +public class ExecutorDelivery implements ResponseDelivery { | ||
| 27 | + /** Used for posting responses, typically to the main thread. */ | ||
| 28 | + private final Executor mResponsePoster; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * Creates a new response delivery interface. | ||
| 32 | + * @param handler {@link Handler} to post responses on | ||
| 33 | + */ | ||
| 34 | + public ExecutorDelivery(final Handler handler) { | ||
| 35 | + // Make an Executor that just wraps the handler. | ||
| 36 | + mResponsePoster = new Executor() { | ||
| 37 | + @Override | ||
| 38 | + public void execute(Runnable command) { | ||
| 39 | + handler.post(command); | ||
| 40 | + } | ||
| 41 | + }; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Creates a new response delivery interface, mockable version | ||
| 46 | + * for testing. | ||
| 47 | + * @param executor For running delivery tasks | ||
| 48 | + */ | ||
| 49 | + public ExecutorDelivery(Executor executor) { | ||
| 50 | + mResponsePoster = executor; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + @Override | ||
| 54 | + public void postResponse(Request<?> request, Response<?> response) { | ||
| 55 | + postResponse(request, response, null); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { | ||
| 60 | + request.markDelivered(); | ||
| 61 | + request.addMarker("post-response"); | ||
| 62 | + mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + @Override | ||
| 66 | + public void postError(Request<?> request, VolleyError error) { | ||
| 67 | + request.addMarker("post-error"); | ||
| 68 | + Response<?> response = Response.error(error); | ||
| 69 | + mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * A Runnable used for delivering network responses to a listener on the | ||
| 74 | + * main thread. | ||
| 75 | + */ | ||
| 76 | + @SuppressWarnings("rawtypes") | ||
| 77 | + private class ResponseDeliveryRunnable implements Runnable { | ||
| 78 | + private final Request mRequest; | ||
| 79 | + private final Response mResponse; | ||
| 80 | + private final Runnable mRunnable; | ||
| 81 | + | ||
| 82 | + public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { | ||
| 83 | + mRequest = request; | ||
| 84 | + mResponse = response; | ||
| 85 | + mRunnable = runnable; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + @SuppressWarnings("unchecked") | ||
| 89 | + @Override | ||
| 90 | + public void run() { | ||
| 91 | + // If this request has canceled, finish it and don't deliver. | ||
| 92 | + if (mRequest.isCanceled()) { | ||
| 93 | + mRequest.finish("canceled-at-delivery"); | ||
| 94 | + return; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + // Deliver a normal response or error, depending. | ||
| 98 | + if (mResponse.isSuccess()) { | ||
| 99 | + mRequest.deliverResponse(mResponse.result); | ||
| 100 | + } else { | ||
| 101 | + mRequest.deliverError(mResponse.error); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + // If this is an intermediate response, add a marker, otherwise we're done | ||
| 105 | + // and the request can be finished. | ||
| 106 | + if (mResponse.intermediate) { | ||
| 107 | + mRequest.addMarker("intermediate-response"); | ||
| 108 | + } else { | ||
| 109 | + mRequest.finish("done"); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + // If we have been provided a post-delivery runnable, run it. | ||
| 113 | + if (mRunnable != null) { | ||
| 114 | + mRunnable.run(); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * An interface for performing requests. | ||
| 21 | + */ | ||
| 22 | +public interface Network { | ||
| 23 | + /** | ||
| 24 | + * Performs the specified request. | ||
| 25 | + * @param request Request to process | ||
| 26 | + * @return A {@link NetworkResponse} with data and caching metadata; will never be null | ||
| 27 | + * @throws VolleyError on errors | ||
| 28 | + */ | ||
| 29 | + public NetworkResponse performRequest(Request<?> request) throws VolleyError; | ||
| 30 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.net.TrafficStats; | ||
| 20 | +import android.os.Build; | ||
| 21 | +import android.os.Process; | ||
| 22 | + | ||
| 23 | +import java.util.concurrent.BlockingQueue; | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + * Provides a thread for performing network dispatch from a queue of requests. | ||
| 27 | + * | ||
| 28 | + * Requests added to the specified queue are processed from the network via a | ||
| 29 | + * specified {@link Network} interface. Responses are committed to cache, if | ||
| 30 | + * eligible, using a specified {@link Cache} interface. Valid responses and | ||
| 31 | + * errors are posted back to the caller via a {@link ResponseDelivery}. | ||
| 32 | + */ | ||
| 33 | +@SuppressWarnings("rawtypes") | ||
| 34 | +public class NetworkDispatcher extends Thread { | ||
| 35 | + /** The queue of requests to service. */ | ||
| 36 | + private final BlockingQueue<Request> mQueue; | ||
| 37 | + /** The network interface for processing requests. */ | ||
| 38 | + private final Network mNetwork; | ||
| 39 | + /** The cache to write to. */ | ||
| 40 | + private final Cache mCache; | ||
| 41 | + /** For posting responses and errors. */ | ||
| 42 | + private final ResponseDelivery mDelivery; | ||
| 43 | + /** Used for telling us to die. */ | ||
| 44 | + private volatile boolean mQuit = false; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Creates a new network dispatcher thread. You must call {@link #start()} | ||
| 48 | + * in order to begin processing. | ||
| 49 | + * | ||
| 50 | + * @param queue Queue of incoming requests for triage | ||
| 51 | + * @param network Network interface to use for performing requests | ||
| 52 | + * @param cache Cache interface to use for writing responses to cache | ||
| 53 | + * @param delivery Delivery interface to use for posting responses | ||
| 54 | + */ | ||
| 55 | + public NetworkDispatcher(BlockingQueue<Request> queue, | ||
| 56 | + Network network, Cache cache, | ||
| 57 | + ResponseDelivery delivery) { | ||
| 58 | + mQueue = queue; | ||
| 59 | + mNetwork = network; | ||
| 60 | + mCache = cache; | ||
| 61 | + mDelivery = delivery; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Forces this dispatcher to quit immediately. If any requests are still in | ||
| 66 | + * the queue, they are not guaranteed to be processed. | ||
| 67 | + */ | ||
| 68 | + public void quit() { | ||
| 69 | + mQuit = true; | ||
| 70 | + interrupt(); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + @Override | ||
| 74 | + public void run() { | ||
| 75 | + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); | ||
| 76 | + Request request; | ||
| 77 | + while (true) { | ||
| 78 | + try { | ||
| 79 | + // Take a request from the queue. | ||
| 80 | + request = mQueue.take(); | ||
| 81 | + } catch (InterruptedException e) { | ||
| 82 | + // We may have been interrupted because it was time to quit. | ||
| 83 | + if (mQuit) { | ||
| 84 | + return; | ||
| 85 | + } | ||
| 86 | + continue; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + try { | ||
| 90 | + request.addMarker("network-queue-take"); | ||
| 91 | + | ||
| 92 | + // If the request was cancelled already, do not perform the | ||
| 93 | + // network request. | ||
| 94 | + if (request.isCanceled()) { | ||
| 95 | + request.finish("network-discard-cancelled"); | ||
| 96 | + continue; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + // Tag the request (if API >= 14) | ||
| 100 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | ||
| 101 | + TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + // Perform the network request. | ||
| 105 | + NetworkResponse networkResponse = mNetwork.performRequest(request); | ||
| 106 | + request.addMarker("network-http-complete"); | ||
| 107 | + | ||
| 108 | + // If the server returned 304 AND we delivered a response already, | ||
| 109 | + // we're done -- don't deliver a second identical response. | ||
| 110 | + if (networkResponse.notModified && request.hasHadResponseDelivered()) { | ||
| 111 | + request.finish("not-modified"); | ||
| 112 | + continue; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + // Parse the response here on the worker thread. | ||
| 116 | + Response<?> response = request.parseNetworkResponse(networkResponse); | ||
| 117 | + request.addMarker("network-parse-complete"); | ||
| 118 | + | ||
| 119 | + // Write to cache if applicable. | ||
| 120 | + // TODO: Only update cache metadata instead of entire record for 304s. | ||
| 121 | + if (request.shouldCache() && response.cacheEntry != null) { | ||
| 122 | + mCache.put(request.getCacheKey(), response.cacheEntry); | ||
| 123 | + request.addMarker("network-cache-written"); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + // Post the response back. | ||
| 127 | + request.markDelivered(); | ||
| 128 | + mDelivery.postResponse(request, response); | ||
| 129 | + } catch (VolleyError volleyError) { | ||
| 130 | + parseAndDeliverNetworkError(request, volleyError); | ||
| 131 | + } catch (Exception e) { | ||
| 132 | + VolleyLog.e(e, "Unhandled exception %s", e.toString()); | ||
| 133 | + mDelivery.postError(request, new VolleyError(e)); | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) { | ||
| 139 | + error = request.parseNetworkError(error); | ||
| 140 | + mDelivery.postError(request, error); | ||
| 141 | + } | ||
| 142 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Indicates that there was a network error when performing a Volley request. | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class NetworkError extends VolleyError { | ||
| 24 | + public NetworkError() { | ||
| 25 | + super(); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public NetworkError(Throwable cause) { | ||
| 29 | + super(cause); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public NetworkError(NetworkResponse networkResponse) { | ||
| 33 | + super(networkResponse); | ||
| 34 | + } | ||
| 35 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import org.apache.http.HttpStatus; | ||
| 20 | + | ||
| 21 | +import java.util.Collections; | ||
| 22 | +import java.util.Map; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * Data and headers returned from {@link Network#performRequest(Request)}. | ||
| 26 | + */ | ||
| 27 | +public class NetworkResponse { | ||
| 28 | + /** | ||
| 29 | + * Creates a new network response. | ||
| 30 | + * @param statusCode the HTTP status code | ||
| 31 | + * @param data Response body | ||
| 32 | + * @param headers Headers returned with this response, or null for none | ||
| 33 | + * @param notModified True if the server returned a 304 and the data was already in cache | ||
| 34 | + */ | ||
| 35 | + public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers, | ||
| 36 | + boolean notModified) { | ||
| 37 | + this.statusCode = statusCode; | ||
| 38 | + this.data = data; | ||
| 39 | + this.headers = headers; | ||
| 40 | + this.notModified = notModified; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public NetworkResponse(byte[] data) { | ||
| 44 | + this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + public NetworkResponse(byte[] data, Map<String, String> headers) { | ||
| 48 | + this(HttpStatus.SC_OK, data, headers, false); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + /** The HTTP status code. */ | ||
| 52 | + public final int statusCode; | ||
| 53 | + | ||
| 54 | + /** Raw data from this response. */ | ||
| 55 | + public final byte[] data; | ||
| 56 | + | ||
| 57 | + /** Response headers. */ | ||
| 58 | + public final Map<String, String> headers; | ||
| 59 | + | ||
| 60 | + /** True if the server returned a 304 (Not Modified). */ | ||
| 61 | + public final boolean notModified; | ||
| 62 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Error indicating that no connection could be established when performing a Volley request. | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class NoConnectionError extends NetworkError { | ||
| 24 | + public NoConnectionError() { | ||
| 25 | + super(); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public NoConnectionError(Throwable reason) { | ||
| 29 | + super(reason); | ||
| 30 | + } | ||
| 31 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Indicates that the server's response could not be parsed. | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class ParseError extends VolleyError { | ||
| 24 | + public ParseError() { } | ||
| 25 | + | ||
| 26 | + public ParseError(NetworkResponse networkResponse) { | ||
| 27 | + super(networkResponse); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public ParseError(Throwable cause) { | ||
| 31 | + super(cause); | ||
| 32 | + } | ||
| 33 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.net.TrafficStats; | ||
| 20 | +import android.net.Uri; | ||
| 21 | +import android.os.Handler; | ||
| 22 | +import android.os.Looper; | ||
| 23 | +import android.os.SystemClock; | ||
| 24 | +import android.text.TextUtils; | ||
| 25 | +import com.android.volley.VolleyLog.MarkerLog; | ||
| 26 | + | ||
| 27 | +import java.io.UnsupportedEncodingException; | ||
| 28 | +import java.net.URLEncoder; | ||
| 29 | +import java.util.Collections; | ||
| 30 | +import java.util.Map; | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * Base class for all network requests. | ||
| 34 | + * | ||
| 35 | + * @param <T> The type of parsed response this request expects. | ||
| 36 | + */ | ||
| 37 | +public abstract class Request<T> implements Comparable<Request<T>> { | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}. | ||
| 41 | + */ | ||
| 42 | + private static final String DEFAULT_PARAMS_ENCODING = "UTF-8"; | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Supported request methods. | ||
| 46 | + */ | ||
| 47 | + public interface Method { | ||
| 48 | + int DEPRECATED_GET_OR_POST = -1; | ||
| 49 | + int GET = 0; | ||
| 50 | + int POST = 1; | ||
| 51 | + int PUT = 2; | ||
| 52 | + int DELETE = 3; | ||
| 53 | + int HEAD = 4; | ||
| 54 | + int OPTIONS = 5; | ||
| 55 | + int TRACE = 6; | ||
| 56 | + int PATCH = 7; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + /** An event log tracing the lifetime of this request; for debugging. */ | ||
| 60 | + private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null; | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS, | ||
| 64 | + * TRACE, and PATCH. | ||
| 65 | + */ | ||
| 66 | + private final int mMethod; | ||
| 67 | + | ||
| 68 | + /** URL of this request. */ | ||
| 69 | + private final String mUrl; | ||
| 70 | + | ||
| 71 | + /** Default tag for {@link TrafficStats}. */ | ||
| 72 | + private final int mDefaultTrafficStatsTag; | ||
| 73 | + | ||
| 74 | + /** Listener interface for errors. */ | ||
| 75 | + private final Response.ErrorListener mErrorListener; | ||
| 76 | + | ||
| 77 | + /** Sequence number of this request, used to enforce FIFO ordering. */ | ||
| 78 | + private Integer mSequence; | ||
| 79 | + | ||
| 80 | + /** The request queue this request is associated with. */ | ||
| 81 | + private RequestQueue mRequestQueue; | ||
| 82 | + | ||
| 83 | + /** Whether or not responses to this request should be cached. */ | ||
| 84 | + private boolean mShouldCache = true; | ||
| 85 | + | ||
| 86 | + /** Whether or not this request has been canceled. */ | ||
| 87 | + private boolean mCanceled = false; | ||
| 88 | + | ||
| 89 | + /** Whether or not a response has been delivered for this request yet. */ | ||
| 90 | + private boolean mResponseDelivered = false; | ||
| 91 | + | ||
| 92 | + // A cheap variant of request tracing used to dump slow requests. | ||
| 93 | + private long mRequestBirthTime = 0; | ||
| 94 | + | ||
| 95 | + /** Threshold at which we should log the request (even when debug logging is not enabled). */ | ||
| 96 | + private static final long SLOW_REQUEST_THRESHOLD_MS = 3000; | ||
| 97 | + | ||
| 98 | + /** The retry policy for this request. */ | ||
| 99 | + private RetryPolicy mRetryPolicy; | ||
| 100 | + | ||
| 101 | + /** | ||
| 102 | + * When a request can be retrieved from cache but must be refreshed from | ||
| 103 | + * the network, the cache entry will be stored here so that in the event of | ||
| 104 | + * a "Not Modified" response, we can be sure it hasn't been evicted from cache. | ||
| 105 | + */ | ||
| 106 | + private Cache.Entry mCacheEntry = null; | ||
| 107 | + | ||
| 108 | + /** An opaque token tagging this request; used for bulk cancellation. */ | ||
| 109 | + private Object mTag; | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * Creates a new request with the given URL and error listener. Note that | ||
| 113 | + * the normal response listener is not provided here as delivery of responses | ||
| 114 | + * is provided by subclasses, who have a better idea of how to deliver an | ||
| 115 | + * already-parsed response. | ||
| 116 | + * | ||
| 117 | + * @deprecated Use {@link #Request(int, String, Response.ErrorListener)}. | ||
| 118 | + */ | ||
| 119 | + public Request(String url, Response.ErrorListener listener) { | ||
| 120 | + this(Method.DEPRECATED_GET_OR_POST, url, listener); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + /** | ||
| 124 | + * Creates a new request with the given method (one of the values from {@link Method}), | ||
| 125 | + * URL, and error listener. Note that the normal response listener is not provided here as | ||
| 126 | + * delivery of responses is provided by subclasses, who have a better idea of how to deliver | ||
| 127 | + * an already-parsed response. | ||
| 128 | + */ | ||
| 129 | + public Request(int method, String url, Response.ErrorListener listener) { | ||
| 130 | + mMethod = method; | ||
| 131 | + mUrl = url; | ||
| 132 | + mErrorListener = listener; | ||
| 133 | + setRetryPolicy(new DefaultRetryPolicy()); | ||
| 134 | + | ||
| 135 | + mDefaultTrafficStatsTag = TextUtils.isEmpty(url) ? 0: Uri.parse(url).getHost().hashCode(); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + /** | ||
| 139 | + * Return the method for this request. Can be one of the values in {@link Method}. | ||
| 140 | + */ | ||
| 141 | + public int getMethod() { | ||
| 142 | + return mMethod; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + /** | ||
| 146 | + * Set a tag on this request. Can be used to cancel all requests with this | ||
| 147 | + * tag by {@link RequestQueue#cancelAll(Object)}. | ||
| 148 | + */ | ||
| 149 | + public void setTag(Object tag) { | ||
| 150 | + mTag = tag; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + /** | ||
| 154 | + * Returns this request's tag. | ||
| 155 | + * @see Request#setTag(Object) | ||
| 156 | + */ | ||
| 157 | + public Object getTag() { | ||
| 158 | + return mTag; | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + /** | ||
| 162 | + * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} | ||
| 163 | + */ | ||
| 164 | + public int getTrafficStatsTag() { | ||
| 165 | + return mDefaultTrafficStatsTag; | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + /** | ||
| 169 | + * Sets the retry policy for this request. | ||
| 170 | + */ | ||
| 171 | + public void setRetryPolicy(RetryPolicy retryPolicy) { | ||
| 172 | + mRetryPolicy = retryPolicy; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + /** | ||
| 176 | + * Adds an event to this request's event log; for debugging. | ||
| 177 | + */ | ||
| 178 | + public void addMarker(String tag) { | ||
| 179 | + if (MarkerLog.ENABLED) { | ||
| 180 | + mEventLog.add(tag, Thread.currentThread().getId()); | ||
| 181 | + } else if (mRequestBirthTime == 0) { | ||
| 182 | + mRequestBirthTime = SystemClock.elapsedRealtime(); | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + /** | ||
| 187 | + * Notifies the request queue that this request has finished (successfully or with error). | ||
| 188 | + * | ||
| 189 | + * <p>Also dumps all events from this request's event log; for debugging.</p> | ||
| 190 | + */ | ||
| 191 | + void finish(final String tag) { | ||
| 192 | + if (mRequestQueue != null) { | ||
| 193 | + mRequestQueue.finish(this); | ||
| 194 | + } | ||
| 195 | + if (MarkerLog.ENABLED) { | ||
| 196 | + final long threadId = Thread.currentThread().getId(); | ||
| 197 | + if (Looper.myLooper() != Looper.getMainLooper()) { | ||
| 198 | + // If we finish marking off of the main thread, we need to | ||
| 199 | + // actually do it on the main thread to ensure correct ordering. | ||
| 200 | + Handler mainThread = new Handler(Looper.getMainLooper()); | ||
| 201 | + mainThread.post(new Runnable() { | ||
| 202 | + @Override | ||
| 203 | + public void run() { | ||
| 204 | + mEventLog.add(tag, threadId); | ||
| 205 | + mEventLog.finish(this.toString()); | ||
| 206 | + } | ||
| 207 | + }); | ||
| 208 | + return; | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + mEventLog.add(tag, threadId); | ||
| 212 | + mEventLog.finish(this.toString()); | ||
| 213 | + } else { | ||
| 214 | + long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime; | ||
| 215 | + if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) { | ||
| 216 | + VolleyLog.d("%d ms: %s", requestTime, this.toString()); | ||
| 217 | + } | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + /** | ||
| 222 | + * Associates this request with the given queue. The request queue will be notified when this | ||
| 223 | + * request has finished. | ||
| 224 | + */ | ||
| 225 | + public void setRequestQueue(RequestQueue requestQueue) { | ||
| 226 | + mRequestQueue = requestQueue; | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + /** | ||
| 230 | + * Sets the sequence number of this request. Used by {@link RequestQueue}. | ||
| 231 | + */ | ||
| 232 | + public final void setSequence(int sequence) { | ||
| 233 | + mSequence = sequence; | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + /** | ||
| 237 | + * Returns the sequence number of this request. | ||
| 238 | + */ | ||
| 239 | + public final int getSequence() { | ||
| 240 | + if (mSequence == null) { | ||
| 241 | + throw new IllegalStateException("getSequence called before setSequence"); | ||
| 242 | + } | ||
| 243 | + return mSequence; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + /** | ||
| 247 | + * Returns the URL of this request. | ||
| 248 | + */ | ||
| 249 | + public String getUrl() { | ||
| 250 | + return mUrl; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + /** | ||
| 254 | + * Returns the cache key for this request. By default, this is the URL. | ||
| 255 | + */ | ||
| 256 | + public String getCacheKey() { | ||
| 257 | + return getUrl(); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + /** | ||
| 261 | + * Annotates this request with an entry retrieved for it from cache. | ||
| 262 | + * Used for cache coherency support. | ||
| 263 | + */ | ||
| 264 | + public void setCacheEntry(Cache.Entry entry) { | ||
| 265 | + mCacheEntry = entry; | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + /** | ||
| 269 | + * Returns the annotated cache entry, or null if there isn't one. | ||
| 270 | + */ | ||
| 271 | + public Cache.Entry getCacheEntry() { | ||
| 272 | + return mCacheEntry; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + /** | ||
| 276 | + * Mark this request as canceled. No callback will be delivered. | ||
| 277 | + */ | ||
| 278 | + public void cancel() { | ||
| 279 | + mCanceled = true; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + /** | ||
| 283 | + * Returns true if this request has been canceled. | ||
| 284 | + */ | ||
| 285 | + public boolean isCanceled() { | ||
| 286 | + return mCanceled; | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + /** | ||
| 290 | + * Returns a list of extra HTTP headers to go along with this request. Can | ||
| 291 | + * throw {@link AuthFailureError} as authentication may be required to | ||
| 292 | + * provide these values. | ||
| 293 | + * @throws AuthFailureError In the event of auth failure | ||
| 294 | + */ | ||
| 295 | + public Map<String, String> getHeaders() throws AuthFailureError { | ||
| 296 | + return Collections.emptyMap(); | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + /** | ||
| 300 | + * Returns a Map of POST parameters to be used for this request, or null if | ||
| 301 | + * a simple GET should be used. Can throw {@link AuthFailureError} as | ||
| 302 | + * authentication may be required to provide these values. | ||
| 303 | + * | ||
| 304 | + * <p>Note that only one of getPostParams() and getPostBody() can return a non-null | ||
| 305 | + * value.</p> | ||
| 306 | + * @throws AuthFailureError In the event of auth failure | ||
| 307 | + * | ||
| 308 | + * @deprecated Use {@link #getParams()} instead. | ||
| 309 | + */ | ||
| 310 | + protected Map<String, String> getPostParams() throws AuthFailureError { | ||
| 311 | + return getParams(); | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + /** | ||
| 315 | + * Returns which encoding should be used when converting POST parameters returned by | ||
| 316 | + * {@link #getPostParams()} into a raw POST body. | ||
| 317 | + * | ||
| 318 | + * <p>This controls both encodings: | ||
| 319 | + * <ol> | ||
| 320 | + * <li>The string encoding used when converting parameter names and values into bytes prior | ||
| 321 | + * to URL encoding them.</li> | ||
| 322 | + * <li>The string encoding used when converting the URL encoded parameters into a raw | ||
| 323 | + * byte array.</li> | ||
| 324 | + * </ol> | ||
| 325 | + * | ||
| 326 | + * @deprecated Use {@link #getParamsEncoding()} instead. | ||
| 327 | + */ | ||
| 328 | + protected String getPostParamsEncoding() { | ||
| 329 | + return getParamsEncoding(); | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + /** | ||
| 333 | + * @deprecated Use {@link #getBodyContentType()} instead. | ||
| 334 | + */ | ||
| 335 | + public String getPostBodyContentType() { | ||
| 336 | + return getBodyContentType(); | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + /** | ||
| 340 | + * Returns the raw POST body to be sent. | ||
| 341 | + * | ||
| 342 | + * @throws AuthFailureError In the event of auth failure | ||
| 343 | + * | ||
| 344 | + * @deprecated Use {@link #getBody()} instead. | ||
| 345 | + */ | ||
| 346 | + public byte[] getPostBody() throws AuthFailureError { | ||
| 347 | + // Note: For compatibility with legacy clients of volley, this implementation must remain | ||
| 348 | + // here instead of simply calling the getBody() function because this function must | ||
| 349 | + // call getPostParams() and getPostParamsEncoding() since legacy clients would have | ||
| 350 | + // overridden these two member functions for POST requests. | ||
| 351 | + Map<String, String> postParams = getPostParams(); | ||
| 352 | + if (postParams != null && postParams.size() > 0) { | ||
| 353 | + return encodeParameters(postParams, getPostParamsEncoding()); | ||
| 354 | + } | ||
| 355 | + return null; | ||
| 356 | + } | ||
| 357 | + | ||
| 358 | + /** | ||
| 359 | + * Returns a Map of parameters to be used for a POST or PUT request. Can throw | ||
| 360 | + * {@link AuthFailureError} as authentication may be required to provide these values. | ||
| 361 | + * | ||
| 362 | + * <p>Note that you can directly override {@link #getBody()} for custom data.</p> | ||
| 363 | + * | ||
| 364 | + * @throws AuthFailureError in the event of auth failure | ||
| 365 | + */ | ||
| 366 | + protected Map<String, String> getParams() throws AuthFailureError { | ||
| 367 | + return null; | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + /** | ||
| 371 | + * Returns which encoding should be used when converting POST or PUT parameters returned by | ||
| 372 | + * {@link #getParams()} into a raw POST or PUT body. | ||
| 373 | + * | ||
| 374 | + * <p>This controls both encodings: | ||
| 375 | + * <ol> | ||
| 376 | + * <li>The string encoding used when converting parameter names and values into bytes prior | ||
| 377 | + * to URL encoding them.</li> | ||
| 378 | + * <li>The string encoding used when converting the URL encoded parameters into a raw | ||
| 379 | + * byte array.</li> | ||
| 380 | + * </ol> | ||
| 381 | + */ | ||
| 382 | + protected String getParamsEncoding() { | ||
| 383 | + return DEFAULT_PARAMS_ENCODING; | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + public String getBodyContentType() { | ||
| 387 | + return "application/x-www-form-urlencoded; charset=" + getParamsEncoding(); | ||
| 388 | + } | ||
| 389 | + | ||
| 390 | + /** | ||
| 391 | + * Returns the raw POST or PUT body to be sent. | ||
| 392 | + * | ||
| 393 | + * @throws AuthFailureError in the event of auth failure | ||
| 394 | + */ | ||
| 395 | + public byte[] getBody() throws AuthFailureError { | ||
| 396 | + Map<String, String> params = getParams(); | ||
| 397 | + if (params != null && params.size() > 0) { | ||
| 398 | + return encodeParameters(params, getParamsEncoding()); | ||
| 399 | + } | ||
| 400 | + return null; | ||
| 401 | + } | ||
| 402 | + | ||
| 403 | + /** | ||
| 404 | + * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string. | ||
| 405 | + */ | ||
| 406 | + private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) { | ||
| 407 | + StringBuilder encodedParams = new StringBuilder(); | ||
| 408 | + try { | ||
| 409 | + for (Map.Entry<String, String> entry : params.entrySet()) { | ||
| 410 | + encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding)); | ||
| 411 | + encodedParams.append('='); | ||
| 412 | + encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding)); | ||
| 413 | + encodedParams.append('&'); | ||
| 414 | + } | ||
| 415 | + return encodedParams.toString().getBytes(paramsEncoding); | ||
| 416 | + } catch (UnsupportedEncodingException uee) { | ||
| 417 | + throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee); | ||
| 418 | + } | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + /** | ||
| 422 | + * Set whether or not responses to this request should be cached. | ||
| 423 | + */ | ||
| 424 | + public final void setShouldCache(boolean shouldCache) { | ||
| 425 | + mShouldCache = shouldCache; | ||
| 426 | + } | ||
| 427 | + | ||
| 428 | + /** | ||
| 429 | + * Returns true if responses to this request should be cached. | ||
| 430 | + */ | ||
| 431 | + public final boolean shouldCache() { | ||
| 432 | + return mShouldCache; | ||
| 433 | + } | ||
| 434 | + | ||
| 435 | + /** | ||
| 436 | + * Priority values. Requests will be processed from higher priorities to | ||
| 437 | + * lower priorities, in FIFO order. | ||
| 438 | + */ | ||
| 439 | + public enum Priority { | ||
| 440 | + LOW, | ||
| 441 | + NORMAL, | ||
| 442 | + HIGH, | ||
| 443 | + IMMEDIATE | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + /** | ||
| 447 | + * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default. | ||
| 448 | + */ | ||
| 449 | + public Priority getPriority() { | ||
| 450 | + return Priority.NORMAL; | ||
| 451 | + } | ||
| 452 | + | ||
| 453 | + /** | ||
| 454 | + * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed | ||
| 455 | + * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry | ||
| 456 | + * attempts remaining, this will cause delivery of a {@link TimeoutError} error. | ||
| 457 | + */ | ||
| 458 | + public final int getTimeoutMs() { | ||
| 459 | + return mRetryPolicy.getCurrentTimeout(); | ||
| 460 | + } | ||
| 461 | + | ||
| 462 | + /** | ||
| 463 | + * Returns the retry policy that should be used for this request. | ||
| 464 | + */ | ||
| 465 | + public RetryPolicy getRetryPolicy() { | ||
| 466 | + return mRetryPolicy; | ||
| 467 | + } | ||
| 468 | + | ||
| 469 | + /** | ||
| 470 | + * Mark this request as having a response delivered on it. This can be used | ||
| 471 | + * later in the request's lifetime for suppressing identical responses. | ||
| 472 | + */ | ||
| 473 | + public void markDelivered() { | ||
| 474 | + mResponseDelivered = true; | ||
| 475 | + } | ||
| 476 | + | ||
| 477 | + /** | ||
| 478 | + * Returns true if this request has had a response delivered for it. | ||
| 479 | + */ | ||
| 480 | + public boolean hasHadResponseDelivered() { | ||
| 481 | + return mResponseDelivered; | ||
| 482 | + } | ||
| 483 | + | ||
| 484 | + /** | ||
| 485 | + * Subclasses must implement this to parse the raw network response | ||
| 486 | + * and return an appropriate response type. This method will be | ||
| 487 | + * called from a worker thread. The response will not be delivered | ||
| 488 | + * if you return null. | ||
| 489 | + * @param response Response from the network | ||
| 490 | + * @return The parsed response, or null in the case of an error | ||
| 491 | + */ | ||
| 492 | + abstract protected Response<T> parseNetworkResponse(NetworkResponse response); | ||
| 493 | + | ||
| 494 | + /** | ||
| 495 | + * Subclasses can override this method to parse 'networkError' and return a more specific error. | ||
| 496 | + * | ||
| 497 | + * <p>The default implementation just returns the passed 'networkError'.</p> | ||
| 498 | + * | ||
| 499 | + * @param volleyError the error retrieved from the network | ||
| 500 | + * @return an NetworkError augmented with additional information | ||
| 501 | + */ | ||
| 502 | + protected VolleyError parseNetworkError(VolleyError volleyError) { | ||
| 503 | + return volleyError; | ||
| 504 | + } | ||
| 505 | + | ||
| 506 | + /** | ||
| 507 | + * Subclasses must implement this to perform delivery of the parsed | ||
| 508 | + * response to their listeners. The given response is guaranteed to | ||
| 509 | + * be non-null; responses that fail to parse are not delivered. | ||
| 510 | + * @param response The parsed response returned by | ||
| 511 | + * {@link #parseNetworkResponse(NetworkResponse)} | ||
| 512 | + */ | ||
| 513 | + abstract protected void deliverResponse(T response); | ||
| 514 | + | ||
| 515 | + /** | ||
| 516 | + * Delivers error message to the ErrorListener that the Request was | ||
| 517 | + * initialized with. | ||
| 518 | + * | ||
| 519 | + * @param error Error details | ||
| 520 | + */ | ||
| 521 | + public void deliverError(VolleyError error) { | ||
| 522 | + if (mErrorListener != null) { | ||
| 523 | + mErrorListener.onErrorResponse(error); | ||
| 524 | + } | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + /** | ||
| 528 | + * Our comparator sorts from high to low priority, and secondarily by | ||
| 529 | + * sequence number to provide FIFO ordering. | ||
| 530 | + */ | ||
| 531 | + @Override | ||
| 532 | + public int compareTo(Request<T> other) { | ||
| 533 | + Priority left = this.getPriority(); | ||
| 534 | + Priority right = other.getPriority(); | ||
| 535 | + | ||
| 536 | + // High-priority requests are "lesser" so they are sorted to the front. | ||
| 537 | + // Equal priorities are sorted by sequence number to provide FIFO ordering. | ||
| 538 | + return left == right ? | ||
| 539 | + this.mSequence - other.mSequence : | ||
| 540 | + right.ordinal() - left.ordinal(); | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + @Override | ||
| 544 | + public String toString() { | ||
| 545 | + String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag()); | ||
| 546 | + return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " " | ||
| 547 | + + getPriority() + " " + mSequence; | ||
| 548 | + } | ||
| 549 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.os.Handler; | ||
| 20 | +import android.os.Looper; | ||
| 21 | + | ||
| 22 | +import java.util.*; | ||
| 23 | +import java.util.concurrent.PriorityBlockingQueue; | ||
| 24 | +import java.util.concurrent.atomic.AtomicInteger; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * A request dispatch queue with a thread pool of dispatchers. | ||
| 28 | + * | ||
| 29 | + * Calling {@link #add(Request)} will enqueue the given Request for dispatch, | ||
| 30 | + * resolving from either cache or network on a worker thread, and then delivering | ||
| 31 | + * a parsed response on the main thread. | ||
| 32 | + */ | ||
| 33 | +@SuppressWarnings("rawtypes") | ||
| 34 | +public class RequestQueue { | ||
| 35 | + | ||
| 36 | + /** Used for generating monotonically-increasing sequence numbers for requests. */ | ||
| 37 | + private AtomicInteger mSequenceGenerator = new AtomicInteger(); | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Staging area for requests that already have a duplicate request in flight. | ||
| 41 | + * | ||
| 42 | + * <ul> | ||
| 43 | + * <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache | ||
| 44 | + * key.</li> | ||
| 45 | + * <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request | ||
| 46 | + * is <em>not</em> contained in that list. Is null if no requests are staged.</li> | ||
| 47 | + * </ul> | ||
| 48 | + */ | ||
| 49 | + private final Map<String, Queue<Request>> mWaitingRequests = | ||
| 50 | + new HashMap<String, Queue<Request>>(); | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * The set of all requests currently being processed by this RequestQueue. A Request | ||
| 54 | + * will be in this set if it is waiting in any queue or currently being processed by | ||
| 55 | + * any dispatcher. | ||
| 56 | + */ | ||
| 57 | + private final Set<Request> mCurrentRequests = new HashSet<Request>(); | ||
| 58 | + | ||
| 59 | + /** The cache triage queue. */ | ||
| 60 | + private final PriorityBlockingQueue<Request> mCacheQueue = | ||
| 61 | + new PriorityBlockingQueue<Request>(); | ||
| 62 | + | ||
| 63 | + /** The queue of requests that are actually going out to the network. */ | ||
| 64 | + private final PriorityBlockingQueue<Request> mNetworkQueue = | ||
| 65 | + new PriorityBlockingQueue<Request>(); | ||
| 66 | + | ||
| 67 | + /** Number of network request dispatcher threads to start. */ | ||
| 68 | + private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; | ||
| 69 | + | ||
| 70 | + /** Cache interface for retrieving and storing respones. */ | ||
| 71 | + private final Cache mCache; | ||
| 72 | + | ||
| 73 | + /** Network interface for performing requests. */ | ||
| 74 | + private final Network mNetwork; | ||
| 75 | + | ||
| 76 | + /** Response delivery mechanism. */ | ||
| 77 | + private final ResponseDelivery mDelivery; | ||
| 78 | + | ||
| 79 | + /** The network dispatchers. */ | ||
| 80 | + private NetworkDispatcher[] mDispatchers; | ||
| 81 | + | ||
| 82 | + /** The cache dispatcher. */ | ||
| 83 | + private CacheDispatcher mCacheDispatcher; | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * Creates the worker pool. Processing will not begin until {@link #start()} is called. | ||
| 87 | + * | ||
| 88 | + * @param cache A Cache to use for persisting responses to disk | ||
| 89 | + * @param network A Network interface for performing HTTP requests | ||
| 90 | + * @param threadPoolSize Number of network dispatcher threads to create | ||
| 91 | + * @param delivery A ResponseDelivery interface for posting responses and errors | ||
| 92 | + */ | ||
| 93 | + public RequestQueue(Cache cache, Network network, int threadPoolSize, | ||
| 94 | + ResponseDelivery delivery) { | ||
| 95 | + mCache = cache; | ||
| 96 | + mNetwork = network; | ||
| 97 | + mDispatchers = new NetworkDispatcher[threadPoolSize]; | ||
| 98 | + mDelivery = delivery; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + /** | ||
| 102 | + * Creates the worker pool. Processing will not begin until {@link #start()} is called. | ||
| 103 | + * | ||
| 104 | + * @param cache A Cache to use for persisting responses to disk | ||
| 105 | + * @param network A Network interface for performing HTTP requests | ||
| 106 | + * @param threadPoolSize Number of network dispatcher threads to create | ||
| 107 | + */ | ||
| 108 | + public RequestQueue(Cache cache, Network network, int threadPoolSize) { | ||
| 109 | + this(cache, network, threadPoolSize, | ||
| 110 | + new ExecutorDelivery(new Handler(Looper.getMainLooper()))); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + /** | ||
| 114 | + * Creates the worker pool. Processing will not begin until {@link #start()} is called. | ||
| 115 | + * | ||
| 116 | + * @param cache A Cache to use for persisting responses to disk | ||
| 117 | + * @param network A Network interface for performing HTTP requests | ||
| 118 | + */ | ||
| 119 | + public RequestQueue(Cache cache, Network network) { | ||
| 120 | + this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + /** | ||
| 124 | + * Starts the dispatchers in this queue. | ||
| 125 | + */ | ||
| 126 | + public void start() { | ||
| 127 | + stop(); // Make sure any currently running dispatchers are stopped. | ||
| 128 | + // Create the cache dispatcher and start it. | ||
| 129 | + mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); | ||
| 130 | + mCacheDispatcher.start(); | ||
| 131 | + | ||
| 132 | + // Create network dispatchers (and corresponding threads) up to the pool size. | ||
| 133 | + for (int i = 0; i < mDispatchers.length; i++) { | ||
| 134 | + NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, | ||
| 135 | + mCache, mDelivery); | ||
| 136 | + mDispatchers[i] = networkDispatcher; | ||
| 137 | + networkDispatcher.start(); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + /** | ||
| 142 | + * Stops the cache and network dispatchers. | ||
| 143 | + */ | ||
| 144 | + public void stop() { | ||
| 145 | + if (mCacheDispatcher != null) { | ||
| 146 | + mCacheDispatcher.quit(); | ||
| 147 | + } | ||
| 148 | + for (int i = 0; i < mDispatchers.length; i++) { | ||
| 149 | + if (mDispatchers[i] != null) { | ||
| 150 | + mDispatchers[i].quit(); | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + /** | ||
| 156 | + * Gets a sequence number. | ||
| 157 | + */ | ||
| 158 | + public int getSequenceNumber() { | ||
| 159 | + return mSequenceGenerator.incrementAndGet(); | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + /** | ||
| 163 | + * Gets the {@link Cache} instance being used. | ||
| 164 | + */ | ||
| 165 | + public Cache getCache() { | ||
| 166 | + return mCache; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + /** | ||
| 170 | + * A simple predicate or filter interface for Requests, for use by | ||
| 171 | + * {@link RequestQueue#cancelAll(RequestFilter)}. | ||
| 172 | + */ | ||
| 173 | + public interface RequestFilter { | ||
| 174 | + public boolean apply(Request<?> request); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + /** | ||
| 178 | + * Cancels all requests in this queue for which the given filter applies. | ||
| 179 | + * @param filter The filtering function to use | ||
| 180 | + */ | ||
| 181 | + public void cancelAll(RequestFilter filter) { | ||
| 182 | + synchronized (mCurrentRequests) { | ||
| 183 | + for (Request<?> request : mCurrentRequests) { | ||
| 184 | + if (filter.apply(request)) { | ||
| 185 | + request.cancel(); | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * Cancels all requests in this queue with the given tag. Tag must be non-null | ||
| 193 | + * and equality is by identity. | ||
| 194 | + */ | ||
| 195 | + public void cancelAll(final Object tag) { | ||
| 196 | + if (tag == null) { | ||
| 197 | + throw new IllegalArgumentException("Cannot cancelAll with a null tag"); | ||
| 198 | + } | ||
| 199 | + cancelAll(new RequestFilter() { | ||
| 200 | + @Override | ||
| 201 | + public boolean apply(Request<?> request) { | ||
| 202 | + return request.getTag() == tag; | ||
| 203 | + } | ||
| 204 | + }); | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + /** | ||
| 208 | + * Adds a Request to the dispatch queue. | ||
| 209 | + * @param request The request to service | ||
| 210 | + * @return The passed-in request | ||
| 211 | + */ | ||
| 212 | + public Request add(Request request) { | ||
| 213 | + // Tag the request as belonging to this queue and add it to the set of current requests. | ||
| 214 | + request.setRequestQueue(this); | ||
| 215 | + synchronized (mCurrentRequests) { | ||
| 216 | + mCurrentRequests.add(request); | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + // Process requests in the order they are added. | ||
| 220 | + request.setSequence(getSequenceNumber()); | ||
| 221 | + request.addMarker("add-to-queue"); | ||
| 222 | + | ||
| 223 | + // If the request is uncacheable, skip the cache queue and go straight to the network. | ||
| 224 | + if (!request.shouldCache()) { | ||
| 225 | + mNetworkQueue.add(request); | ||
| 226 | + return request; | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + // Insert request into stage if there's already a request with the same cache key in flight. | ||
| 230 | + synchronized (mWaitingRequests) { | ||
| 231 | + String cacheKey = request.getCacheKey(); | ||
| 232 | + if (mWaitingRequests.containsKey(cacheKey)) { | ||
| 233 | + // There is already a request in flight. Queue up. | ||
| 234 | + Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey); | ||
| 235 | + if (stagedRequests == null) { | ||
| 236 | + stagedRequests = new LinkedList<Request>(); | ||
| 237 | + } | ||
| 238 | + stagedRequests.add(request); | ||
| 239 | + mWaitingRequests.put(cacheKey, stagedRequests); | ||
| 240 | + if (VolleyLog.DEBUG) { | ||
| 241 | + VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); | ||
| 242 | + } | ||
| 243 | + } else { | ||
| 244 | + // Insert 'null' queue for this cacheKey, indicating there is now a request in | ||
| 245 | + // flight. | ||
| 246 | + mWaitingRequests.put(cacheKey, null); | ||
| 247 | + mCacheQueue.add(request); | ||
| 248 | + } | ||
| 249 | + return request; | ||
| 250 | + } | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + /** | ||
| 254 | + * Called from {@link Request#finish(String)}, indicating that processing of the given request | ||
| 255 | + * has finished. | ||
| 256 | + * | ||
| 257 | + * <p>Releases waiting requests for <code>request.getCacheKey()</code> if | ||
| 258 | + * <code>request.shouldCache()</code>.</p> | ||
| 259 | + */ | ||
| 260 | + void finish(Request request) { | ||
| 261 | + // Remove from the set of requests currently being processed. | ||
| 262 | + synchronized (mCurrentRequests) { | ||
| 263 | + mCurrentRequests.remove(request); | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + if (request.shouldCache()) { | ||
| 267 | + synchronized (mWaitingRequests) { | ||
| 268 | + String cacheKey = request.getCacheKey(); | ||
| 269 | + Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey); | ||
| 270 | + if (waitingRequests != null) { | ||
| 271 | + if (VolleyLog.DEBUG) { | ||
| 272 | + VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", | ||
| 273 | + waitingRequests.size(), cacheKey); | ||
| 274 | + } | ||
| 275 | + // Process all queued up requests. They won't be considered as in flight, but | ||
| 276 | + // that's not a problem as the cache has been primed by 'request'. | ||
| 277 | + mCacheQueue.addAll(waitingRequests); | ||
| 278 | + } | ||
| 279 | + } | ||
| 280 | + } | ||
| 281 | + } | ||
| 282 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Encapsulates a parsed response for delivery. | ||
| 21 | + * | ||
| 22 | + * @param <T> Parsed type of this response | ||
| 23 | + */ | ||
| 24 | +public class Response<T> { | ||
| 25 | + | ||
| 26 | + /** Callback interface for delivering parsed responses. */ | ||
| 27 | + public interface Listener<T> { | ||
| 28 | + /** Called when a response is received. */ | ||
| 29 | + public void onResponse(T response); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + /** Callback interface for delivering error responses. */ | ||
| 33 | + public interface ErrorListener { | ||
| 34 | + /** | ||
| 35 | + * Callback method that an error has been occurred with the | ||
| 36 | + * provided error code and optional user-readable message. | ||
| 37 | + */ | ||
| 38 | + public void onErrorResponse(VolleyError error); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + /** Returns a successful response containing the parsed result. */ | ||
| 42 | + public static <T> Response<T> success(T result, Cache.Entry cacheEntry) { | ||
| 43 | + return new Response<T>(result, cacheEntry); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Returns a failed response containing the given error code and an optional | ||
| 48 | + * localized message displayed to the user. | ||
| 49 | + */ | ||
| 50 | + public static <T> Response<T> error(VolleyError error) { | ||
| 51 | + return new Response<T>(error); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** Parsed response, or null in the case of error. */ | ||
| 55 | + public final T result; | ||
| 56 | + | ||
| 57 | + /** Cache metadata for this response, or null in the case of error. */ | ||
| 58 | + public final Cache.Entry cacheEntry; | ||
| 59 | + | ||
| 60 | + /** Detailed error information if <code>errorCode != OK</code>. */ | ||
| 61 | + public final VolleyError error; | ||
| 62 | + | ||
| 63 | + /** True if this response was a soft-expired one and a second one MAY be coming. */ | ||
| 64 | + public boolean intermediate = false; | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * Returns whether this response is considered successful. | ||
| 68 | + */ | ||
| 69 | + public boolean isSuccess() { | ||
| 70 | + return error == null; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + private Response(T result, Cache.Entry cacheEntry) { | ||
| 75 | + this.result = result; | ||
| 76 | + this.cacheEntry = cacheEntry; | ||
| 77 | + this.error = null; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + private Response(VolleyError error) { | ||
| 81 | + this.result = null; | ||
| 82 | + this.cacheEntry = null; | ||
| 83 | + this.error = error; | ||
| 84 | + } | ||
| 85 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +public interface ResponseDelivery { | ||
| 20 | + /** | ||
| 21 | + * Parses a response from the network or cache and delivers it. | ||
| 22 | + */ | ||
| 23 | + public void postResponse(Request<?> request, Response<?> response); | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * Parses a response from the network or cache and delivers it. The provided | ||
| 27 | + * Runnable will be executed after delivery. | ||
| 28 | + */ | ||
| 29 | + public void postResponse(Request<?> request, Response<?> response, Runnable runnable); | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * Posts an error for the given request. | ||
| 33 | + */ | ||
| 34 | + public void postError(Request<?> request, VolleyError error); | ||
| 35 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Retry policy for a request. | ||
| 21 | + */ | ||
| 22 | +public interface RetryPolicy { | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * Returns the current timeout (used for logging). | ||
| 26 | + */ | ||
| 27 | + public int getCurrentTimeout(); | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * Returns the current retry count (used for logging). | ||
| 31 | + */ | ||
| 32 | + public int getCurrentRetryCount(); | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Prepares for the next retry by applying a backoff to the timeout. | ||
| 36 | + * @param error The error code of the last attempt. | ||
| 37 | + * @throws VolleyError In the event that the retry could not be performed (for example if we | ||
| 38 | + * ran out of attempts), the passed in error is thrown. | ||
| 39 | + */ | ||
| 40 | + public void retry(VolleyError error) throws VolleyError; | ||
| 41 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Indicates that the error responded with an error response. | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class ServerError extends VolleyError { | ||
| 24 | + public ServerError(NetworkResponse networkResponse) { | ||
| 25 | + super(networkResponse); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public ServerError() { | ||
| 29 | + super(); | ||
| 30 | + } | ||
| 31 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Indicates that the connection or the socket timed out. | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class TimeoutError extends VolleyError { } |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * Exception style class encapsulating Volley errors | ||
| 21 | + */ | ||
| 22 | +@SuppressWarnings("serial") | ||
| 23 | +public class VolleyError extends Exception { | ||
| 24 | + public final NetworkResponse networkResponse; | ||
| 25 | + | ||
| 26 | + public VolleyError() { | ||
| 27 | + networkResponse = null; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public VolleyError(NetworkResponse response) { | ||
| 31 | + networkResponse = response; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + public VolleyError(String exceptionMessage) { | ||
| 35 | + super(exceptionMessage); | ||
| 36 | + networkResponse = null; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public VolleyError(String exceptionMessage, Throwable reason) { | ||
| 40 | + super(exceptionMessage, reason); | ||
| 41 | + networkResponse = null; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public VolleyError(Throwable cause) { | ||
| 45 | + super(cause); | ||
| 46 | + networkResponse = null; | ||
| 47 | + } | ||
| 48 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley; | ||
| 18 | + | ||
| 19 | +import android.os.SystemClock; | ||
| 20 | +import android.util.Log; | ||
| 21 | + | ||
| 22 | +import java.util.ArrayList; | ||
| 23 | +import java.util.List; | ||
| 24 | +import java.util.Locale; | ||
| 25 | + | ||
| 26 | +/** Logging helper class. */ | ||
| 27 | +public class VolleyLog { | ||
| 28 | + public static String TAG = "Volley"; | ||
| 29 | + | ||
| 30 | + public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * Customize the log tag for your application, so that other apps | ||
| 34 | + * using Volley don't mix their logs with yours. | ||
| 35 | + * <br /> | ||
| 36 | + * Enable the log property for your tag before starting your app: | ||
| 37 | + * <br /> | ||
| 38 | + * {@code adb shell setprop log.tag.<tag>} | ||
| 39 | + */ | ||
| 40 | + public static void setTag(String tag) { | ||
| 41 | + d("Changing log tag to %s", tag); | ||
| 42 | + TAG = tag; | ||
| 43 | + | ||
| 44 | + // Reinitialize the DEBUG "constant" | ||
| 45 | + DEBUG = Log.isLoggable(TAG, Log.VERBOSE); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public static void v(String format, Object... args) { | ||
| 49 | + if (DEBUG) { | ||
| 50 | + Log.v(TAG, buildMessage(format, args)); | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + public static void d(String format, Object... args) { | ||
| 55 | + Log.d(TAG, buildMessage(format, args)); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + public static void e(String format, Object... args) { | ||
| 59 | + Log.e(TAG, buildMessage(format, args)); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + public static void e(Throwable tr, String format, Object... args) { | ||
| 63 | + Log.e(TAG, buildMessage(format, args), tr); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + public static void wtf(String format, Object... args) { | ||
| 67 | + Log.wtf(TAG, buildMessage(format, args)); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + public static void wtf(Throwable tr, String format, Object... args) { | ||
| 71 | + Log.wtf(TAG, buildMessage(format, args), tr); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * Formats the caller's provided message and prepends useful info like | ||
| 76 | + * calling thread ID and method name. | ||
| 77 | + */ | ||
| 78 | + private static String buildMessage(String format, Object... args) { | ||
| 79 | + String msg = (args == null) ? format : String.format(Locale.US, format, args); | ||
| 80 | + StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace(); | ||
| 81 | + | ||
| 82 | + String caller = "<unknown>"; | ||
| 83 | + // Walk up the stack looking for the first caller outside of VolleyLog. | ||
| 84 | + // It will be at least two frames up, so start there. | ||
| 85 | + for (int i = 2; i < trace.length; i++) { | ||
| 86 | + Class<?> clazz = trace[i].getClass(); | ||
| 87 | + if (!clazz.equals(VolleyLog.class)) { | ||
| 88 | + String callingClass = trace[i].getClassName(); | ||
| 89 | + callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1); | ||
| 90 | + callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1); | ||
| 91 | + | ||
| 92 | + caller = callingClass + "." + trace[i].getMethodName(); | ||
| 93 | + break; | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + return String.format(Locale.US, "[%d] %s: %s", | ||
| 97 | + Thread.currentThread().getId(), caller, msg); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + /** | ||
| 101 | + * A simple event log with records containing a name, thread ID, and timestamp. | ||
| 102 | + */ | ||
| 103 | + static class MarkerLog { | ||
| 104 | + public static final boolean ENABLED = VolleyLog.DEBUG; | ||
| 105 | + | ||
| 106 | + /** Minimum duration from first marker to last in an marker log to warrant logging. */ | ||
| 107 | + private static final long MIN_DURATION_FOR_LOGGING_MS = 0; | ||
| 108 | + | ||
| 109 | + private static class Marker { | ||
| 110 | + public final String name; | ||
| 111 | + public final long thread; | ||
| 112 | + public final long time; | ||
| 113 | + | ||
| 114 | + public Marker(String name, long thread, long time) { | ||
| 115 | + this.name = name; | ||
| 116 | + this.thread = thread; | ||
| 117 | + this.time = time; | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + private final List<Marker> mMarkers = new ArrayList<Marker>(); | ||
| 122 | + private boolean mFinished = false; | ||
| 123 | + | ||
| 124 | + /** Adds a marker to this log with the specified name. */ | ||
| 125 | + public synchronized void add(String name, long threadId) { | ||
| 126 | + if (mFinished) { | ||
| 127 | + throw new IllegalStateException("Marker added to finished log"); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime())); | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + /** | ||
| 134 | + * Closes the log, dumping it to logcat if the time difference between | ||
| 135 | + * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}. | ||
| 136 | + * @param header Header string to print above the marker log. | ||
| 137 | + */ | ||
| 138 | + public synchronized void finish(String header) { | ||
| 139 | + mFinished = true; | ||
| 140 | + | ||
| 141 | + long duration = getTotalDuration(); | ||
| 142 | + if (duration <= MIN_DURATION_FOR_LOGGING_MS) { | ||
| 143 | + return; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + long prevTime = mMarkers.get(0).time; | ||
| 147 | + d("(%-4d ms) %s", duration, header); | ||
| 148 | + for (Marker marker : mMarkers) { | ||
| 149 | + long thisTime = marker.time; | ||
| 150 | + d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name); | ||
| 151 | + prevTime = thisTime; | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + @Override | ||
| 156 | + protected void finalize() throws Throwable { | ||
| 157 | + // Catch requests that have been collected (and hence end-of-lifed) | ||
| 158 | + // but had no debugging output printed for them. | ||
| 159 | + if (!mFinished) { | ||
| 160 | + finish("Request on the loose"); | ||
| 161 | + e("Marker log finalized without finish() - uncaught exit point for request"); | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + /** Returns the time difference between the first and last events in this log. */ | ||
| 166 | + private long getTotalDuration() { | ||
| 167 | + if (mMarkers.size() == 0) { | ||
| 168 | + return 0; | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + long first = mMarkers.get(0).time; | ||
| 172 | + long last = mMarkers.get(mMarkers.size() - 1).time; | ||
| 173 | + return last - first; | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.accounts.Account; | ||
| 20 | +import android.accounts.AccountManager; | ||
| 21 | +import android.accounts.AccountManagerFuture; | ||
| 22 | +import android.content.Context; | ||
| 23 | +import android.content.Intent; | ||
| 24 | +import android.os.Bundle; | ||
| 25 | +import com.android.volley.AuthFailureError; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * An Authenticator that uses {@link AccountManager} to get auth | ||
| 29 | + * tokens of a specified type for a specified account. | ||
| 30 | + */ | ||
| 31 | +public class AndroidAuthenticator implements Authenticator { | ||
| 32 | + private final Context mContext; | ||
| 33 | + private final Account mAccount; | ||
| 34 | + private final String mAuthTokenType; | ||
| 35 | + private final boolean mNotifyAuthFailure; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * Creates a new authenticator. | ||
| 39 | + * @param context Context for accessing AccountManager | ||
| 40 | + * @param account Account to authenticate as | ||
| 41 | + * @param authTokenType Auth token type passed to AccountManager | ||
| 42 | + */ | ||
| 43 | + public AndroidAuthenticator(Context context, Account account, String authTokenType) { | ||
| 44 | + this(context, account, authTokenType, false); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * Creates a new authenticator. | ||
| 49 | + * @param context Context for accessing AccountManager | ||
| 50 | + * @param account Account to authenticate as | ||
| 51 | + * @param authTokenType Auth token type passed to AccountManager | ||
| 52 | + * @param notifyAuthFailure Whether to raise a notification upon auth failure | ||
| 53 | + */ | ||
| 54 | + public AndroidAuthenticator(Context context, Account account, String authTokenType, | ||
| 55 | + boolean notifyAuthFailure) { | ||
| 56 | + mContext = context; | ||
| 57 | + mAccount = account; | ||
| 58 | + mAuthTokenType = authTokenType; | ||
| 59 | + mNotifyAuthFailure = notifyAuthFailure; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * Returns the Account being used by this authenticator. | ||
| 64 | + */ | ||
| 65 | + public Account getAccount() { | ||
| 66 | + return mAccount; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + @Override | ||
| 70 | + public String getAuthToken() throws AuthFailureError { | ||
| 71 | + final AccountManager accountManager = AccountManager.get(mContext); | ||
| 72 | + AccountManagerFuture<Bundle> future = accountManager.getAuthToken(mAccount, | ||
| 73 | + mAuthTokenType, mNotifyAuthFailure, null, null); | ||
| 74 | + Bundle result; | ||
| 75 | + try { | ||
| 76 | + result = future.getResult(); | ||
| 77 | + } catch (Exception e) { | ||
| 78 | + throw new AuthFailureError("Error while retrieving auth token", e); | ||
| 79 | + } | ||
| 80 | + String authToken = null; | ||
| 81 | + if (future.isDone() && !future.isCancelled()) { | ||
| 82 | + if (result.containsKey(AccountManager.KEY_INTENT)) { | ||
| 83 | + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); | ||
| 84 | + throw new AuthFailureError(intent); | ||
| 85 | + } | ||
| 86 | + authToken = result.getString(AccountManager.KEY_AUTHTOKEN); | ||
| 87 | + } | ||
| 88 | + if (authToken == null) { | ||
| 89 | + throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + return authToken; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Override | ||
| 96 | + public void invalidateAuthToken(String authToken) { | ||
| 97 | + AccountManager.get(mContext).invalidateAuthToken(mAccount.type, authToken); | ||
| 98 | + } | ||
| 99 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.AuthFailureError; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * An interface for interacting with auth tokens. | ||
| 23 | + */ | ||
| 24 | +public interface Authenticator { | ||
| 25 | + /** | ||
| 26 | + * Synchronously retrieves an auth token. | ||
| 27 | + * | ||
| 28 | + * @throws AuthFailureError If authentication did not succeed | ||
| 29 | + */ | ||
| 30 | + public String getAuthToken() throws AuthFailureError; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * Invalidates the provided auth token. | ||
| 34 | + */ | ||
| 35 | + public void invalidateAuthToken(String authToken); | ||
| 36 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.os.SystemClock; | ||
| 20 | +import com.android.volley.*; | ||
| 21 | +import org.apache.http.*; | ||
| 22 | +import org.apache.http.conn.ConnectTimeoutException; | ||
| 23 | +import org.apache.http.impl.cookie.DateUtils; | ||
| 24 | + | ||
| 25 | +import java.io.IOException; | ||
| 26 | +import java.io.InputStream; | ||
| 27 | +import java.net.MalformedURLException; | ||
| 28 | +import java.net.SocketTimeoutException; | ||
| 29 | +import java.util.Date; | ||
| 30 | +import java.util.HashMap; | ||
| 31 | +import java.util.Map; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * A network performing Volley requests over an {@link HttpStack}. | ||
| 35 | + */ | ||
| 36 | +public class BasicNetwork implements Network { | ||
| 37 | + protected static final boolean DEBUG = VolleyLog.DEBUG; | ||
| 38 | + | ||
| 39 | + private static int SLOW_REQUEST_THRESHOLD_MS = 3000; | ||
| 40 | + | ||
| 41 | + private static int DEFAULT_POOL_SIZE = 4096; | ||
| 42 | + | ||
| 43 | + protected final HttpStack mHttpStack; | ||
| 44 | + | ||
| 45 | + protected final ByteArrayPool mPool; | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * @param httpStack HTTP stack to be used | ||
| 49 | + */ | ||
| 50 | + public BasicNetwork(HttpStack httpStack) { | ||
| 51 | + // If a pool isn't passed in, then build a small default pool that will give us a lot of | ||
| 52 | + // benefit and not use too much memory. | ||
| 53 | + this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * @param httpStack HTTP stack to be used | ||
| 58 | + * @param pool a buffer pool that improves GC performance in copy operations | ||
| 59 | + */ | ||
| 60 | + public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { | ||
| 61 | + mHttpStack = httpStack; | ||
| 62 | + mPool = pool; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + @Override | ||
| 66 | + public NetworkResponse performRequest(Request<?> request) throws VolleyError { | ||
| 67 | + long requestStart = SystemClock.elapsedRealtime(); | ||
| 68 | + while (true) { | ||
| 69 | + HttpResponse httpResponse = null; | ||
| 70 | + byte[] responseContents = null; | ||
| 71 | + Map<String, String> responseHeaders = new HashMap<String, String>(); | ||
| 72 | + try { | ||
| 73 | + // Gather headers. | ||
| 74 | + Map<String, String> headers = new HashMap<String, String>(); | ||
| 75 | + addCacheHeaders(headers, request.getCacheEntry()); | ||
| 76 | + httpResponse = mHttpStack.performRequest(request, headers); | ||
| 77 | + StatusLine statusLine = httpResponse.getStatusLine(); | ||
| 78 | + int statusCode = statusLine.getStatusCode(); | ||
| 79 | + | ||
| 80 | + responseHeaders = convertHeaders(httpResponse.getAllHeaders()); | ||
| 81 | + // Handle cache validation. | ||
| 82 | + if (statusCode == HttpStatus.SC_NOT_MODIFIED) { | ||
| 83 | + return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, | ||
| 84 | + request.getCacheEntry().data, responseHeaders, true); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + // Some responses such as 204s do not have content. We must check. | ||
| 88 | + if (httpResponse.getEntity() != null) { | ||
| 89 | + responseContents = entityToBytes(httpResponse.getEntity()); | ||
| 90 | + } else { | ||
| 91 | + // Add 0 byte response as a way of honestly representing a | ||
| 92 | + // no-content request. | ||
| 93 | + responseContents = new byte[0]; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + // if the request is slow, log it. | ||
| 97 | + long requestLifetime = SystemClock.elapsedRealtime() - requestStart; | ||
| 98 | + logSlowRequests(requestLifetime, request, responseContents, statusLine); | ||
| 99 | + | ||
| 100 | + if (statusCode < 200 || statusCode > 299) { | ||
| 101 | + throw new IOException(); | ||
| 102 | + } | ||
| 103 | + return new NetworkResponse(statusCode, responseContents, responseHeaders, false); | ||
| 104 | + } catch (SocketTimeoutException e) { | ||
| 105 | + attemptRetryOnException("socket", request, new TimeoutError()); | ||
| 106 | + } catch (ConnectTimeoutException e) { | ||
| 107 | + attemptRetryOnException("connection", request, new TimeoutError()); | ||
| 108 | + } catch (MalformedURLException e) { | ||
| 109 | + throw new RuntimeException("Bad URL " + request.getUrl(), e); | ||
| 110 | + } catch (IOException e) { | ||
| 111 | + int statusCode = 0; | ||
| 112 | + NetworkResponse networkResponse = null; | ||
| 113 | + if (httpResponse != null) { | ||
| 114 | + statusCode = httpResponse.getStatusLine().getStatusCode(); | ||
| 115 | + } else { | ||
| 116 | + throw new NoConnectionError(e); | ||
| 117 | + } | ||
| 118 | + VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); | ||
| 119 | + if (responseContents != null) { | ||
| 120 | + networkResponse = new NetworkResponse(statusCode, responseContents, | ||
| 121 | + responseHeaders, false); | ||
| 122 | + if (statusCode == HttpStatus.SC_UNAUTHORIZED || | ||
| 123 | + statusCode == HttpStatus.SC_FORBIDDEN) { | ||
| 124 | + attemptRetryOnException("auth", | ||
| 125 | + request, new AuthFailureError(networkResponse)); | ||
| 126 | + } else { | ||
| 127 | + // TODO: Only throw ServerError for 5xx status codes. | ||
| 128 | + throw new ServerError(networkResponse); | ||
| 129 | + } | ||
| 130 | + } else { | ||
| 131 | + throw new NetworkError(networkResponse); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + /** | ||
| 138 | + * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. | ||
| 139 | + */ | ||
| 140 | + private void logSlowRequests(long requestLifetime, Request<?> request, | ||
| 141 | + byte[] responseContents, StatusLine statusLine) { | ||
| 142 | + if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { | ||
| 143 | + VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + | ||
| 144 | + "[rc=%d], [retryCount=%s]", request, requestLifetime, | ||
| 145 | + responseContents != null ? responseContents.length : "null", | ||
| 146 | + statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + /** | ||
| 151 | + * Attempts to prepare the request for a retry. If there are no more attempts remaining in the | ||
| 152 | + * request's retry policy, a timeout exception is thrown. | ||
| 153 | + * @param request The request to use. | ||
| 154 | + */ | ||
| 155 | + private static void attemptRetryOnException(String logPrefix, Request<?> request, | ||
| 156 | + VolleyError exception) throws VolleyError { | ||
| 157 | + RetryPolicy retryPolicy = request.getRetryPolicy(); | ||
| 158 | + int oldTimeout = request.getTimeoutMs(); | ||
| 159 | + | ||
| 160 | + try { | ||
| 161 | + retryPolicy.retry(exception); | ||
| 162 | + } catch (VolleyError e) { | ||
| 163 | + request.addMarker( | ||
| 164 | + String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); | ||
| 165 | + throw e; | ||
| 166 | + } | ||
| 167 | + request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { | ||
| 171 | + // If there's no cache entry, we're done. | ||
| 172 | + if (entry == null) { | ||
| 173 | + return; | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + if (entry.etag != null) { | ||
| 177 | + headers.put("If-None-Match", entry.etag); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + if (entry.serverDate > 0) { | ||
| 181 | + Date refTime = new Date(entry.serverDate); | ||
| 182 | + headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + protected void logError(String what, String url, long start) { | ||
| 187 | + long now = SystemClock.elapsedRealtime(); | ||
| 188 | + VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** Reads the contents of HttpEntity into a byte[]. */ | ||
| 192 | + private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { | ||
| 193 | + PoolingByteArrayOutputStream bytes = | ||
| 194 | + new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); | ||
| 195 | + byte[] buffer = null; | ||
| 196 | + try { | ||
| 197 | + InputStream in = entity.getContent(); | ||
| 198 | + if (in == null) { | ||
| 199 | + throw new ServerError(); | ||
| 200 | + } | ||
| 201 | + buffer = mPool.getBuf(1024); | ||
| 202 | + int count; | ||
| 203 | + while ((count = in.read(buffer)) != -1) { | ||
| 204 | + bytes.write(buffer, 0, count); | ||
| 205 | + } | ||
| 206 | + return bytes.toByteArray(); | ||
| 207 | + } finally { | ||
| 208 | + try { | ||
| 209 | + // Close the InputStream and release the resources by "consuming the content". | ||
| 210 | + entity.consumeContent(); | ||
| 211 | + } catch (IOException e) { | ||
| 212 | + // This can happen if there was an exception above that left the entity in | ||
| 213 | + // an invalid state. | ||
| 214 | + VolleyLog.v("Error occured when calling consumingContent"); | ||
| 215 | + } | ||
| 216 | + mPool.returnBuf(buffer); | ||
| 217 | + bytes.close(); | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + /** | ||
| 222 | + * Converts Headers[] to Map<String, String>. | ||
| 223 | + */ | ||
| 224 | + private static Map<String, String> convertHeaders(Header[] headers) { | ||
| 225 | + Map<String, String> result = new HashMap<String, String>(); | ||
| 226 | + for (int i = 0; i < headers.length; i++) { | ||
| 227 | + result.put(headers[i].getName(), headers[i].getValue()); | ||
| 228 | + } | ||
| 229 | + return result; | ||
| 230 | + } | ||
| 231 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2012 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import java.util.*; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * ByteArrayPool is a source and repository of <code>byte[]</code> objects. Its purpose is to | ||
| 23 | + * supply those buffers to consumers who need to use them for a short period of time and then | ||
| 24 | + * dispose of them. Simply creating and disposing such buffers in the conventional manner can | ||
| 25 | + * considerable heap churn and garbage collection delays on Android, which lacks good management of | ||
| 26 | + * short-lived heap objects. It may be advantageous to trade off some memory in the form of a | ||
| 27 | + * permanently allocated pool of buffers in order to gain heap performance improvements; that is | ||
| 28 | + * what this class does. | ||
| 29 | + * <p> | ||
| 30 | + * A good candidate user for this class is something like an I/O system that uses large temporary | ||
| 31 | + * <code>byte[]</code> buffers to copy data around. In these use cases, often the consumer wants | ||
| 32 | + * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks | ||
| 33 | + * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into | ||
| 34 | + * account and also to maximize the odds of being able to reuse a recycled buffer, this class is | ||
| 35 | + * free to return buffers larger than the requested size. The caller needs to be able to gracefully | ||
| 36 | + * deal with getting buffers any size over the minimum. | ||
| 37 | + * <p> | ||
| 38 | + * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this | ||
| 39 | + * class will allocate a new buffer and return it. | ||
| 40 | + * <p> | ||
| 41 | + * This class has no special ownership of buffers it creates; the caller is free to take a buffer | ||
| 42 | + * it receives from this pool, use it permanently, and never return it to the pool; additionally, | ||
| 43 | + * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there | ||
| 44 | + * are no other lingering references to it. | ||
| 45 | + * <p> | ||
| 46 | + * This class ensures that the total size of the buffers in its recycling pool never exceeds a | ||
| 47 | + * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit, | ||
| 48 | + * least-recently-used buffers are disposed. | ||
| 49 | + */ | ||
| 50 | +public class ByteArrayPool { | ||
| 51 | + /** The buffer pool, arranged both by last use and by buffer size */ | ||
| 52 | + private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>(); | ||
| 53 | + private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64); | ||
| 54 | + | ||
| 55 | + /** The total size of the buffers in the pool */ | ||
| 56 | + private int mCurrentSize = 0; | ||
| 57 | + | ||
| 58 | + /** | ||
| 59 | + * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay | ||
| 60 | + * under this limit. | ||
| 61 | + */ | ||
| 62 | + private final int mSizeLimit; | ||
| 63 | + | ||
| 64 | + /** Compares buffers by size */ | ||
| 65 | + protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() { | ||
| 66 | + @Override | ||
| 67 | + public int compare(byte[] lhs, byte[] rhs) { | ||
| 68 | + return lhs.length - rhs.length; | ||
| 69 | + } | ||
| 70 | + }; | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * @param sizeLimit the maximum size of the pool, in bytes | ||
| 74 | + */ | ||
| 75 | + public ByteArrayPool(int sizeLimit) { | ||
| 76 | + mSizeLimit = sizeLimit; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Returns a buffer from the pool if one is available in the requested size, or allocates a new | ||
| 81 | + * one if a pooled one is not available. | ||
| 82 | + * | ||
| 83 | + * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be | ||
| 84 | + * larger. | ||
| 85 | + * @return a byte[] buffer is always returned. | ||
| 86 | + */ | ||
| 87 | + public synchronized byte[] getBuf(int len) { | ||
| 88 | + for (int i = 0; i < mBuffersBySize.size(); i++) { | ||
| 89 | + byte[] buf = mBuffersBySize.get(i); | ||
| 90 | + if (buf.length >= len) { | ||
| 91 | + mCurrentSize -= buf.length; | ||
| 92 | + mBuffersBySize.remove(i); | ||
| 93 | + mBuffersByLastUse.remove(buf); | ||
| 94 | + return buf; | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + return new byte[len]; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + /** | ||
| 101 | + * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted | ||
| 102 | + * size. | ||
| 103 | + * | ||
| 104 | + * @param buf the buffer to return to the pool. | ||
| 105 | + */ | ||
| 106 | + public synchronized void returnBuf(byte[] buf) { | ||
| 107 | + if (buf == null || buf.length > mSizeLimit) { | ||
| 108 | + return; | ||
| 109 | + } | ||
| 110 | + mBuffersByLastUse.add(buf); | ||
| 111 | + int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); | ||
| 112 | + if (pos < 0) { | ||
| 113 | + pos = -pos - 1; | ||
| 114 | + } | ||
| 115 | + mBuffersBySize.add(pos, buf); | ||
| 116 | + mCurrentSize += buf.length; | ||
| 117 | + trim(); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + /** | ||
| 121 | + * Removes buffers from the pool until it is under its size limit. | ||
| 122 | + */ | ||
| 123 | + private synchronized void trim() { | ||
| 124 | + while (mCurrentSize > mSizeLimit) { | ||
| 125 | + byte[] buf = mBuffersByLastUse.remove(0); | ||
| 126 | + mBuffersBySize.remove(buf); | ||
| 127 | + mCurrentSize -= buf.length; | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.os.Handler; | ||
| 20 | +import android.os.Looper; | ||
| 21 | +import com.android.volley.Cache; | ||
| 22 | +import com.android.volley.NetworkResponse; | ||
| 23 | +import com.android.volley.Request; | ||
| 24 | +import com.android.volley.Response; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * A synthetic request used for clearing the cache. | ||
| 28 | + */ | ||
| 29 | +public class ClearCacheRequest extends Request<Object> { | ||
| 30 | + private final Cache mCache; | ||
| 31 | + private final Runnable mCallback; | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Creates a synthetic request for clearing the cache. | ||
| 35 | + * @param cache Cache to clear | ||
| 36 | + * @param callback Callback to make on the main thread once the cache is clear, | ||
| 37 | + * or null for none | ||
| 38 | + */ | ||
| 39 | + public ClearCacheRequest(Cache cache, Runnable callback) { | ||
| 40 | + super(Method.GET, null, null); | ||
| 41 | + mCache = cache; | ||
| 42 | + mCallback = callback; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Override | ||
| 46 | + public boolean isCanceled() { | ||
| 47 | + // This is a little bit of a hack, but hey, why not. | ||
| 48 | + mCache.clear(); | ||
| 49 | + if (mCallback != null) { | ||
| 50 | + Handler handler = new Handler(Looper.getMainLooper()); | ||
| 51 | + handler.postAtFrontOfQueue(mCallback); | ||
| 52 | + } | ||
| 53 | + return true; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Override | ||
| 57 | + public Priority getPriority() { | ||
| 58 | + return Priority.IMMEDIATE; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Override | ||
| 62 | + protected Response<Object> parseNetworkResponse(NetworkResponse response) { | ||
| 63 | + return null; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + @Override | ||
| 67 | + protected void deliverResponse(Object response) { | ||
| 68 | + } | ||
| 69 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.os.SystemClock; | ||
| 20 | +import com.android.volley.Cache; | ||
| 21 | +import com.android.volley.VolleyLog; | ||
| 22 | + | ||
| 23 | +import java.io.*; | ||
| 24 | +import java.util.*; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * Cache implementation that caches files directly onto the hard disk in the specified | ||
| 28 | + * directory. The default disk usage size is 5MB, but is configurable. | ||
| 29 | + */ | ||
| 30 | +public class DiskBasedCache implements Cache { | ||
| 31 | + | ||
| 32 | + /** Map of the Key, CacheHeader pairs */ | ||
| 33 | + private final Map<String, CacheHeader> mEntries = | ||
| 34 | + new LinkedHashMap<String, CacheHeader>(16, .75f, true); | ||
| 35 | + | ||
| 36 | + /** Total amount of space currently used by the cache in bytes. */ | ||
| 37 | + private long mTotalSize = 0; | ||
| 38 | + | ||
| 39 | + /** The root directory to use for the cache. */ | ||
| 40 | + private final File mRootDirectory; | ||
| 41 | + | ||
| 42 | + /** The maximum size of the cache in bytes. */ | ||
| 43 | + private final int mMaxCacheSizeInBytes; | ||
| 44 | + | ||
| 45 | + /** Default maximum disk usage in bytes. */ | ||
| 46 | + private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; | ||
| 47 | + | ||
| 48 | + /** High water mark percentage for the cache */ | ||
| 49 | + private static final float HYSTERESIS_FACTOR = 0.9f; | ||
| 50 | + | ||
| 51 | + /** Magic number for current version of cache file format. */ | ||
| 52 | + private static final int CACHE_MAGIC = 0x20120504; | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * Constructs an instance of the DiskBasedCache at the specified directory. | ||
| 56 | + * @param rootDirectory The root directory of the cache. | ||
| 57 | + * @param maxCacheSizeInBytes The maximum size of the cache in bytes. | ||
| 58 | + */ | ||
| 59 | + public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { | ||
| 60 | + mRootDirectory = rootDirectory; | ||
| 61 | + mMaxCacheSizeInBytes = maxCacheSizeInBytes; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Constructs an instance of the DiskBasedCache at the specified directory using | ||
| 66 | + * the default maximum cache size of 5MB. | ||
| 67 | + * @param rootDirectory The root directory of the cache. | ||
| 68 | + */ | ||
| 69 | + public DiskBasedCache(File rootDirectory) { | ||
| 70 | + this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * Clears the cache. Deletes all cached files from disk. | ||
| 75 | + */ | ||
| 76 | + @Override | ||
| 77 | + public synchronized void clear() { | ||
| 78 | + File[] files = mRootDirectory.listFiles(); | ||
| 79 | + if (files != null) { | ||
| 80 | + for (File file : files) { | ||
| 81 | + file.delete(); | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | + mEntries.clear(); | ||
| 85 | + mTotalSize = 0; | ||
| 86 | + VolleyLog.d("Cache cleared."); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * Returns the cache entry with the specified key if it exists, null otherwise. | ||
| 91 | + */ | ||
| 92 | + @Override | ||
| 93 | + public synchronized Entry get(String key) { | ||
| 94 | + CacheHeader entry = mEntries.get(key); | ||
| 95 | + // if the entry does not exist, return. | ||
| 96 | + if (entry == null) { | ||
| 97 | + return null; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + File file = getFileForKey(key); | ||
| 101 | + CountingInputStream cis = null; | ||
| 102 | + try { | ||
| 103 | + cis = new CountingInputStream(new FileInputStream(file)); | ||
| 104 | + CacheHeader.readHeader(cis); // eat header | ||
| 105 | + byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); | ||
| 106 | + return entry.toCacheEntry(data); | ||
| 107 | + } catch (IOException e) { | ||
| 108 | + VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); | ||
| 109 | + remove(key); | ||
| 110 | + return null; | ||
| 111 | + } finally { | ||
| 112 | + if (cis != null) { | ||
| 113 | + try { | ||
| 114 | + cis.close(); | ||
| 115 | + } catch (IOException ioe) { | ||
| 116 | + return null; | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + /** | ||
| 123 | + * Initializes the DiskBasedCache by scanning for all files currently in the | ||
| 124 | + * specified root directory. Creates the root directory if necessary. | ||
| 125 | + */ | ||
| 126 | + @Override | ||
| 127 | + public synchronized void initialize() { | ||
| 128 | + if (!mRootDirectory.exists()) { | ||
| 129 | + if (!mRootDirectory.mkdirs()) { | ||
| 130 | + VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); | ||
| 131 | + } | ||
| 132 | + return; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + File[] files = mRootDirectory.listFiles(); | ||
| 136 | + if (files == null) { | ||
| 137 | + return; | ||
| 138 | + } | ||
| 139 | + for (File file : files) { | ||
| 140 | + FileInputStream fis = null; | ||
| 141 | + try { | ||
| 142 | + fis = new FileInputStream(file); | ||
| 143 | + CacheHeader entry = CacheHeader.readHeader(fis); | ||
| 144 | + entry.size = file.length(); | ||
| 145 | + putEntry(entry.key, entry); | ||
| 146 | + } catch (IOException e) { | ||
| 147 | + if (file != null) { | ||
| 148 | + file.delete(); | ||
| 149 | + } | ||
| 150 | + } finally { | ||
| 151 | + try { | ||
| 152 | + if (fis != null) { | ||
| 153 | + fis.close(); | ||
| 154 | + } | ||
| 155 | + } catch (IOException ignored) { } | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + /** | ||
| 161 | + * Invalidates an entry in the cache. | ||
| 162 | + * @param key Cache key | ||
| 163 | + * @param fullExpire True to fully expire the entry, false to soft expire | ||
| 164 | + */ | ||
| 165 | + @Override | ||
| 166 | + public synchronized void invalidate(String key, boolean fullExpire) { | ||
| 167 | + Entry entry = get(key); | ||
| 168 | + if (entry != null) { | ||
| 169 | + entry.softTtl = 0; | ||
| 170 | + if (fullExpire) { | ||
| 171 | + entry.ttl = 0; | ||
| 172 | + } | ||
| 173 | + put(key, entry); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + /** | ||
| 179 | + * Puts the entry with the specified key into the cache. | ||
| 180 | + */ | ||
| 181 | + @Override | ||
| 182 | + public synchronized void put(String key, Entry entry) { | ||
| 183 | + pruneIfNeeded(entry.data.length); | ||
| 184 | + File file = getFileForKey(key); | ||
| 185 | + try { | ||
| 186 | + FileOutputStream fos = new FileOutputStream(file); | ||
| 187 | + CacheHeader e = new CacheHeader(key, entry); | ||
| 188 | + e.writeHeader(fos); | ||
| 189 | + fos.write(entry.data); | ||
| 190 | + fos.close(); | ||
| 191 | + putEntry(key, e); | ||
| 192 | + return; | ||
| 193 | + } catch (IOException e) { | ||
| 194 | + } | ||
| 195 | + boolean deleted = file.delete(); | ||
| 196 | + if (!deleted) { | ||
| 197 | + VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + /** | ||
| 202 | + * Removes the specified key from the cache if it exists. | ||
| 203 | + */ | ||
| 204 | + @Override | ||
| 205 | + public synchronized void remove(String key) { | ||
| 206 | + boolean deleted = getFileForKey(key).delete(); | ||
| 207 | + removeEntry(key); | ||
| 208 | + if (!deleted) { | ||
| 209 | + VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", | ||
| 210 | + key, getFilenameForKey(key)); | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + /** | ||
| 215 | + * Creates a pseudo-unique filename for the specified cache key. | ||
| 216 | + * @param key The key to generate a file name for. | ||
| 217 | + * @return A pseudo-unique filename. | ||
| 218 | + */ | ||
| 219 | + private String getFilenameForKey(String key) { | ||
| 220 | + int firstHalfLength = key.length() / 2; | ||
| 221 | + String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); | ||
| 222 | + localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); | ||
| 223 | + return localFilename; | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + /** | ||
| 227 | + * Returns a file object for the given cache key. | ||
| 228 | + */ | ||
| 229 | + public File getFileForKey(String key) { | ||
| 230 | + return new File(mRootDirectory, getFilenameForKey(key)); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + /** | ||
| 234 | + * Prunes the cache to fit the amount of bytes specified. | ||
| 235 | + * @param neededSpace The amount of bytes we are trying to fit into the cache. | ||
| 236 | + */ | ||
| 237 | + private void pruneIfNeeded(int neededSpace) { | ||
| 238 | + if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { | ||
| 239 | + return; | ||
| 240 | + } | ||
| 241 | + if (VolleyLog.DEBUG) { | ||
| 242 | + VolleyLog.v("Pruning old cache entries."); | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + long before = mTotalSize; | ||
| 246 | + int prunedFiles = 0; | ||
| 247 | + long startTime = SystemClock.elapsedRealtime(); | ||
| 248 | + | ||
| 249 | + Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); | ||
| 250 | + while (iterator.hasNext()) { | ||
| 251 | + Map.Entry<String, CacheHeader> entry = iterator.next(); | ||
| 252 | + CacheHeader e = entry.getValue(); | ||
| 253 | + boolean deleted = getFileForKey(e.key).delete(); | ||
| 254 | + if (deleted) { | ||
| 255 | + mTotalSize -= e.size; | ||
| 256 | + } else { | ||
| 257 | + VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", | ||
| 258 | + e.key, getFilenameForKey(e.key)); | ||
| 259 | + } | ||
| 260 | + iterator.remove(); | ||
| 261 | + prunedFiles++; | ||
| 262 | + | ||
| 263 | + if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { | ||
| 264 | + break; | ||
| 265 | + } | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + if (VolleyLog.DEBUG) { | ||
| 269 | + VolleyLog.v("pruned %d files, %d bytes, %d ms", | ||
| 270 | + prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); | ||
| 271 | + } | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + /** | ||
| 275 | + * Puts the entry with the specified key into the cache. | ||
| 276 | + * @param key The key to identify the entry by. | ||
| 277 | + * @param entry The entry to cache. | ||
| 278 | + */ | ||
| 279 | + private void putEntry(String key, CacheHeader entry) { | ||
| 280 | + if (!mEntries.containsKey(key)) { | ||
| 281 | + mTotalSize += entry.size; | ||
| 282 | + } else { | ||
| 283 | + CacheHeader oldEntry = mEntries.get(key); | ||
| 284 | + mTotalSize += (entry.size - oldEntry.size); | ||
| 285 | + } | ||
| 286 | + mEntries.put(key, entry); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + /** | ||
| 290 | + * Removes the entry identified by 'key' from the cache. | ||
| 291 | + */ | ||
| 292 | + private void removeEntry(String key) { | ||
| 293 | + CacheHeader entry = mEntries.get(key); | ||
| 294 | + if (entry != null) { | ||
| 295 | + mTotalSize -= entry.size; | ||
| 296 | + mEntries.remove(key); | ||
| 297 | + } | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + /** | ||
| 301 | + * Reads the contents of an InputStream into a byte[]. | ||
| 302 | + * */ | ||
| 303 | + private static byte[] streamToBytes(InputStream in, int length) throws IOException { | ||
| 304 | + byte[] bytes = new byte[length]; | ||
| 305 | + int count; | ||
| 306 | + int pos = 0; | ||
| 307 | + while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { | ||
| 308 | + pos += count; | ||
| 309 | + } | ||
| 310 | + if (pos != length) { | ||
| 311 | + throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); | ||
| 312 | + } | ||
| 313 | + return bytes; | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + /** | ||
| 317 | + * Handles holding onto the cache headers for an entry. | ||
| 318 | + */ | ||
| 319 | + // Visible for testing. | ||
| 320 | + static class CacheHeader { | ||
| 321 | + /** The size of the data identified by this CacheHeader. (This is not | ||
| 322 | + * serialized to disk. */ | ||
| 323 | + public long size; | ||
| 324 | + | ||
| 325 | + /** The key that identifies the cache entry. */ | ||
| 326 | + public String key; | ||
| 327 | + | ||
| 328 | + /** ETag for cache coherence. */ | ||
| 329 | + public String etag; | ||
| 330 | + | ||
| 331 | + /** Date of this response as reported by the server. */ | ||
| 332 | + public long serverDate; | ||
| 333 | + | ||
| 334 | + /** TTL for this record. */ | ||
| 335 | + public long ttl; | ||
| 336 | + | ||
| 337 | + /** Soft TTL for this record. */ | ||
| 338 | + public long softTtl; | ||
| 339 | + | ||
| 340 | + /** Headers from the response resulting in this cache entry. */ | ||
| 341 | + public Map<String, String> responseHeaders; | ||
| 342 | + | ||
| 343 | + private CacheHeader() { } | ||
| 344 | + | ||
| 345 | + /** | ||
| 346 | + * Instantiates a new CacheHeader object | ||
| 347 | + * @param key The key that identifies the cache entry | ||
| 348 | + * @param entry The cache entry. | ||
| 349 | + */ | ||
| 350 | + public CacheHeader(String key, Entry entry) { | ||
| 351 | + this.key = key; | ||
| 352 | + this.size = entry.data.length; | ||
| 353 | + this.etag = entry.etag; | ||
| 354 | + this.serverDate = entry.serverDate; | ||
| 355 | + this.ttl = entry.ttl; | ||
| 356 | + this.softTtl = entry.softTtl; | ||
| 357 | + this.responseHeaders = entry.responseHeaders; | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + /** | ||
| 361 | + * Reads the header off of an InputStream and returns a CacheHeader object. | ||
| 362 | + * @param is The InputStream to read from. | ||
| 363 | + * @throws IOException | ||
| 364 | + */ | ||
| 365 | + public static CacheHeader readHeader(InputStream is) throws IOException { | ||
| 366 | + CacheHeader entry = new CacheHeader(); | ||
| 367 | + int magic = readInt(is); | ||
| 368 | + if (magic != CACHE_MAGIC) { | ||
| 369 | + // don't bother deleting, it'll get pruned eventually | ||
| 370 | + throw new IOException(); | ||
| 371 | + } | ||
| 372 | + entry.key = readString(is); | ||
| 373 | + entry.etag = readString(is); | ||
| 374 | + if (entry.etag.equals("")) { | ||
| 375 | + entry.etag = null; | ||
| 376 | + } | ||
| 377 | + entry.serverDate = readLong(is); | ||
| 378 | + entry.ttl = readLong(is); | ||
| 379 | + entry.softTtl = readLong(is); | ||
| 380 | + entry.responseHeaders = readStringStringMap(is); | ||
| 381 | + return entry; | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + /** | ||
| 385 | + * Creates a cache entry for the specified data. | ||
| 386 | + */ | ||
| 387 | + public Entry toCacheEntry(byte[] data) { | ||
| 388 | + Entry e = new Entry(); | ||
| 389 | + e.data = data; | ||
| 390 | + e.etag = etag; | ||
| 391 | + e.serverDate = serverDate; | ||
| 392 | + e.ttl = ttl; | ||
| 393 | + e.softTtl = softTtl; | ||
| 394 | + e.responseHeaders = responseHeaders; | ||
| 395 | + return e; | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + | ||
| 399 | + /** | ||
| 400 | + * Writes the contents of this CacheHeader to the specified OutputStream. | ||
| 401 | + */ | ||
| 402 | + public boolean writeHeader(OutputStream os) { | ||
| 403 | + try { | ||
| 404 | + writeInt(os, CACHE_MAGIC); | ||
| 405 | + writeString(os, key); | ||
| 406 | + writeString(os, etag == null ? "" : etag); | ||
| 407 | + writeLong(os, serverDate); | ||
| 408 | + writeLong(os, ttl); | ||
| 409 | + writeLong(os, softTtl); | ||
| 410 | + writeStringStringMap(responseHeaders, os); | ||
| 411 | + os.flush(); | ||
| 412 | + return true; | ||
| 413 | + } catch (IOException e) { | ||
| 414 | + VolleyLog.d("%s", e.toString()); | ||
| 415 | + return false; | ||
| 416 | + } | ||
| 417 | + } | ||
| 418 | + | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + private static class CountingInputStream extends FilterInputStream { | ||
| 422 | + private int bytesRead = 0; | ||
| 423 | + | ||
| 424 | + private CountingInputStream(InputStream in) { | ||
| 425 | + super(in); | ||
| 426 | + } | ||
| 427 | + | ||
| 428 | + @Override | ||
| 429 | + public int read() throws IOException { | ||
| 430 | + int result = super.read(); | ||
| 431 | + if (result != -1) { | ||
| 432 | + bytesRead++; | ||
| 433 | + } | ||
| 434 | + return result; | ||
| 435 | + } | ||
| 436 | + | ||
| 437 | + @Override | ||
| 438 | + public int read(byte[] buffer, int offset, int count) throws IOException { | ||
| 439 | + int result = super.read(buffer, offset, count); | ||
| 440 | + if (result != -1) { | ||
| 441 | + bytesRead += result; | ||
| 442 | + } | ||
| 443 | + return result; | ||
| 444 | + } | ||
| 445 | + } | ||
| 446 | + | ||
| 447 | + /* | ||
| 448 | + * Homebrewed simple serialization system used for reading and writing cache | ||
| 449 | + * headers on disk. Once upon a time, this used the standard Java | ||
| 450 | + * Object{Input,Output}Stream, but the default implementation relies heavily | ||
| 451 | + * on reflection (even for standard types) and generates a ton of garbage. | ||
| 452 | + */ | ||
| 453 | + | ||
| 454 | + /** | ||
| 455 | + * Simple wrapper around {@link InputStream#read()} that throws EOFException | ||
| 456 | + * instead of returning -1. | ||
| 457 | + */ | ||
| 458 | + private static int read(InputStream is) throws IOException { | ||
| 459 | + int b = is.read(); | ||
| 460 | + if (b == -1) { | ||
| 461 | + throw new EOFException(); | ||
| 462 | + } | ||
| 463 | + return b; | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + static void writeInt(OutputStream os, int n) throws IOException { | ||
| 467 | + os.write((n >> 0) & 0xff); | ||
| 468 | + os.write((n >> 8) & 0xff); | ||
| 469 | + os.write((n >> 16) & 0xff); | ||
| 470 | + os.write((n >> 24) & 0xff); | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + static int readInt(InputStream is) throws IOException { | ||
| 474 | + int n = 0; | ||
| 475 | + n |= (read(is) << 0); | ||
| 476 | + n |= (read(is) << 8); | ||
| 477 | + n |= (read(is) << 16); | ||
| 478 | + n |= (read(is) << 24); | ||
| 479 | + return n; | ||
| 480 | + } | ||
| 481 | + | ||
| 482 | + static void writeLong(OutputStream os, long n) throws IOException { | ||
| 483 | + os.write((byte)(n >>> 0)); | ||
| 484 | + os.write((byte)(n >>> 8)); | ||
| 485 | + os.write((byte)(n >>> 16)); | ||
| 486 | + os.write((byte)(n >>> 24)); | ||
| 487 | + os.write((byte)(n >>> 32)); | ||
| 488 | + os.write((byte)(n >>> 40)); | ||
| 489 | + os.write((byte)(n >>> 48)); | ||
| 490 | + os.write((byte)(n >>> 56)); | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + static long readLong(InputStream is) throws IOException { | ||
| 494 | + long n = 0; | ||
| 495 | + n |= ((read(is) & 0xFFL) << 0); | ||
| 496 | + n |= ((read(is) & 0xFFL) << 8); | ||
| 497 | + n |= ((read(is) & 0xFFL) << 16); | ||
| 498 | + n |= ((read(is) & 0xFFL) << 24); | ||
| 499 | + n |= ((read(is) & 0xFFL) << 32); | ||
| 500 | + n |= ((read(is) & 0xFFL) << 40); | ||
| 501 | + n |= ((read(is) & 0xFFL) << 48); | ||
| 502 | + n |= ((read(is) & 0xFFL) << 56); | ||
| 503 | + return n; | ||
| 504 | + } | ||
| 505 | + | ||
| 506 | + static void writeString(OutputStream os, String s) throws IOException { | ||
| 507 | + byte[] b = s.getBytes("UTF-8"); | ||
| 508 | + writeLong(os, b.length); | ||
| 509 | + os.write(b, 0, b.length); | ||
| 510 | + } | ||
| 511 | + | ||
| 512 | + static String readString(InputStream is) throws IOException { | ||
| 513 | + int n = (int) readLong(is); | ||
| 514 | + byte[] b = streamToBytes(is, n); | ||
| 515 | + return new String(b, "UTF-8"); | ||
| 516 | + } | ||
| 517 | + | ||
| 518 | + static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException { | ||
| 519 | + if (map != null) { | ||
| 520 | + writeInt(os, map.size()); | ||
| 521 | + for (Map.Entry<String, String> entry : map.entrySet()) { | ||
| 522 | + writeString(os, entry.getKey()); | ||
| 523 | + writeString(os, entry.getValue()); | ||
| 524 | + } | ||
| 525 | + } else { | ||
| 526 | + writeInt(os, 0); | ||
| 527 | + } | ||
| 528 | + } | ||
| 529 | + | ||
| 530 | + static Map<String, String> readStringStringMap(InputStream is) throws IOException { | ||
| 531 | + int size = readInt(is); | ||
| 532 | + Map<String, String> result = (size == 0) | ||
| 533 | + ? Collections.<String, String>emptyMap() | ||
| 534 | + : new HashMap<String, String>(size); | ||
| 535 | + for (int i = 0; i < size; i++) { | ||
| 536 | + String key = readString(is).intern(); | ||
| 537 | + String value = readString(is).intern(); | ||
| 538 | + result.put(key, value); | ||
| 539 | + } | ||
| 540 | + return result; | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + | ||
| 544 | +} |
| 1 | +package com.android.volley.toolbox; | ||
| 2 | + | ||
| 3 | +import com.android.volley.NetworkResponse; | ||
| 4 | +import com.android.volley.ParseError; | ||
| 5 | +import com.android.volley.Request; | ||
| 6 | +import com.android.volley.Response; | ||
| 7 | +import com.android.volley.Response.ErrorListener; | ||
| 8 | +import com.android.volley.Response.Listener; | ||
| 9 | +import com.google.gson.Gson; | ||
| 10 | + | ||
| 11 | +import java.io.UnsupportedEncodingException; | ||
| 12 | + | ||
| 13 | +public class GsonRequest<T> extends Request<T> { | ||
| 14 | + | ||
| 15 | + private final Listener<T> mListener; | ||
| 16 | + | ||
| 17 | + private Gson mGson; | ||
| 18 | + | ||
| 19 | + private Class<T> mClass; | ||
| 20 | + | ||
| 21 | + public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener, | ||
| 22 | + ErrorListener errorListener) { | ||
| 23 | + super(method, url, errorListener); | ||
| 24 | + mGson = new Gson(); | ||
| 25 | + mClass = clazz; | ||
| 26 | + mListener = listener; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public GsonRequest(String url, Class<T> clazz, Listener<T> listener, | ||
| 30 | + ErrorListener errorListener) { | ||
| 31 | + this(Method.GET, url, clazz, listener, errorListener); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + protected Response<T> parseNetworkResponse(NetworkResponse response) { | ||
| 36 | + try { | ||
| 37 | + String jsonString = new String(response.data, | ||
| 38 | + HttpHeaderParser.parseCharset(response.headers)); | ||
| 39 | + return Response.success(mGson.fromJson(jsonString, mClass), | ||
| 40 | + HttpHeaderParser.parseCacheHeaders(response)); | ||
| 41 | + } catch (UnsupportedEncodingException e) { | ||
| 42 | + return Response.error(new ParseError(e)); | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Override | ||
| 47 | + protected void deliverResponse(T response) { | ||
| 48 | + mListener.onResponse(response); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.AuthFailureError; | ||
| 20 | +import com.android.volley.Request; | ||
| 21 | +import com.android.volley.Request.Method; | ||
| 22 | +import org.apache.http.HttpEntity; | ||
| 23 | +import org.apache.http.HttpResponse; | ||
| 24 | +import org.apache.http.NameValuePair; | ||
| 25 | +import org.apache.http.client.HttpClient; | ||
| 26 | +import org.apache.http.client.methods.*; | ||
| 27 | +import org.apache.http.entity.ByteArrayEntity; | ||
| 28 | +import org.apache.http.message.BasicNameValuePair; | ||
| 29 | +import org.apache.http.params.HttpConnectionParams; | ||
| 30 | +import org.apache.http.params.HttpParams; | ||
| 31 | + | ||
| 32 | +import java.io.IOException; | ||
| 33 | +import java.net.URI; | ||
| 34 | +import java.util.ArrayList; | ||
| 35 | +import java.util.List; | ||
| 36 | +import java.util.Map; | ||
| 37 | + | ||
| 38 | +/** | ||
| 39 | + * An HttpStack that performs request over an {@link HttpClient}. | ||
| 40 | + */ | ||
| 41 | +public class HttpClientStack implements HttpStack { | ||
| 42 | + protected final HttpClient mClient; | ||
| 43 | + | ||
| 44 | + private final static String HEADER_CONTENT_TYPE = "Content-Type"; | ||
| 45 | + | ||
| 46 | + public HttpClientStack(HttpClient client) { | ||
| 47 | + mClient = client; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) { | ||
| 51 | + for (String key : headers.keySet()) { | ||
| 52 | + httpRequest.setHeader(key, headers.get(key)); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @SuppressWarnings("unused") | ||
| 57 | + private static List<NameValuePair> getPostParameterPairs(Map<String, String> postParams) { | ||
| 58 | + List<NameValuePair> result = new ArrayList<NameValuePair>(postParams.size()); | ||
| 59 | + for (String key : postParams.keySet()) { | ||
| 60 | + result.add(new BasicNameValuePair(key, postParams.get(key))); | ||
| 61 | + } | ||
| 62 | + return result; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + @Override | ||
| 66 | + public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) | ||
| 67 | + throws IOException, AuthFailureError { | ||
| 68 | + HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); | ||
| 69 | + addHeaders(httpRequest, additionalHeaders); | ||
| 70 | + addHeaders(httpRequest, request.getHeaders()); | ||
| 71 | + onPrepareRequest(httpRequest); | ||
| 72 | + HttpParams httpParams = httpRequest.getParams(); | ||
| 73 | + int timeoutMs = request.getTimeoutMs(); | ||
| 74 | + // TODO: Reevaluate this connection timeout based on more wide-scale | ||
| 75 | + // data collection and possibly different for wifi vs. 3G. | ||
| 76 | + HttpConnectionParams.setConnectionTimeout(httpParams, 5000); | ||
| 77 | + HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); | ||
| 78 | + return mClient.execute(httpRequest); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + /** | ||
| 82 | + * Creates the appropriate subclass of HttpUriRequest for passed in request. | ||
| 83 | + */ | ||
| 84 | + @SuppressWarnings("deprecation") | ||
| 85 | + /* protected */ static HttpUriRequest createHttpRequest(Request<?> request, | ||
| 86 | + Map<String, String> additionalHeaders) throws AuthFailureError { | ||
| 87 | + switch (request.getMethod()) { | ||
| 88 | + case Method.DEPRECATED_GET_OR_POST: { | ||
| 89 | + // This is the deprecated way that needs to be handled for backwards compatibility. | ||
| 90 | + // If the request's post body is null, then the assumption is that the request is | ||
| 91 | + // GET. Otherwise, it is assumed that the request is a POST. | ||
| 92 | + byte[] postBody = request.getPostBody(); | ||
| 93 | + if (postBody != null) { | ||
| 94 | + HttpPost postRequest = new HttpPost(request.getUrl()); | ||
| 95 | + postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); | ||
| 96 | + HttpEntity entity; | ||
| 97 | + entity = new ByteArrayEntity(postBody); | ||
| 98 | + postRequest.setEntity(entity); | ||
| 99 | + return postRequest; | ||
| 100 | + } else { | ||
| 101 | + return new HttpGet(request.getUrl()); | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | + case Method.GET: | ||
| 105 | + return new HttpGet(request.getUrl()); | ||
| 106 | + case Method.DELETE: | ||
| 107 | + return new HttpDelete(request.getUrl()); | ||
| 108 | + case Method.POST: { | ||
| 109 | + HttpPost postRequest = new HttpPost(request.getUrl()); | ||
| 110 | + postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); | ||
| 111 | + setEntityIfNonEmptyBody(postRequest, request); | ||
| 112 | + return postRequest; | ||
| 113 | + } | ||
| 114 | + case Method.PUT: { | ||
| 115 | + HttpPut putRequest = new HttpPut(request.getUrl()); | ||
| 116 | + putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); | ||
| 117 | + setEntityIfNonEmptyBody(putRequest, request); | ||
| 118 | + return putRequest; | ||
| 119 | + } | ||
| 120 | + case Method.HEAD: | ||
| 121 | + return new HttpHead(request.getUrl()); | ||
| 122 | + case Method.OPTIONS: | ||
| 123 | + return new HttpOptions(request.getUrl()); | ||
| 124 | + case Method.TRACE: | ||
| 125 | + return new HttpTrace(request.getUrl()); | ||
| 126 | + case Method.PATCH: { | ||
| 127 | + HttpPatch patchRequest = new HttpPatch(request.getUrl()); | ||
| 128 | + patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); | ||
| 129 | + setEntityIfNonEmptyBody(patchRequest, request); | ||
| 130 | + return patchRequest; | ||
| 131 | + } | ||
| 132 | + default: | ||
| 133 | + throw new IllegalStateException("Unknown request method."); | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, | ||
| 138 | + Request<?> request) throws AuthFailureError { | ||
| 139 | + byte[] body = request.getBody(); | ||
| 140 | + if (body != null) { | ||
| 141 | + HttpEntity entity = new ByteArrayEntity(body); | ||
| 142 | + httpRequest.setEntity(entity); | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + /** | ||
| 147 | + * Called before the request is executed using the underlying HttpClient. | ||
| 148 | + * | ||
| 149 | + * <p>Overwrite in subclasses to augment the request.</p> | ||
| 150 | + */ | ||
| 151 | + protected void onPrepareRequest(HttpUriRequest request) throws IOException { | ||
| 152 | + // Nothing. | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + /** | ||
| 156 | + * The HttpPatch class does not exist in the Android framework, so this has been defined here. | ||
| 157 | + */ | ||
| 158 | + public static final class HttpPatch extends HttpEntityEnclosingRequestBase { | ||
| 159 | + | ||
| 160 | + public final static String METHOD_NAME = "PATCH"; | ||
| 161 | + | ||
| 162 | + public HttpPatch() { | ||
| 163 | + super(); | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + public HttpPatch(final URI uri) { | ||
| 167 | + super(); | ||
| 168 | + setURI(uri); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + /** | ||
| 172 | + * @throws IllegalArgumentException if the uri is invalid. | ||
| 173 | + */ | ||
| 174 | + public HttpPatch(final String uri) { | ||
| 175 | + super(); | ||
| 176 | + setURI(URI.create(uri)); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + @Override | ||
| 180 | + public String getMethod() { | ||
| 181 | + return METHOD_NAME; | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + } | ||
| 185 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.Cache; | ||
| 20 | +import com.android.volley.NetworkResponse; | ||
| 21 | +import org.apache.http.impl.cookie.DateParseException; | ||
| 22 | +import org.apache.http.impl.cookie.DateUtils; | ||
| 23 | +import org.apache.http.protocol.HTTP; | ||
| 24 | + | ||
| 25 | +import java.util.Map; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * Utility methods for parsing HTTP headers. | ||
| 29 | + */ | ||
| 30 | +public class HttpHeaderParser { | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. | ||
| 34 | + * | ||
| 35 | + * @param response The network response to parse headers from | ||
| 36 | + * @return a cache entry for the given response, or null if the response is not cacheable. | ||
| 37 | + */ | ||
| 38 | + public static Cache.Entry parseCacheHeaders(NetworkResponse response) { | ||
| 39 | + long now = System.currentTimeMillis(); | ||
| 40 | + | ||
| 41 | + Map<String, String> headers = response.headers; | ||
| 42 | + | ||
| 43 | + long serverDate = 0; | ||
| 44 | + long serverExpires = 0; | ||
| 45 | + long softExpire = 0; | ||
| 46 | + long maxAge = 0; | ||
| 47 | + boolean hasCacheControl = false; | ||
| 48 | + | ||
| 49 | + String serverEtag = null; | ||
| 50 | + String headerValue; | ||
| 51 | + | ||
| 52 | + headerValue = headers.get("Date"); | ||
| 53 | + if (headerValue != null) { | ||
| 54 | + serverDate = parseDateAsEpoch(headerValue); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + headerValue = headers.get("Cache-Control"); | ||
| 58 | + if (headerValue != null) { | ||
| 59 | + hasCacheControl = true; | ||
| 60 | + String[] tokens = headerValue.split(","); | ||
| 61 | + for (int i = 0; i < tokens.length; i++) { | ||
| 62 | + String token = tokens[i].trim(); | ||
| 63 | + if (token.equals("no-cache") || token.equals("no-store")) { | ||
| 64 | + return null; | ||
| 65 | + } else if (token.startsWith("max-age=")) { | ||
| 66 | + try { | ||
| 67 | + maxAge = Long.parseLong(token.substring(8)); | ||
| 68 | + } catch (Exception e) { | ||
| 69 | + } | ||
| 70 | + } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { | ||
| 71 | + maxAge = 0; | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + headerValue = headers.get("Expires"); | ||
| 77 | + if (headerValue != null) { | ||
| 78 | + serverExpires = parseDateAsEpoch(headerValue); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + serverEtag = headers.get("ETag"); | ||
| 82 | + | ||
| 83 | + // Cache-Control takes precedence over an Expires header, even if both exist and Expires | ||
| 84 | + // is more restrictive. | ||
| 85 | + if (hasCacheControl) { | ||
| 86 | + softExpire = now + maxAge * 1000; | ||
| 87 | + } else if (serverDate > 0 && serverExpires >= serverDate) { | ||
| 88 | + // Default semantic for Expire header in HTTP specification is softExpire. | ||
| 89 | + softExpire = now + (serverExpires - serverDate); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + Cache.Entry entry = new Cache.Entry(); | ||
| 93 | + entry.data = response.data; | ||
| 94 | + entry.etag = serverEtag; | ||
| 95 | + entry.softTtl = softExpire; | ||
| 96 | + entry.ttl = entry.softTtl; | ||
| 97 | + entry.serverDate = serverDate; | ||
| 98 | + entry.responseHeaders = headers; | ||
| 99 | + | ||
| 100 | + return entry; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + /** | ||
| 104 | + * Parse date in RFC1123 format, and return its value as epoch | ||
| 105 | + */ | ||
| 106 | + public static long parseDateAsEpoch(String dateStr) { | ||
| 107 | + try { | ||
| 108 | + // Parse date in RFC1123 format if this header contains one | ||
| 109 | + return DateUtils.parseDate(dateStr).getTime(); | ||
| 110 | + } catch (DateParseException e) { | ||
| 111 | + // Date in invalid format, fallback to 0 | ||
| 112 | + return 0; | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * Returns the charset specified in the Content-Type of this header, | ||
| 118 | + * or the HTTP default (ISO-8859-1) if none can be found. | ||
| 119 | + */ | ||
| 120 | + public static String parseCharset(Map<String, String> headers) { | ||
| 121 | + String contentType = headers.get(HTTP.CONTENT_TYPE); | ||
| 122 | + if (contentType != null) { | ||
| 123 | + String[] params = contentType.split(";"); | ||
| 124 | + for (int i = 1; i < params.length; i++) { | ||
| 125 | + String[] pair = params[i].trim().split("="); | ||
| 126 | + if (pair.length == 2) { | ||
| 127 | + if (pair[0].equals("charset")) { | ||
| 128 | + return pair[1]; | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + return HTTP.UTF_8; | ||
| 135 | + } | ||
| 136 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.AuthFailureError; | ||
| 20 | +import com.android.volley.Request; | ||
| 21 | +import org.apache.http.HttpResponse; | ||
| 22 | + | ||
| 23 | +import java.io.IOException; | ||
| 24 | +import java.util.Map; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * An HTTP stack abstraction. | ||
| 28 | + */ | ||
| 29 | +public interface HttpStack { | ||
| 30 | + /** | ||
| 31 | + * Performs an HTTP request with the given parameters. | ||
| 32 | + * | ||
| 33 | + * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, | ||
| 34 | + * and the Content-Type header is set to request.getPostBodyContentType().</p> | ||
| 35 | + * | ||
| 36 | + * @param request the request to perform | ||
| 37 | + * @param additionalHeaders additional headers to be sent together with | ||
| 38 | + * {@link Request#getHeaders()} | ||
| 39 | + * @return the HTTP response | ||
| 40 | + */ | ||
| 41 | + public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) | ||
| 42 | + throws IOException, AuthFailureError; | ||
| 43 | + | ||
| 44 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.AuthFailureError; | ||
| 20 | +import com.android.volley.Request; | ||
| 21 | +import com.android.volley.Request.Method; | ||
| 22 | +import org.apache.http.*; | ||
| 23 | +import org.apache.http.entity.BasicHttpEntity; | ||
| 24 | +import org.apache.http.message.BasicHeader; | ||
| 25 | +import org.apache.http.message.BasicHttpResponse; | ||
| 26 | +import org.apache.http.message.BasicStatusLine; | ||
| 27 | + | ||
| 28 | +import javax.net.ssl.HttpsURLConnection; | ||
| 29 | +import javax.net.ssl.SSLSocketFactory; | ||
| 30 | +import java.io.DataOutputStream; | ||
| 31 | +import java.io.IOException; | ||
| 32 | +import java.io.InputStream; | ||
| 33 | +import java.net.HttpURLConnection; | ||
| 34 | +import java.net.URL; | ||
| 35 | +import java.util.HashMap; | ||
| 36 | +import java.util.List; | ||
| 37 | +import java.util.Map; | ||
| 38 | +import java.util.Map.Entry; | ||
| 39 | + | ||
| 40 | +/** | ||
| 41 | + * An {@link HttpStack} based on {@link HttpURLConnection}. | ||
| 42 | + */ | ||
| 43 | +public class HurlStack implements HttpStack { | ||
| 44 | + | ||
| 45 | + private static final String HEADER_CONTENT_TYPE = "Content-Type"; | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * An interface for transforming URLs before use. | ||
| 49 | + */ | ||
| 50 | + public interface UrlRewriter { | ||
| 51 | + /** | ||
| 52 | + * Returns a URL to use instead of the provided one, or null to indicate | ||
| 53 | + * this URL should not be used at all. | ||
| 54 | + */ | ||
| 55 | + public String rewriteUrl(String originalUrl); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + private final UrlRewriter mUrlRewriter; | ||
| 59 | + private final SSLSocketFactory mSslSocketFactory; | ||
| 60 | + | ||
| 61 | + public HurlStack() { | ||
| 62 | + this(null); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * @param urlRewriter Rewriter to use for request URLs | ||
| 67 | + */ | ||
| 68 | + public HurlStack(UrlRewriter urlRewriter) { | ||
| 69 | + this(urlRewriter, null); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * @param urlRewriter Rewriter to use for request URLs | ||
| 74 | + * @param sslSocketFactory SSL factory to use for HTTPS connections | ||
| 75 | + */ | ||
| 76 | + public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { | ||
| 77 | + mUrlRewriter = urlRewriter; | ||
| 78 | + mSslSocketFactory = sslSocketFactory; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + @Override | ||
| 82 | + public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) | ||
| 83 | + throws IOException, AuthFailureError { | ||
| 84 | + String url = request.getUrl(); | ||
| 85 | + HashMap<String, String> map = new HashMap<String, String>(); | ||
| 86 | + map.putAll(request.getHeaders()); | ||
| 87 | + map.putAll(additionalHeaders); | ||
| 88 | + if (mUrlRewriter != null) { | ||
| 89 | + String rewritten = mUrlRewriter.rewriteUrl(url); | ||
| 90 | + if (rewritten == null) { | ||
| 91 | + throw new IOException("URL blocked by rewriter: " + url); | ||
| 92 | + } | ||
| 93 | + url = rewritten; | ||
| 94 | + } | ||
| 95 | + URL parsedUrl = new URL(url); | ||
| 96 | + HttpURLConnection connection = openConnection(parsedUrl, request); | ||
| 97 | + for (String headerName : map.keySet()) { | ||
| 98 | + connection.addRequestProperty(headerName, map.get(headerName)); | ||
| 99 | + } | ||
| 100 | + setConnectionParametersForRequest(connection, request); | ||
| 101 | + // Initialize HttpResponse with data from the HttpURLConnection. | ||
| 102 | + ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); | ||
| 103 | + int responseCode = connection.getResponseCode(); | ||
| 104 | + if (responseCode == -1) { | ||
| 105 | + // -1 is returned by getResponseCode() if the response code could not be retrieved. | ||
| 106 | + // Signal to the caller that something was wrong with the connection. | ||
| 107 | + throw new IOException("Could not retrieve response code from HttpUrlConnection."); | ||
| 108 | + } | ||
| 109 | + StatusLine responseStatus = new BasicStatusLine(protocolVersion, | ||
| 110 | + connection.getResponseCode(), connection.getResponseMessage()); | ||
| 111 | + BasicHttpResponse response = new BasicHttpResponse(responseStatus); | ||
| 112 | + response.setEntity(entityFromConnection(connection)); | ||
| 113 | + for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { | ||
| 114 | + if (header.getKey() != null) { | ||
| 115 | + Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); | ||
| 116 | + response.addHeader(h); | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + return response; | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + /** | ||
| 123 | + * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. | ||
| 124 | + * @param connection | ||
| 125 | + * @return an HttpEntity populated with data from <code>connection</code>. | ||
| 126 | + */ | ||
| 127 | + private static HttpEntity entityFromConnection(HttpURLConnection connection) { | ||
| 128 | + BasicHttpEntity entity = new BasicHttpEntity(); | ||
| 129 | + InputStream inputStream; | ||
| 130 | + try { | ||
| 131 | + inputStream = connection.getInputStream(); | ||
| 132 | + } catch (IOException ioe) { | ||
| 133 | + inputStream = connection.getErrorStream(); | ||
| 134 | + } | ||
| 135 | + entity.setContent(inputStream); | ||
| 136 | + entity.setContentLength(connection.getContentLength()); | ||
| 137 | + entity.setContentEncoding(connection.getContentEncoding()); | ||
| 138 | + entity.setContentType(connection.getContentType()); | ||
| 139 | + return entity; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + /** | ||
| 143 | + * Create an {@link HttpURLConnection} for the specified {@code url}. | ||
| 144 | + */ | ||
| 145 | + protected HttpURLConnection createConnection(URL url) throws IOException { | ||
| 146 | + return (HttpURLConnection) url.openConnection(); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + /** | ||
| 150 | + * Opens an {@link HttpURLConnection} with parameters. | ||
| 151 | + * @param url | ||
| 152 | + * @return an open connection | ||
| 153 | + * @throws IOException | ||
| 154 | + */ | ||
| 155 | + private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { | ||
| 156 | + HttpURLConnection connection = createConnection(url); | ||
| 157 | + | ||
| 158 | + int timeoutMs = request.getTimeoutMs(); | ||
| 159 | + connection.setConnectTimeout(timeoutMs); | ||
| 160 | + connection.setReadTimeout(timeoutMs); | ||
| 161 | + connection.setUseCaches(false); | ||
| 162 | + connection.setDoInput(true); | ||
| 163 | + connection.setChunkedStreamingMode(0); | ||
| 164 | + | ||
| 165 | + // use caller-provided custom SslSocketFactory, if any, for HTTPS | ||
| 166 | + if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { | ||
| 167 | + ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + return connection; | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + @SuppressWarnings("deprecation") | ||
| 174 | + /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, | ||
| 175 | + Request<?> request) throws IOException, AuthFailureError { | ||
| 176 | + switch (request.getMethod()) { | ||
| 177 | + case Method.DEPRECATED_GET_OR_POST: | ||
| 178 | + // This is the deprecated way that needs to be handled for backwards compatibility. | ||
| 179 | + // If the request's post body is null, then the assumption is that the request is | ||
| 180 | + // GET. Otherwise, it is assumed that the request is a POST. | ||
| 181 | + byte[] postBody = request.getPostBody(); | ||
| 182 | + if (postBody != null) { | ||
| 183 | + // Prepare output. There is no need to set Content-Length explicitly, | ||
| 184 | + // since this is handled by HttpURLConnection using the size of the prepared | ||
| 185 | + // output stream. | ||
| 186 | + connection.setDoOutput(true); | ||
| 187 | + connection.setRequestMethod("POST"); | ||
| 188 | + connection.addRequestProperty(HEADER_CONTENT_TYPE, | ||
| 189 | + request.getPostBodyContentType()); | ||
| 190 | + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); | ||
| 191 | + out.write(postBody); | ||
| 192 | + out.close(); | ||
| 193 | + } | ||
| 194 | + break; | ||
| 195 | + case Method.GET: | ||
| 196 | + // Not necessary to set the request method because connection defaults to GET but | ||
| 197 | + // being explicit here. | ||
| 198 | + connection.setRequestMethod("GET"); | ||
| 199 | + break; | ||
| 200 | + case Method.DELETE: | ||
| 201 | + connection.setRequestMethod("DELETE"); | ||
| 202 | + break; | ||
| 203 | + case Method.POST: | ||
| 204 | + connection.setRequestMethod("POST"); | ||
| 205 | + addBodyIfExists(connection, request); | ||
| 206 | + break; | ||
| 207 | + case Method.PUT: | ||
| 208 | + connection.setRequestMethod("PUT"); | ||
| 209 | + addBodyIfExists(connection, request); | ||
| 210 | + break; | ||
| 211 | + case Method.HEAD: | ||
| 212 | + connection.setRequestMethod("HEAD"); | ||
| 213 | + break; | ||
| 214 | + case Method.OPTIONS: | ||
| 215 | + connection.setRequestMethod("OPTIONS"); | ||
| 216 | + break; | ||
| 217 | + case Method.TRACE: | ||
| 218 | + connection.setRequestMethod("TRACE"); | ||
| 219 | + break; | ||
| 220 | + case Method.PATCH: | ||
| 221 | + addBodyIfExists(connection, request); | ||
| 222 | + connection.setRequestMethod("PATCH"); | ||
| 223 | + break; | ||
| 224 | + default: | ||
| 225 | + throw new IllegalStateException("Unknown method type."); | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) | ||
| 230 | + throws IOException, AuthFailureError { | ||
| 231 | + byte[] body = request.getBody(); | ||
| 232 | + if (body != null) { | ||
| 233 | + connection.setDoOutput(true); | ||
| 234 | + connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); | ||
| 235 | + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); | ||
| 236 | + out.write(body); | ||
| 237 | + out.close(); | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | +} |
| 1 | +/** | ||
| 2 | + * Copyright (C) 2013 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package com.android.volley.toolbox; | ||
| 17 | + | ||
| 18 | +import android.graphics.Bitmap; | ||
| 19 | +import android.graphics.Bitmap.Config; | ||
| 20 | +import android.os.Handler; | ||
| 21 | +import android.os.Looper; | ||
| 22 | +import android.widget.ImageView; | ||
| 23 | +import com.android.volley.Request; | ||
| 24 | +import com.android.volley.RequestQueue; | ||
| 25 | +import com.android.volley.Response.ErrorListener; | ||
| 26 | +import com.android.volley.Response.Listener; | ||
| 27 | +import com.android.volley.VolleyError; | ||
| 28 | + | ||
| 29 | +import java.util.HashMap; | ||
| 30 | +import java.util.LinkedList; | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * Helper that handles loading and caching images from remote URLs. | ||
| 34 | + * | ||
| 35 | + * The simple way to use this class is to call {@link ImageLoader#get(String, ImageListener)} | ||
| 36 | + * and to pass in the default image listener provided by | ||
| 37 | + * {@link ImageLoader#getImageListener(ImageView, int, int)}. Note that all function calls to | ||
| 38 | + * this class must be made from the main thead, and all responses will be delivered to the main | ||
| 39 | + * thread as well. | ||
| 40 | + */ | ||
| 41 | +public class ImageLoader { | ||
| 42 | + /** RequestQueue for dispatching ImageRequests onto. */ | ||
| 43 | + private final RequestQueue mRequestQueue; | ||
| 44 | + | ||
| 45 | + /** Amount of time to wait after first response arrives before delivering all responses. */ | ||
| 46 | + private int mBatchResponseDelayMs = 100; | ||
| 47 | + | ||
| 48 | + /** The cache implementation to be used as an L1 cache before calling into volley. */ | ||
| 49 | + private final ImageCache mCache; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so | ||
| 53 | + * that we can coalesce multiple requests to the same URL into a single network request. | ||
| 54 | + */ | ||
| 55 | + private final HashMap<String, BatchedImageRequest> mInFlightRequests = | ||
| 56 | + new HashMap<String, BatchedImageRequest>(); | ||
| 57 | + | ||
| 58 | + /** HashMap of the currently pending responses (waiting to be delivered). */ | ||
| 59 | + private final HashMap<String, BatchedImageRequest> mBatchedResponses = | ||
| 60 | + new HashMap<String, BatchedImageRequest>(); | ||
| 61 | + | ||
| 62 | + /** Handler to the main thread. */ | ||
| 63 | + private final Handler mHandler = new Handler(Looper.getMainLooper()); | ||
| 64 | + | ||
| 65 | + /** Runnable for in-flight response delivery. */ | ||
| 66 | + private Runnable mRunnable; | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * Simple cache adapter interface. If provided to the ImageLoader, it | ||
| 70 | + * will be used as an L1 cache before dispatch to Volley. Implementations | ||
| 71 | + * must not block. Implementation with an LruCache is recommended. | ||
| 72 | + */ | ||
| 73 | + public interface ImageCache { | ||
| 74 | + public Bitmap getBitmap(String url); | ||
| 75 | + public void putBitmap(String url, Bitmap bitmap); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * Constructs a new ImageLoader. | ||
| 80 | + * @param queue The RequestQueue to use for making image requests. | ||
| 81 | + * @param imageCache The cache to use as an L1 cache. | ||
| 82 | + */ | ||
| 83 | + public ImageLoader(RequestQueue queue, ImageCache imageCache) { | ||
| 84 | + mRequestQueue = queue; | ||
| 85 | + mCache = imageCache; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * The default implementation of ImageListener which handles basic functionality | ||
| 90 | + * of showing a default image until the network response is received, at which point | ||
| 91 | + * it will switch to either the actual image or the error image. | ||
| 92 | + * @param imageView The imageView that the listener is associated with. | ||
| 93 | + * @param defaultImageResId Default image resource ID to use, or 0 if it doesn't exist. | ||
| 94 | + * @param errorImageResId Error image resource ID to use, or 0 if it doesn't exist. | ||
| 95 | + */ | ||
| 96 | + public static ImageListener getImageListener(final ImageView view, | ||
| 97 | + final int defaultImageResId, final int errorImageResId) { | ||
| 98 | + return new ImageListener() { | ||
| 99 | + @Override | ||
| 100 | + public void onErrorResponse(VolleyError error) { | ||
| 101 | + if (errorImageResId != 0) { | ||
| 102 | + view.setImageResource(errorImageResId); | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + @Override | ||
| 107 | + public void onResponse(ImageContainer response, boolean isImmediate) { | ||
| 108 | + if (response.getBitmap() != null) { | ||
| 109 | + view.setImageBitmap(response.getBitmap()); | ||
| 110 | + } else if (defaultImageResId != 0) { | ||
| 111 | + view.setImageResource(defaultImageResId); | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + }; | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + /** | ||
| 118 | + * Interface for the response handlers on image requests. | ||
| 119 | + * | ||
| 120 | + * The call flow is this: | ||
| 121 | + * 1. Upon being attached to a request, onResponse(response, true) will | ||
| 122 | + * be invoked to reflect any cached data that was already available. If the | ||
| 123 | + * data was available, response.getBitmap() will be non-null. | ||
| 124 | + * | ||
| 125 | + * 2. After a network response returns, only one of the following cases will happen: | ||
| 126 | + * - onResponse(response, false) will be called if the image was loaded. | ||
| 127 | + * or | ||
| 128 | + * - onErrorResponse will be called if there was an error loading the image. | ||
| 129 | + */ | ||
| 130 | + public interface ImageListener extends ErrorListener { | ||
| 131 | + /** | ||
| 132 | + * Listens for non-error changes to the loading of the image request. | ||
| 133 | + * | ||
| 134 | + * @param response Holds all information pertaining to the request, as well | ||
| 135 | + * as the bitmap (if it is loaded). | ||
| 136 | + * @param isImmediate True if this was called during ImageLoader.get() variants. | ||
| 137 | + * This can be used to differentiate between a cached image loading and a network | ||
| 138 | + * image loading in order to, for example, run an animation to fade in network loaded | ||
| 139 | + * images. | ||
| 140 | + */ | ||
| 141 | + public void onResponse(ImageContainer response, boolean isImmediate); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + /** | ||
| 145 | + * Checks if the item is available in the cache. | ||
| 146 | + * @param requestUrl The url of the remote image | ||
| 147 | + * @param maxWidth The maximum width of the returned image. | ||
| 148 | + * @param maxHeight The maximum height of the returned image. | ||
| 149 | + * @return True if the item exists in cache, false otherwise. | ||
| 150 | + */ | ||
| 151 | + public boolean isCached(String requestUrl, int maxWidth, int maxHeight) { | ||
| 152 | + throwIfNotOnMainThread(); | ||
| 153 | + | ||
| 154 | + String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); | ||
| 155 | + return mCache.getBitmap(cacheKey) != null; | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + /** | ||
| 159 | + * Returns an ImageContainer for the requested URL. | ||
| 160 | + * | ||
| 161 | + * The ImageContainer will contain either the specified default bitmap or the loaded bitmap. | ||
| 162 | + * If the default was returned, the {@link ImageLoader} will be invoked when the | ||
| 163 | + * request is fulfilled. | ||
| 164 | + * | ||
| 165 | + * @param requestUrl The URL of the image to be loaded. | ||
| 166 | + * @param defaultImage Optional default image to return until the actual image is loaded. | ||
| 167 | + */ | ||
| 168 | + public ImageContainer get(String requestUrl, final ImageListener listener) { | ||
| 169 | + return get(requestUrl, listener, 0, 0); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + /** | ||
| 173 | + * Issues a bitmap request with the given URL if that image is not available | ||
| 174 | + * in the cache, and returns a bitmap container that contains all of the data | ||
| 175 | + * relating to the request (as well as the default image if the requested | ||
| 176 | + * image is not available). | ||
| 177 | + * @param requestUrl The url of the remote image | ||
| 178 | + * @param imageListener The listener to call when the remote image is loaded | ||
| 179 | + * @param maxWidth The maximum width of the returned image. | ||
| 180 | + * @param maxHeight The maximum height of the returned image. | ||
| 181 | + * @return A container object that contains all of the properties of the request, as well as | ||
| 182 | + * the currently available image (default if remote is not loaded). | ||
| 183 | + */ | ||
| 184 | + public ImageContainer get(String requestUrl, ImageListener imageListener, | ||
| 185 | + int maxWidth, int maxHeight) { | ||
| 186 | + // only fulfill requests that were initiated from the main thread. | ||
| 187 | + throwIfNotOnMainThread(); | ||
| 188 | + | ||
| 189 | + final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); | ||
| 190 | + | ||
| 191 | + // Try to look up the request in the cache of remote images. | ||
| 192 | + Bitmap cachedBitmap = mCache.getBitmap(cacheKey); | ||
| 193 | + if (cachedBitmap != null) { | ||
| 194 | + // Return the cached bitmap. | ||
| 195 | + ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); | ||
| 196 | + imageListener.onResponse(container, true); | ||
| 197 | + return container; | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + // The bitmap did not exist in the cache, fetch it! | ||
| 201 | + ImageContainer imageContainer = | ||
| 202 | + new ImageContainer(null, requestUrl, cacheKey, imageListener); | ||
| 203 | + | ||
| 204 | + // Update the caller to let them know that they should use the default bitmap. | ||
| 205 | + imageListener.onResponse(imageContainer, true); | ||
| 206 | + | ||
| 207 | + // Check to see if a request is already in-flight. | ||
| 208 | + BatchedImageRequest request = mInFlightRequests.get(cacheKey); | ||
| 209 | + if (request != null) { | ||
| 210 | + // If it is, add this request to the list of listeners. | ||
| 211 | + request.addContainer(imageContainer); | ||
| 212 | + return imageContainer; | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + // The request is not already in flight. Send the new request to the network and | ||
| 216 | + // track it. | ||
| 217 | + Request<?> newRequest = | ||
| 218 | + new ImageRequest(requestUrl, new Listener<Bitmap>() { | ||
| 219 | + @Override | ||
| 220 | + public void onResponse(Bitmap response) { | ||
| 221 | + onGetImageSuccess(cacheKey, response); | ||
| 222 | + } | ||
| 223 | + }, maxWidth, maxHeight, | ||
| 224 | + Config.RGB_565, new ErrorListener() { | ||
| 225 | + @Override | ||
| 226 | + public void onErrorResponse(VolleyError error) { | ||
| 227 | + onGetImageError(cacheKey, error); | ||
| 228 | + } | ||
| 229 | + }); | ||
| 230 | + | ||
| 231 | + mRequestQueue.add(newRequest); | ||
| 232 | + mInFlightRequests.put(cacheKey, | ||
| 233 | + new BatchedImageRequest(newRequest, imageContainer)); | ||
| 234 | + return imageContainer; | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + /** | ||
| 238 | + * Sets the amount of time to wait after the first response arrives before delivering all | ||
| 239 | + * responses. Batching can be disabled entirely by passing in 0. | ||
| 240 | + * @param newBatchedResponseDelayMs The time in milliseconds to wait. | ||
| 241 | + */ | ||
| 242 | + public void setBatchedResponseDelay(int newBatchedResponseDelayMs) { | ||
| 243 | + mBatchResponseDelayMs = newBatchedResponseDelayMs; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + /** | ||
| 247 | + * Handler for when an image was successfully loaded. | ||
| 248 | + * @param cacheKey The cache key that is associated with the image request. | ||
| 249 | + * @param response The bitmap that was returned from the network. | ||
| 250 | + */ | ||
| 251 | + private void onGetImageSuccess(String cacheKey, Bitmap response) { | ||
| 252 | + // cache the image that was fetched. | ||
| 253 | + mCache.putBitmap(cacheKey, response); | ||
| 254 | + | ||
| 255 | + // remove the request from the list of in-flight requests. | ||
| 256 | + BatchedImageRequest request = mInFlightRequests.remove(cacheKey); | ||
| 257 | + | ||
| 258 | + if (request != null) { | ||
| 259 | + // Update the response bitmap. | ||
| 260 | + request.mResponseBitmap = response; | ||
| 261 | + | ||
| 262 | + // Send the batched response | ||
| 263 | + batchResponse(cacheKey, request); | ||
| 264 | + } | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + /** | ||
| 268 | + * Handler for when an image failed to load. | ||
| 269 | + * @param cacheKey The cache key that is associated with the image request. | ||
| 270 | + */ | ||
| 271 | + private void onGetImageError(String cacheKey, VolleyError error) { | ||
| 272 | + // Notify the requesters that something failed via a null result. | ||
| 273 | + // Remove this request from the list of in-flight requests. | ||
| 274 | + BatchedImageRequest request = mInFlightRequests.remove(cacheKey); | ||
| 275 | + | ||
| 276 | + // Set the error for this request | ||
| 277 | + request.setError(error); | ||
| 278 | + | ||
| 279 | + if (request != null) { | ||
| 280 | + // Send the batched response | ||
| 281 | + batchResponse(cacheKey, request); | ||
| 282 | + } | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + /** | ||
| 286 | + * Container object for all of the data surrounding an image request. | ||
| 287 | + */ | ||
| 288 | + public class ImageContainer { | ||
| 289 | + /** | ||
| 290 | + * The most relevant bitmap for the container. If the image was in cache, the | ||
| 291 | + * Holder to use for the final bitmap (the one that pairs to the requested URL). | ||
| 292 | + */ | ||
| 293 | + private Bitmap mBitmap; | ||
| 294 | + | ||
| 295 | + private final ImageListener mListener; | ||
| 296 | + | ||
| 297 | + /** The cache key that was associated with the request */ | ||
| 298 | + private final String mCacheKey; | ||
| 299 | + | ||
| 300 | + /** The request URL that was specified */ | ||
| 301 | + private final String mRequestUrl; | ||
| 302 | + | ||
| 303 | + /** | ||
| 304 | + * Constructs a BitmapContainer object. | ||
| 305 | + * @param bitmap The final bitmap (if it exists). | ||
| 306 | + * @param requestUrl The requested URL for this container. | ||
| 307 | + * @param cacheKey The cache key that identifies the requested URL for this container. | ||
| 308 | + */ | ||
| 309 | + public ImageContainer(Bitmap bitmap, String requestUrl, | ||
| 310 | + String cacheKey, ImageListener listener) { | ||
| 311 | + mBitmap = bitmap; | ||
| 312 | + mRequestUrl = requestUrl; | ||
| 313 | + mCacheKey = cacheKey; | ||
| 314 | + mListener = listener; | ||
| 315 | + } | ||
| 316 | + | ||
| 317 | + /** | ||
| 318 | + * Releases interest in the in-flight request (and cancels it if no one else is listening). | ||
| 319 | + */ | ||
| 320 | + public void cancelRequest() { | ||
| 321 | + if (mListener == null) { | ||
| 322 | + return; | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + BatchedImageRequest request = mInFlightRequests.get(mCacheKey); | ||
| 326 | + if (request != null) { | ||
| 327 | + boolean canceled = request.removeContainerAndCancelIfNecessary(this); | ||
| 328 | + if (canceled) { | ||
| 329 | + mInFlightRequests.remove(mCacheKey); | ||
| 330 | + } | ||
| 331 | + } else { | ||
| 332 | + // check to see if it is already batched for delivery. | ||
| 333 | + request = mBatchedResponses.get(mCacheKey); | ||
| 334 | + if (request != null) { | ||
| 335 | + request.removeContainerAndCancelIfNecessary(this); | ||
| 336 | + if (request.mContainers.size() == 0) { | ||
| 337 | + mBatchedResponses.remove(mCacheKey); | ||
| 338 | + } | ||
| 339 | + } | ||
| 340 | + } | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + /** | ||
| 344 | + * Returns the bitmap associated with the request URL if it has been loaded, null otherwise. | ||
| 345 | + */ | ||
| 346 | + public Bitmap getBitmap() { | ||
| 347 | + return mBitmap; | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + /** | ||
| 351 | + * Returns the requested URL for this container. | ||
| 352 | + */ | ||
| 353 | + public String getRequestUrl() { | ||
| 354 | + return mRequestUrl; | ||
| 355 | + } | ||
| 356 | + } | ||
| 357 | + | ||
| 358 | + /** | ||
| 359 | + * Wrapper class used to map a Request to the set of active ImageContainer objects that are | ||
| 360 | + * interested in its results. | ||
| 361 | + */ | ||
| 362 | + private class BatchedImageRequest { | ||
| 363 | + /** The request being tracked */ | ||
| 364 | + private final Request<?> mRequest; | ||
| 365 | + | ||
| 366 | + /** The result of the request being tracked by this item */ | ||
| 367 | + private Bitmap mResponseBitmap; | ||
| 368 | + | ||
| 369 | + /** Error if one occurred for this response */ | ||
| 370 | + private VolleyError mError; | ||
| 371 | + | ||
| 372 | + /** List of all of the active ImageContainers that are interested in the request */ | ||
| 373 | + private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>(); | ||
| 374 | + | ||
| 375 | + /** | ||
| 376 | + * Constructs a new BatchedImageRequest object | ||
| 377 | + * @param request The request being tracked | ||
| 378 | + * @param container The ImageContainer of the person who initiated the request. | ||
| 379 | + */ | ||
| 380 | + public BatchedImageRequest(Request<?> request, ImageContainer container) { | ||
| 381 | + mRequest = request; | ||
| 382 | + mContainers.add(container); | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + /** | ||
| 386 | + * Set the error for this response | ||
| 387 | + */ | ||
| 388 | + public void setError(VolleyError error) { | ||
| 389 | + mError = error; | ||
| 390 | + } | ||
| 391 | + | ||
| 392 | + /** | ||
| 393 | + * Get the error for this response | ||
| 394 | + */ | ||
| 395 | + public VolleyError getError() { | ||
| 396 | + return mError; | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + /** | ||
| 400 | + * Adds another ImageContainer to the list of those interested in the results of | ||
| 401 | + * the request. | ||
| 402 | + */ | ||
| 403 | + public void addContainer(ImageContainer container) { | ||
| 404 | + mContainers.add(container); | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + /** | ||
| 408 | + * Detatches the bitmap container from the request and cancels the request if no one is | ||
| 409 | + * left listening. | ||
| 410 | + * @param container The container to remove from the list | ||
| 411 | + * @return True if the request was canceled, false otherwise. | ||
| 412 | + */ | ||
| 413 | + public boolean removeContainerAndCancelIfNecessary(ImageContainer container) { | ||
| 414 | + mContainers.remove(container); | ||
| 415 | + if (mContainers.size() == 0) { | ||
| 416 | + mRequest.cancel(); | ||
| 417 | + return true; | ||
| 418 | + } | ||
| 419 | + return false; | ||
| 420 | + } | ||
| 421 | + } | ||
| 422 | + | ||
| 423 | + /** | ||
| 424 | + * Starts the runnable for batched delivery of responses if it is not already started. | ||
| 425 | + * @param cacheKey The cacheKey of the response being delivered. | ||
| 426 | + * @param request The BatchedImageRequest to be delivered. | ||
| 427 | + * @param error The volley error associated with the request (if applicable). | ||
| 428 | + */ | ||
| 429 | + private void batchResponse(String cacheKey, BatchedImageRequest request) { | ||
| 430 | + mBatchedResponses.put(cacheKey, request); | ||
| 431 | + // If we don't already have a batch delivery runnable in flight, make a new one. | ||
| 432 | + // Note that this will be used to deliver responses to all callers in mBatchedResponses. | ||
| 433 | + if (mRunnable == null) { | ||
| 434 | + mRunnable = new Runnable() { | ||
| 435 | + @Override | ||
| 436 | + public void run() { | ||
| 437 | + for (BatchedImageRequest bir : mBatchedResponses.values()) { | ||
| 438 | + for (ImageContainer container : bir.mContainers) { | ||
| 439 | + // If one of the callers in the batched request canceled the request | ||
| 440 | + // after the response was received but before it was delivered, | ||
| 441 | + // skip them. | ||
| 442 | + if (container.mListener == null) { | ||
| 443 | + continue; | ||
| 444 | + } | ||
| 445 | + if (bir.getError() == null) { | ||
| 446 | + container.mBitmap = bir.mResponseBitmap; | ||
| 447 | + container.mListener.onResponse(container, false); | ||
| 448 | + } else { | ||
| 449 | + container.mListener.onErrorResponse(bir.getError()); | ||
| 450 | + } | ||
| 451 | + } | ||
| 452 | + } | ||
| 453 | + mBatchedResponses.clear(); | ||
| 454 | + mRunnable = null; | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + }; | ||
| 458 | + // Post the runnable. | ||
| 459 | + mHandler.postDelayed(mRunnable, mBatchResponseDelayMs); | ||
| 460 | + } | ||
| 461 | + } | ||
| 462 | + | ||
| 463 | + private void throwIfNotOnMainThread() { | ||
| 464 | + if (Looper.myLooper() != Looper.getMainLooper()) { | ||
| 465 | + throw new IllegalStateException("ImageLoader must be invoked from the main thread."); | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | + /** | ||
| 469 | + * Creates a cache key for use with the L1 cache. | ||
| 470 | + * @param url The URL of the request. | ||
| 471 | + * @param maxWidth The max-width of the output. | ||
| 472 | + * @param maxHeight The max-height of the output. | ||
| 473 | + */ | ||
| 474 | + private static String getCacheKey(String url, int maxWidth, int maxHeight) { | ||
| 475 | + return new StringBuilder(url.length() + 12).append("#W").append(maxWidth) | ||
| 476 | + .append("#H").append(maxHeight).append(url).toString(); | ||
| 477 | + } | ||
| 478 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.graphics.Bitmap; | ||
| 20 | +import android.graphics.Bitmap.Config; | ||
| 21 | +import android.graphics.BitmapFactory; | ||
| 22 | +import com.android.volley.*; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * A canned request for getting an image at a given URL and calling | ||
| 26 | + * back with a decoded Bitmap. | ||
| 27 | + */ | ||
| 28 | +public class ImageRequest extends Request<Bitmap> { | ||
| 29 | + /** Socket timeout in milliseconds for image requests */ | ||
| 30 | + private static final int IMAGE_TIMEOUT_MS = 1000; | ||
| 31 | + | ||
| 32 | + /** Default number of retries for image requests */ | ||
| 33 | + private static final int IMAGE_MAX_RETRIES = 2; | ||
| 34 | + | ||
| 35 | + /** Default backoff multiplier for image requests */ | ||
| 36 | + private static final float IMAGE_BACKOFF_MULT = 2f; | ||
| 37 | + | ||
| 38 | + private final Response.Listener<Bitmap> mListener; | ||
| 39 | + private final Config mDecodeConfig; | ||
| 40 | + private final int mMaxWidth; | ||
| 41 | + private final int mMaxHeight; | ||
| 42 | + | ||
| 43 | + /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */ | ||
| 44 | + private static final Object sDecodeLock = new Object(); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Creates a new image request, decoding to a maximum specified width and | ||
| 48 | + * height. If both width and height are zero, the image will be decoded to | ||
| 49 | + * its natural size. If one of the two is nonzero, that dimension will be | ||
| 50 | + * clamped and the other one will be set to preserve the image's aspect | ||
| 51 | + * ratio. If both width and height are nonzero, the image will be decoded to | ||
| 52 | + * be fit in the rectangle of dimensions width x height while keeping its | ||
| 53 | + * aspect ratio. | ||
| 54 | + * | ||
| 55 | + * @param url URL of the image | ||
| 56 | + * @param listener Listener to receive the decoded bitmap | ||
| 57 | + * @param maxWidth Maximum width to decode this bitmap to, or zero for none | ||
| 58 | + * @param maxHeight Maximum height to decode this bitmap to, or zero for | ||
| 59 | + * none | ||
| 60 | + * @param decodeConfig Format to decode the bitmap to | ||
| 61 | + * @param errorListener Error listener, or null to ignore errors | ||
| 62 | + */ | ||
| 63 | + public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, | ||
| 64 | + Config decodeConfig, Response.ErrorListener errorListener) { | ||
| 65 | + super(Method.GET, url, errorListener); | ||
| 66 | + setRetryPolicy( | ||
| 67 | + new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT)); | ||
| 68 | + mListener = listener; | ||
| 69 | + mDecodeConfig = decodeConfig; | ||
| 70 | + mMaxWidth = maxWidth; | ||
| 71 | + mMaxHeight = maxHeight; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + @Override | ||
| 75 | + public Priority getPriority() { | ||
| 76 | + return Priority.LOW; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Scales one side of a rectangle to fit aspect ratio. | ||
| 81 | + * | ||
| 82 | + * @param maxPrimary Maximum size of the primary dimension (i.e. width for | ||
| 83 | + * max width), or zero to maintain aspect ratio with secondary | ||
| 84 | + * dimension | ||
| 85 | + * @param maxSecondary Maximum size of the secondary dimension, or zero to | ||
| 86 | + * maintain aspect ratio with primary dimension | ||
| 87 | + * @param actualPrimary Actual size of the primary dimension | ||
| 88 | + * @param actualSecondary Actual size of the secondary dimension | ||
| 89 | + */ | ||
| 90 | + private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, | ||
| 91 | + int actualSecondary) { | ||
| 92 | + // If no dominant value at all, just return the actual. | ||
| 93 | + if (maxPrimary == 0 && maxSecondary == 0) { | ||
| 94 | + return actualPrimary; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + // If primary is unspecified, scale primary to match secondary's scaling ratio. | ||
| 98 | + if (maxPrimary == 0) { | ||
| 99 | + double ratio = (double) maxSecondary / (double) actualSecondary; | ||
| 100 | + return (int) (actualPrimary * ratio); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + if (maxSecondary == 0) { | ||
| 104 | + return maxPrimary; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + double ratio = (double) actualSecondary / (double) actualPrimary; | ||
| 108 | + int resized = maxPrimary; | ||
| 109 | + if (resized * ratio > maxSecondary) { | ||
| 110 | + resized = (int) (maxSecondary / ratio); | ||
| 111 | + } | ||
| 112 | + return resized; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + @Override | ||
| 116 | + protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) { | ||
| 117 | + // Serialize all decode on a global lock to reduce concurrent heap usage. | ||
| 118 | + synchronized (sDecodeLock) { | ||
| 119 | + try { | ||
| 120 | + return doParse(response); | ||
| 121 | + } catch (OutOfMemoryError e) { | ||
| 122 | + VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl()); | ||
| 123 | + return Response.error(new ParseError(e)); | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + /** | ||
| 129 | + * The real guts of parseNetworkResponse. Broken out for readability. | ||
| 130 | + */ | ||
| 131 | + private Response<Bitmap> doParse(NetworkResponse response) { | ||
| 132 | + byte[] data = response.data; | ||
| 133 | + BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); | ||
| 134 | + Bitmap bitmap = null; | ||
| 135 | + if (mMaxWidth == 0 && mMaxHeight == 0) { | ||
| 136 | + decodeOptions.inPreferredConfig = mDecodeConfig; | ||
| 137 | + bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); | ||
| 138 | + } else { | ||
| 139 | + // If we have to resize this image, first get the natural bounds. | ||
| 140 | + decodeOptions.inJustDecodeBounds = true; | ||
| 141 | + BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); | ||
| 142 | + int actualWidth = decodeOptions.outWidth; | ||
| 143 | + int actualHeight = decodeOptions.outHeight; | ||
| 144 | + | ||
| 145 | + // Then compute the dimensions we would ideally like to decode to. | ||
| 146 | + int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, | ||
| 147 | + actualWidth, actualHeight); | ||
| 148 | + int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, | ||
| 149 | + actualHeight, actualWidth); | ||
| 150 | + | ||
| 151 | + // Decode to the nearest power of two scaling factor. | ||
| 152 | + decodeOptions.inJustDecodeBounds = false; | ||
| 153 | + // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it? | ||
| 154 | + // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; | ||
| 155 | + decodeOptions.inSampleSize = | ||
| 156 | + findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); | ||
| 157 | + Bitmap tempBitmap = | ||
| 158 | + BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); | ||
| 159 | + | ||
| 160 | + // If necessary, scale down to the maximal acceptable size. | ||
| 161 | + if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || | ||
| 162 | + tempBitmap.getHeight() > desiredHeight)) { | ||
| 163 | + bitmap = Bitmap.createScaledBitmap(tempBitmap, | ||
| 164 | + desiredWidth, desiredHeight, true); | ||
| 165 | + tempBitmap.recycle(); | ||
| 166 | + } else { | ||
| 167 | + bitmap = tempBitmap; | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + if (bitmap == null) { | ||
| 172 | + return Response.error(new ParseError(response)); | ||
| 173 | + } else { | ||
| 174 | + return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + @Override | ||
| 179 | + protected void deliverResponse(Bitmap response) { | ||
| 180 | + mListener.onResponse(response); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + /** | ||
| 184 | + * Returns the largest power-of-two divisor for use in downscaling a bitmap | ||
| 185 | + * that will not result in the scaling past the desired dimensions. | ||
| 186 | + * | ||
| 187 | + * @param actualWidth Actual width of the bitmap | ||
| 188 | + * @param actualHeight Actual height of the bitmap | ||
| 189 | + * @param desiredWidth Desired width of the bitmap | ||
| 190 | + * @param desiredHeight Desired height of the bitmap | ||
| 191 | + */ | ||
| 192 | + // Visible for testing. | ||
| 193 | + static int findBestSampleSize( | ||
| 194 | + int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { | ||
| 195 | + double wr = (double) actualWidth / desiredWidth; | ||
| 196 | + double hr = (double) actualHeight / desiredHeight; | ||
| 197 | + double ratio = Math.min(wr, hr); | ||
| 198 | + float n = 1.0f; | ||
| 199 | + while ((n * 2) <= ratio) { | ||
| 200 | + n *= 2; | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + return (int) n; | ||
| 204 | + } | ||
| 205 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.NetworkResponse; | ||
| 20 | +import com.android.volley.ParseError; | ||
| 21 | +import com.android.volley.Response; | ||
| 22 | +import com.android.volley.Response.ErrorListener; | ||
| 23 | +import com.android.volley.Response.Listener; | ||
| 24 | +import org.json.JSONArray; | ||
| 25 | +import org.json.JSONException; | ||
| 26 | + | ||
| 27 | +import java.io.UnsupportedEncodingException; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * A request for retrieving a {@link JSONArray} response body at a given URL. | ||
| 31 | + */ | ||
| 32 | +public class JsonArrayRequest extends JsonRequest<JSONArray> { | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Creates a new request. | ||
| 36 | + * @param url URL to fetch the JSON from | ||
| 37 | + * @param listener Listener to receive the JSON response | ||
| 38 | + * @param errorListener Error listener, or null to ignore errors. | ||
| 39 | + */ | ||
| 40 | + public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) { | ||
| 41 | + super(Method.GET, url, null, listener, errorListener); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Override | ||
| 45 | + protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) { | ||
| 46 | + try { | ||
| 47 | + String jsonString = | ||
| 48 | + new String(response.data, HttpHeaderParser.parseCharset(response.headers)); | ||
| 49 | + return Response.success(new JSONArray(jsonString), | ||
| 50 | + HttpHeaderParser.parseCacheHeaders(response)); | ||
| 51 | + } catch (UnsupportedEncodingException e) { | ||
| 52 | + return Response.error(new ParseError(e)); | ||
| 53 | + } catch (JSONException je) { | ||
| 54 | + return Response.error(new ParseError(je)); | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.NetworkResponse; | ||
| 20 | +import com.android.volley.ParseError; | ||
| 21 | +import com.android.volley.Response; | ||
| 22 | +import com.android.volley.Response.ErrorListener; | ||
| 23 | +import com.android.volley.Response.Listener; | ||
| 24 | +import org.json.JSONException; | ||
| 25 | +import org.json.JSONObject; | ||
| 26 | + | ||
| 27 | +import java.io.UnsupportedEncodingException; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * A request for retrieving a {@link JSONObject} response body at a given URL, allowing for an | ||
| 31 | + * optional {@link JSONObject} to be passed in as part of the request body. | ||
| 32 | + */ | ||
| 33 | +public class JsonObjectRequest extends JsonRequest<JSONObject> { | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Creates a new request. | ||
| 37 | + * @param method the HTTP method to use | ||
| 38 | + * @param url URL to fetch the JSON from | ||
| 39 | + * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and | ||
| 40 | + * indicates no parameters will be posted along with request. | ||
| 41 | + * @param listener Listener to receive the JSON response | ||
| 42 | + * @param errorListener Error listener, or null to ignore errors. | ||
| 43 | + */ | ||
| 44 | + public JsonObjectRequest(int method, String url, JSONObject jsonRequest, | ||
| 45 | + Listener<JSONObject> listener, ErrorListener errorListener) { | ||
| 46 | + super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, | ||
| 47 | + errorListener); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is | ||
| 52 | + * <code>null</code>, <code>POST</code> otherwise. | ||
| 53 | + * | ||
| 54 | + * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener) | ||
| 55 | + */ | ||
| 56 | + public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, | ||
| 57 | + ErrorListener errorListener) { | ||
| 58 | + this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, | ||
| 59 | + listener, errorListener); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + @Override | ||
| 63 | + protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { | ||
| 64 | + try { | ||
| 65 | + String jsonString = | ||
| 66 | + new String(response.data, HttpHeaderParser.parseCharset(response.headers)); | ||
| 67 | + return Response.success(new JSONObject(jsonString), | ||
| 68 | + HttpHeaderParser.parseCacheHeaders(response)); | ||
| 69 | + } catch (UnsupportedEncodingException e) { | ||
| 70 | + return Response.error(new ParseError(e)); | ||
| 71 | + } catch (JSONException je) { | ||
| 72 | + return Response.error(new ParseError(je)); | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.NetworkResponse; | ||
| 20 | +import com.android.volley.Request; | ||
| 21 | +import com.android.volley.Response; | ||
| 22 | +import com.android.volley.Response.ErrorListener; | ||
| 23 | +import com.android.volley.Response.Listener; | ||
| 24 | +import com.android.volley.VolleyLog; | ||
| 25 | + | ||
| 26 | +import java.io.UnsupportedEncodingException; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * A request for retrieving a T type response body at a given URL that also | ||
| 30 | + * optionally sends along a JSON body in the request specified. | ||
| 31 | + * | ||
| 32 | + * @param <T> JSON type of response expected | ||
| 33 | + */ | ||
| 34 | +public abstract class JsonRequest<T> extends Request<T> { | ||
| 35 | + /** Charset for request. */ | ||
| 36 | + private static final String PROTOCOL_CHARSET = "utf-8"; | ||
| 37 | + | ||
| 38 | + /** Content type for request. */ | ||
| 39 | + private static final String PROTOCOL_CONTENT_TYPE = | ||
| 40 | + String.format("application/json; charset=%s", PROTOCOL_CHARSET); | ||
| 41 | + | ||
| 42 | + private final Listener<T> mListener; | ||
| 43 | + private final String mRequestBody; | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Deprecated constructor for a JsonRequest which defaults to GET unless {@link #getPostBody()} | ||
| 47 | + * or {@link #getPostParams()} is overridden (which defaults to POST). | ||
| 48 | + * | ||
| 49 | + * @deprecated Use {@link #JsonRequest(int, String, String, Listener, ErrorListener)}. | ||
| 50 | + */ | ||
| 51 | + public JsonRequest(String url, String requestBody, Listener<T> listener, | ||
| 52 | + ErrorListener errorListener) { | ||
| 53 | + this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public JsonRequest(int method, String url, String requestBody, Listener<T> listener, | ||
| 57 | + ErrorListener errorListener) { | ||
| 58 | + super(method, url, errorListener); | ||
| 59 | + mListener = listener; | ||
| 60 | + mRequestBody = requestBody; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + protected void deliverResponse(T response) { | ||
| 65 | + mListener.onResponse(response); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @Override | ||
| 69 | + abstract protected Response<T> parseNetworkResponse(NetworkResponse response); | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * @deprecated Use {@link #getBodyContentType()}. | ||
| 73 | + */ | ||
| 74 | + @Override | ||
| 75 | + public String getPostBodyContentType() { | ||
| 76 | + return getBodyContentType(); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * @deprecated Use {@link #getBody()}. | ||
| 81 | + */ | ||
| 82 | + @Override | ||
| 83 | + public byte[] getPostBody() { | ||
| 84 | + return getBody(); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Override | ||
| 88 | + public String getBodyContentType() { | ||
| 89 | + return PROTOCOL_CONTENT_TYPE; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + @Override | ||
| 93 | + public byte[] getBody() { | ||
| 94 | + try { | ||
| 95 | + return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET); | ||
| 96 | + } catch (UnsupportedEncodingException uee) { | ||
| 97 | + VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", | ||
| 98 | + mRequestBody, PROTOCOL_CHARSET); | ||
| 99 | + return null; | ||
| 100 | + } | ||
| 101 | + } | ||
| 102 | +} |
| 1 | +/** | ||
| 2 | + * Copyright (C) 2013 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package com.android.volley.toolbox; | ||
| 17 | + | ||
| 18 | +import android.content.Context; | ||
| 19 | +import android.text.TextUtils; | ||
| 20 | +import android.util.AttributeSet; | ||
| 21 | +import android.view.ViewGroup.LayoutParams; | ||
| 22 | +import android.widget.ImageView; | ||
| 23 | +import com.android.volley.VolleyError; | ||
| 24 | +import com.android.volley.toolbox.ImageLoader.ImageContainer; | ||
| 25 | +import com.android.volley.toolbox.ImageLoader.ImageListener; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * Handles fetching an image from a URL as well as the life-cycle of the | ||
| 29 | + * associated request. | ||
| 30 | + */ | ||
| 31 | +public class NetworkImageView extends ImageView { | ||
| 32 | + /** The URL of the network image to load */ | ||
| 33 | + private String mUrl; | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Resource ID of the image to be used as a placeholder until the network image is loaded. | ||
| 37 | + */ | ||
| 38 | + private int mDefaultImageId; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * Resource ID of the image to be used if the network response fails. | ||
| 42 | + */ | ||
| 43 | + private int mErrorImageId; | ||
| 44 | + | ||
| 45 | + /** Local copy of the ImageLoader. */ | ||
| 46 | + private ImageLoader mImageLoader; | ||
| 47 | + | ||
| 48 | + /** Current ImageContainer. (either in-flight or finished) */ | ||
| 49 | + private ImageContainer mImageContainer; | ||
| 50 | + | ||
| 51 | + public NetworkImageView(Context context) { | ||
| 52 | + this(context, null); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + public NetworkImageView(Context context, AttributeSet attrs) { | ||
| 56 | + this(context, attrs, 0); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + public NetworkImageView(Context context, AttributeSet attrs, int defStyle) { | ||
| 60 | + super(context, attrs, defStyle); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * Sets URL of the image that should be loaded into this view. Note that calling this will | ||
| 65 | + * immediately either set the cached image (if available) or the default image specified by | ||
| 66 | + * {@link NetworkImageView#setDefaultImageResId(int)} on the view. | ||
| 67 | + * | ||
| 68 | + * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and | ||
| 69 | + * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling | ||
| 70 | + * this function. | ||
| 71 | + * | ||
| 72 | + * @param url The URL that should be loaded into this ImageView. | ||
| 73 | + * @param imageLoader ImageLoader that will be used to make the request. | ||
| 74 | + */ | ||
| 75 | + public void setImageUrl(String url, ImageLoader imageLoader) { | ||
| 76 | + mUrl = url; | ||
| 77 | + mImageLoader = imageLoader; | ||
| 78 | + // The URL has potentially changed. See if we need to load it. | ||
| 79 | + loadImageIfNecessary(false); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + /** | ||
| 83 | + * Sets the default image resource ID to be used for this view until the attempt to load it | ||
| 84 | + * completes. | ||
| 85 | + */ | ||
| 86 | + public void setDefaultImageResId(int defaultImage) { | ||
| 87 | + mDefaultImageId = defaultImage; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + /** | ||
| 91 | + * Sets the error image resource ID to be used for this view in the event that the image | ||
| 92 | + * requested fails to load. | ||
| 93 | + */ | ||
| 94 | + public void setErrorImageResId(int errorImage) { | ||
| 95 | + mErrorImageId = errorImage; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * Loads the image for the view if it isn't already loaded. | ||
| 100 | + * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. | ||
| 101 | + */ | ||
| 102 | + private void loadImageIfNecessary(final boolean isInLayoutPass) { | ||
| 103 | + int width = getWidth(); | ||
| 104 | + int height = getHeight(); | ||
| 105 | + | ||
| 106 | + boolean isFullyWrapContent = getLayoutParams() != null | ||
| 107 | + && getLayoutParams().height == LayoutParams.WRAP_CONTENT | ||
| 108 | + && getLayoutParams().width == LayoutParams.WRAP_CONTENT; | ||
| 109 | + // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content | ||
| 110 | + // view, hold off on loading the image. | ||
| 111 | + if (width == 0 && height == 0 && !isFullyWrapContent) { | ||
| 112 | + return; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + // if the URL to be loaded in this view is empty, cancel any old requests and clear the | ||
| 116 | + // currently loaded image. | ||
| 117 | + if (TextUtils.isEmpty(mUrl)) { | ||
| 118 | + if (mImageContainer != null) { | ||
| 119 | + mImageContainer.cancelRequest(); | ||
| 120 | + mImageContainer = null; | ||
| 121 | + } | ||
| 122 | + setDefaultImageOrNull(); | ||
| 123 | + return; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + // if there was an old request in this view, check if it needs to be canceled. | ||
| 127 | + if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { | ||
| 128 | + if (mImageContainer.getRequestUrl().equals(mUrl)) { | ||
| 129 | + // if the request is from the same URL, return. | ||
| 130 | + return; | ||
| 131 | + } else { | ||
| 132 | + // if there is a pre-existing request, cancel it if it's fetching a different URL. | ||
| 133 | + mImageContainer.cancelRequest(); | ||
| 134 | + setDefaultImageOrNull(); | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + // The pre-existing content of this view didn't match the current URL. Load the new image | ||
| 139 | + // from the network. | ||
| 140 | + ImageContainer newContainer = mImageLoader.get(mUrl, | ||
| 141 | + new ImageListener() { | ||
| 142 | + @Override | ||
| 143 | + public void onErrorResponse(VolleyError error) { | ||
| 144 | + if (mErrorImageId != 0) { | ||
| 145 | + setImageResource(mErrorImageId); | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + @Override | ||
| 150 | + public void onResponse(final ImageContainer response, boolean isImmediate) { | ||
| 151 | + // If this was an immediate response that was delivered inside of a layout | ||
| 152 | + // pass do not set the image immediately as it will trigger a requestLayout | ||
| 153 | + // inside of a layout. Instead, defer setting the image by posting back to | ||
| 154 | + // the main thread. | ||
| 155 | + if (isImmediate && isInLayoutPass) { | ||
| 156 | + post(new Runnable() { | ||
| 157 | + @Override | ||
| 158 | + public void run() { | ||
| 159 | + onResponse(response, false); | ||
| 160 | + } | ||
| 161 | + }); | ||
| 162 | + return; | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + if (response.getBitmap() != null) { | ||
| 166 | + setImageBitmap(response.getBitmap()); | ||
| 167 | + } else if (mDefaultImageId != 0) { | ||
| 168 | + setImageResource(mDefaultImageId); | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | + }); | ||
| 172 | + | ||
| 173 | + // update the ImageContainer to be the new bitmap container. | ||
| 174 | + mImageContainer = newContainer; | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + private void setDefaultImageOrNull() { | ||
| 178 | + if(mDefaultImageId != 0) { | ||
| 179 | + setImageResource(mDefaultImageId); | ||
| 180 | + } | ||
| 181 | + else { | ||
| 182 | + setImageBitmap(null); | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + @Override | ||
| 187 | + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | ||
| 188 | + super.onLayout(changed, left, top, right, bottom); | ||
| 189 | + loadImageIfNecessary(true); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + @Override | ||
| 193 | + protected void onDetachedFromWindow() { | ||
| 194 | + if (mImageContainer != null) { | ||
| 195 | + // If the view was bound to an image request, cancel it and clear | ||
| 196 | + // out the image from the view. | ||
| 197 | + mImageContainer.cancelRequest(); | ||
| 198 | + setImageBitmap(null); | ||
| 199 | + // also clear out the container so we can reload the image if necessary. | ||
| 200 | + mImageContainer = null; | ||
| 201 | + } | ||
| 202 | + super.onDetachedFromWindow(); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + @Override | ||
| 206 | + protected void drawableStateChanged() { | ||
| 207 | + super.drawableStateChanged(); | ||
| 208 | + invalidate(); | ||
| 209 | + } | ||
| 210 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.Cache; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * A cache that doesn't. | ||
| 23 | + */ | ||
| 24 | +public class NoCache implements Cache { | ||
| 25 | + @Override | ||
| 26 | + public void clear() { | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + @Override | ||
| 30 | + public Entry get(String key) { | ||
| 31 | + return null; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public void put(String key, Entry entry) { | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @Override | ||
| 39 | + public void invalidate(String key, boolean fullExpire) { | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + public void remove(String key) { | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Override | ||
| 47 | + public void initialize() { | ||
| 48 | + } | ||
| 49 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2012 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import java.io.ByteArrayOutputStream; | ||
| 20 | +import java.io.IOException; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * A variation of {@link ByteArrayOutputStream} that uses a pool of byte[] buffers instead | ||
| 24 | + * of always allocating them fresh, saving on heap churn. | ||
| 25 | + */ | ||
| 26 | +public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { | ||
| 27 | + /** | ||
| 28 | + * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is | ||
| 29 | + * the default size to which the underlying byte array is initialized. | ||
| 30 | + */ | ||
| 31 | + private static final int DEFAULT_SIZE = 256; | ||
| 32 | + | ||
| 33 | + private final ByteArrayPool mPool; | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written | ||
| 37 | + * to this instance, the underlying byte array will expand. | ||
| 38 | + */ | ||
| 39 | + public PoolingByteArrayOutputStream(ByteArrayPool pool) { | ||
| 40 | + this(pool, DEFAULT_SIZE); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If | ||
| 45 | + * more than {@code size} bytes are written to this instance, the underlying byte array will | ||
| 46 | + * expand. | ||
| 47 | + * | ||
| 48 | + * @param size initial size for the underlying byte array. The value will be pinned to a default | ||
| 49 | + * minimum size. | ||
| 50 | + */ | ||
| 51 | + public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { | ||
| 52 | + mPool = pool; | ||
| 53 | + buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Override | ||
| 57 | + public void close() throws IOException { | ||
| 58 | + mPool.returnBuf(buf); | ||
| 59 | + buf = null; | ||
| 60 | + super.close(); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + public void finalize() { | ||
| 65 | + mPool.returnBuf(buf); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * Ensures there is enough space in the buffer for the given number of additional bytes. | ||
| 70 | + */ | ||
| 71 | + private void expand(int i) { | ||
| 72 | + /* Can the buffer handle @i more bytes, if not expand it */ | ||
| 73 | + if (count + i <= buf.length) { | ||
| 74 | + return; | ||
| 75 | + } | ||
| 76 | + byte[] newbuf = mPool.getBuf((count + i) * 2); | ||
| 77 | +// System.arraycopy(buf, 0, newbuf, 0, count); | ||
| 78 | + for (int j = 0; j < count; j++) { | ||
| 79 | + newbuf[j] = buf[j]; | ||
| 80 | + } | ||
| 81 | + mPool.returnBuf(buf); | ||
| 82 | + buf = newbuf; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public synchronized void write(byte[] buffer, int offset, int len) { | ||
| 87 | + expand(len); | ||
| 88 | + super.write(buffer, offset, len); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + @Override | ||
| 92 | + public synchronized void write(int oneByte) { | ||
| 93 | + expand(1); | ||
| 94 | + super.write(oneByte); | ||
| 95 | + } | ||
| 96 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.Request; | ||
| 20 | +import com.android.volley.Response; | ||
| 21 | +import com.android.volley.VolleyError; | ||
| 22 | + | ||
| 23 | +import java.util.concurrent.ExecutionException; | ||
| 24 | +import java.util.concurrent.Future; | ||
| 25 | +import java.util.concurrent.TimeUnit; | ||
| 26 | +import java.util.concurrent.TimeoutException; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * A Future that represents a Volley request. | ||
| 30 | + * | ||
| 31 | + * Used by providing as your response and error listeners. For example: | ||
| 32 | + * <pre> | ||
| 33 | + * RequestFuture<JSONObject> future = RequestFuture.newFuture(); | ||
| 34 | + * MyRequest request = new MyRequest(URL, future, future); | ||
| 35 | + * | ||
| 36 | + * // If you want to be able to cancel the request: | ||
| 37 | + * future.setRequest(requestQueue.add(request)); | ||
| 38 | + * | ||
| 39 | + * // Otherwise: | ||
| 40 | + * requestQueue.add(request); | ||
| 41 | + * | ||
| 42 | + * try { | ||
| 43 | + * JSONObject response = future.get(); | ||
| 44 | + * // do something with response | ||
| 45 | + * } catch (InterruptedException e) { | ||
| 46 | + * // handle the error | ||
| 47 | + * } catch (ExecutionException e) { | ||
| 48 | + * // handle the error | ||
| 49 | + * } | ||
| 50 | + * </pre> | ||
| 51 | + * | ||
| 52 | + * @param <T> The type of parsed response this future expects. | ||
| 53 | + */ | ||
| 54 | +public class RequestFuture<T> implements Future<T>, Response.Listener<T>, | ||
| 55 | + Response.ErrorListener { | ||
| 56 | + private Request<?> mRequest; | ||
| 57 | + private boolean mResultReceived = false; | ||
| 58 | + private T mResult; | ||
| 59 | + private VolleyError mException; | ||
| 60 | + | ||
| 61 | + public static <E> RequestFuture<E> newFuture() { | ||
| 62 | + return new RequestFuture<E>(); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + private RequestFuture() {} | ||
| 66 | + | ||
| 67 | + public void setRequest(Request<?> request) { | ||
| 68 | + mRequest = request; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + @Override | ||
| 72 | + public synchronized boolean cancel(boolean mayInterruptIfRunning) { | ||
| 73 | + if (mRequest == null) { | ||
| 74 | + return false; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + if (!isDone()) { | ||
| 78 | + mRequest.cancel(); | ||
| 79 | + return true; | ||
| 80 | + } else { | ||
| 81 | + return false; | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public T get() throws InterruptedException, ExecutionException { | ||
| 87 | + try { | ||
| 88 | + return doGet(null); | ||
| 89 | + } catch (TimeoutException e) { | ||
| 90 | + throw new AssertionError(e); | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + @Override | ||
| 95 | + public T get(long timeout, TimeUnit unit) | ||
| 96 | + throws InterruptedException, ExecutionException, TimeoutException { | ||
| 97 | + return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit)); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + private synchronized T doGet(Long timeoutMs) | ||
| 101 | + throws InterruptedException, ExecutionException, TimeoutException { | ||
| 102 | + if (mException != null) { | ||
| 103 | + throw new ExecutionException(mException); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + if (mResultReceived) { | ||
| 107 | + return mResult; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + if (timeoutMs == null) { | ||
| 111 | + wait(0); | ||
| 112 | + } else if (timeoutMs > 0) { | ||
| 113 | + wait(timeoutMs); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + if (mException != null) { | ||
| 117 | + throw new ExecutionException(mException); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + if (!mResultReceived) { | ||
| 121 | + throw new TimeoutException(); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + return mResult; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + @Override | ||
| 128 | + public boolean isCancelled() { | ||
| 129 | + if (mRequest == null) { | ||
| 130 | + return false; | ||
| 131 | + } | ||
| 132 | + return mRequest.isCanceled(); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + @Override | ||
| 136 | + public synchronized boolean isDone() { | ||
| 137 | + return mResultReceived || mException != null || isCancelled(); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + @Override | ||
| 141 | + public synchronized void onResponse(T response) { | ||
| 142 | + mResultReceived = true; | ||
| 143 | + mResult = response; | ||
| 144 | + notifyAll(); | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + @Override | ||
| 148 | + public synchronized void onErrorResponse(VolleyError error) { | ||
| 149 | + mException = error; | ||
| 150 | + notifyAll(); | ||
| 151 | + } | ||
| 152 | +} | ||
| 153 | + |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2011 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import com.android.volley.NetworkResponse; | ||
| 20 | +import com.android.volley.Request; | ||
| 21 | +import com.android.volley.Response; | ||
| 22 | +import com.android.volley.Response.ErrorListener; | ||
| 23 | +import com.android.volley.Response.Listener; | ||
| 24 | + | ||
| 25 | +import java.io.UnsupportedEncodingException; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * A canned request for retrieving the response body at a given URL as a String. | ||
| 29 | + */ | ||
| 30 | +public class StringRequest extends Request<String> { | ||
| 31 | + private final Listener<String> mListener; | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Creates a new request with the given method. | ||
| 35 | + * | ||
| 36 | + * @param method the request {@link Method} to use | ||
| 37 | + * @param url URL to fetch the string at | ||
| 38 | + * @param listener Listener to receive the String response | ||
| 39 | + * @param errorListener Error listener, or null to ignore errors | ||
| 40 | + */ | ||
| 41 | + public StringRequest(int method, String url, Listener<String> listener, | ||
| 42 | + ErrorListener errorListener) { | ||
| 43 | + super(method, url, errorListener); | ||
| 44 | + mListener = listener; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * Creates a new GET request. | ||
| 49 | + * | ||
| 50 | + * @param url URL to fetch the string at | ||
| 51 | + * @param listener Listener to receive the String response | ||
| 52 | + * @param errorListener Error listener, or null to ignore errors | ||
| 53 | + */ | ||
| 54 | + public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { | ||
| 55 | + this(Method.GET, url, listener, errorListener); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + protected void deliverResponse(String response) { | ||
| 60 | + mListener.onResponse(response); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + protected Response<String> parseNetworkResponse(NetworkResponse response) { | ||
| 65 | + String parsed; | ||
| 66 | + try { | ||
| 67 | + parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); | ||
| 68 | + } catch (UnsupportedEncodingException e) { | ||
| 69 | + parsed = new String(response.data); | ||
| 70 | + } | ||
| 71 | + return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); | ||
| 72 | + } | ||
| 73 | +} |
| 1 | +/* | ||
| 2 | + * Copyright (C) 2012 The Android Open Source Project | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package com.android.volley.toolbox; | ||
| 18 | + | ||
| 19 | +import android.content.Context; | ||
| 20 | +import android.content.pm.PackageInfo; | ||
| 21 | +import android.content.pm.PackageManager.NameNotFoundException; | ||
| 22 | +import android.net.http.AndroidHttpClient; | ||
| 23 | +import android.os.Build; | ||
| 24 | +import com.android.volley.Network; | ||
| 25 | +import com.android.volley.RequestQueue; | ||
| 26 | + | ||
| 27 | +import java.io.File; | ||
| 28 | + | ||
| 29 | +public class Volley { | ||
| 30 | + | ||
| 31 | + /** Default on-disk cache directory. */ | ||
| 32 | + private static final String DEFAULT_CACHE_DIR = "volley"; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. | ||
| 36 | + * | ||
| 37 | + * @param context A {@link Context} to use for creating the cache dir. | ||
| 38 | + * @param stack An {@link HttpStack} to use for the network, or null for default. | ||
| 39 | + * @return A started {@link RequestQueue} instance. | ||
| 40 | + */ | ||
| 41 | + public static RequestQueue newRequestQueue(Context context, HttpStack stack) { | ||
| 42 | + File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); | ||
| 43 | + | ||
| 44 | + String userAgent = "volley/0"; | ||
| 45 | + try { | ||
| 46 | + String packageName = context.getPackageName(); | ||
| 47 | + PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); | ||
| 48 | + userAgent = packageName + "/" + info.versionCode; | ||
| 49 | + } catch (NameNotFoundException e) { | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + if (stack == null) { | ||
| 53 | + if (Build.VERSION.SDK_INT >= 9) { | ||
| 54 | + stack = new HurlStack(); | ||
| 55 | + } else { | ||
| 56 | + // Prior to Gingerbread, HttpUrlConnection was unreliable. | ||
| 57 | + // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html | ||
| 58 | + stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + Network network = new BasicNetwork(stack); | ||
| 63 | + | ||
| 64 | + RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); | ||
| 65 | + queue.start(); | ||
| 66 | + | ||
| 67 | + return queue; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. | ||
| 72 | + * | ||
| 73 | + * @param context A {@link Context} to use for creating the cache dir. | ||
| 74 | + * @return A started {@link RequestQueue} instance. | ||
| 75 | + */ | ||
| 76 | + public static RequestQueue newRequestQueue(Context context) { | ||
| 77 | + return newRequestQueue(context, null); | ||
| 78 | + } | ||
| 79 | +} |
volley/src/main/res/values/strings.xml
0 → 100644
| 1 | +<!-- | ||
| 2 | + ~ Copyright (c) 2016. wugian | ||
| 3 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + ~ you may not use this file except in compliance with the License. | ||
| 5 | + ~ You may obtain a copy of the License at | ||
| 6 | + ~ | ||
| 7 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + ~ | ||
| 9 | + ~ Unless required by applicable law or agreed to in writing, software | ||
| 10 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + ~ See the License for the specific language governing permissions and | ||
| 13 | + ~ limitations under the License. | ||
| 14 | + ~ | ||
| 15 | + --> | ||
| 16 | + | ||
| 17 | +<resources> | ||
| 18 | +</resources> |
Please
register
or
login
to post a comment