Commit 91657defbce09e1d929f26939f3097e5c61b4393

Authored by Penley
1 parent 724a0c23

init other subproject impl lock control

Showing 91 changed files with 8513 additions and 0 deletions
  1 +*.iml
  2 +.gradle
  3 +.idea
  4 +/dist
  5 +/gradle
  6 +import-summary.txt
  7 +/local.properties
  8 +.DS_Store
  9 +/build
  10 +/captures
  11 +/freeline
  12 +freeline_core
  13 +freeline.py
  14 +*.hprof
  15 +freeline_project_description.json
  1 +# GimiCinema
  2 +基于Mstar 平台的本地点播系统
  3 +
  4 +* Email:[pan.li@qnbar.com](mailto:pan.li@qnbar.com)
  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 +
  1 +/build
  2 +debug-db.iml
  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'
  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 +}
No preview for this file type
  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 +}
  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>
  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 +}
  1 +.padding-fifty {
  2 + padding-top: 50px;
  3 +}
  4 +
  5 +.padding-twenty {
  6 + padding-top: 20px;
  7 +}
  8 +
  9 +.display-none {
  10 + display: none;
  11 +}
  12 +
  13 +.list-group-item {
  14 + word-break: break-all;
  15 +}
No preview for this file type
  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 +}
  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"/>
  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 +
  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 "$@"
  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
  1 +include ':transitionhelper', ':uil', ':volley', ':pinying', ':debug-db'
  2 +include ':gimiCinema'
  1 +/build
  2 +transitionhelper.iml
  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 +//}
  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 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2 + package="immortalz.me.library">
  3 +
  4 + <application
  5 + android:allowBackup="true"
  6 + android:supportsRtl="true">
  7 + </application>
  8 +
  9 +</manifest>
  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 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<set xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <alpha
  4 + android:duration="3000"
  5 + android:fromAlpha="0"
  6 + android:toAlpha="1" />
  7 +
  8 +</set>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<set xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <alpha
  4 + android:duration="3000"
  5 + android:fromAlpha="1"
  6 + android:toAlpha="0" />
  7 +</set>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <color name="showmethod_start_color">#FFFFFF</color>
  4 + <color name="showmethod_end_color">#C8C8C8</color>
  5 + <color name="colorPrimaryDark">#303F9F</color>
  6 +</resources>
  1 +<resources>
  2 +</resources>
  1 +/build
  2 +volley.iml
  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 +}
  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 +#}
  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.&lt;tag&gt;}
  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&lt;JSONObject&gt; 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 +}
  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