diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..bc3eb86
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b0a270f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5525e4c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..555bd48
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..065a7d5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+ defaultConfig {
+ applicationId "android_challenge.com.moviesdatabase"
+ minSdkVersion 11
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:recyclerview-v7:25.1.0'
+ compile 'com.android.support:cardview-v7:25.1.0'
+ compile 'com.android.support:design:25.1.0'
+ compile 'com.android.support:appcompat-v7:25.1.0'
+ compile 'com.android.support.constraint:constraint-layout:1.0.2'
+ compile 'com.android.volley:volley:1.0.0'
+ testCompile 'junit:junit:4.12'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..6af4248
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\Fabio_2\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/android_challenge/com/moviesdatabase/ExampleInstrumentedTest.java b/app/src/androidTest/java/android_challenge/com/moviesdatabase/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..1fb953f
--- /dev/null
+++ b/app/src/androidTest/java/android_challenge/com/moviesdatabase/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package android_challenge.com.moviesdatabase;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("android_challenge.com.moviesdatabase", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6df06e8
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MovieDetails.java b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MovieDetails.java
new file mode 100644
index 0000000..ee1f154
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MovieDetails.java
@@ -0,0 +1,148 @@
+package android_challenge.com.moviesdatabase.Activity;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+import org.json.JSONException;
+import org.json.JSONObject;
+import android_challenge.com.moviesdatabase.Interface.ResultsMovie;
+import android_challenge.com.moviesdatabase.R;
+import android_challenge.com.moviesdatabase.WebRequests.CustomVolleyRequestQueue;
+import android_challenge.com.moviesdatabase.WebRequests.MovieDetailsRequest;
+
+public class MovieDetails extends AppCompatActivity {
+
+ private String imdbID;
+
+ private TextView title;
+ private TextView type;
+ private TextView year;
+ private TextView released;
+ private TextView rated;
+ private TextView runtime;
+ private TextView director;
+ private TextView genre;
+ private TextView actors;
+ private TextView plot;
+ private TextView awards;
+ private TextView language;
+ private TextView country;
+ private TextView metascore;
+ private TextView imdbrating;
+ private TextView imdbvotes;
+ private TextView writer;
+ private NetworkImageView mNetworkImageView;
+ private ImageLoader mImageLoader;
+
+ ResultsMovie initResultsmovieCallback1 = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_movie_details);
+
+ getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ getSupportActionBar().setCustomView(R.layout.actionbar_detail);
+
+ title=(TextView)findViewById(getResources().getIdentifier("action_bar_title", "id", getPackageName()));
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ imdbID = null;
+ } else {
+ imdbID = extras.getString("ID");
+ }
+
+ type = (TextView) findViewById(R.id.type_movie);
+ year = (TextView) findViewById(R.id.year_movie);
+ released = (TextView) findViewById(R.id.released_movie);
+ rated = (TextView) findViewById(R.id.rated_movie);
+ runtime = (TextView) findViewById(R.id.runtime_movie);
+ director = (TextView) findViewById(R.id.director_movie);
+ genre = (TextView) findViewById(R.id.genre_movie);
+ actors = (TextView) findViewById(R.id.actors_movie);
+ plot = (TextView) findViewById(R.id.plot_movie);
+ awards = (TextView) findViewById(R.id.awards_movie);
+ language = (TextView) findViewById(R.id.language_movie);
+ country = (TextView) findViewById(R.id.country_movie);
+ metascore = (TextView) findViewById(R.id.metascore_movie);
+ imdbrating = (TextView) findViewById(R.id.imdbrating_movie);
+ imdbvotes = (TextView) findViewById(R.id.imdbvotes_movie);
+ writer = (TextView) findViewById(R.id.writer_movie);
+ mNetworkImageView = (NetworkImageView)findViewById(R.id.poster_movie);
+ makeRequest();
+ }
+
+ public void makeRequest() {
+
+ ConnectivityManager connMgr = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+
+ if (networkInfo != null && networkInfo.isConnected()) {
+ initResultsVolleyArrayCallback();
+ MovieDetailsRequest moviedetails = new MovieDetailsRequest(initResultsmovieCallback1);
+ moviedetails.searchByID(this, imdbID);
+ } else {
+ new AlertDialog.Builder(MovieDetails.this)
+ .setTitle("Sem Internet!")
+ .setMessage("Não tem nenhuma conexão de rede disponível!")
+ .setPositiveButton(R.string.box_msg_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
+
+
+ void initResultsVolleyArrayCallback() {
+
+ initResultsmovieCallback1 = new ResultsMovie() {
+
+ @Override
+ public void notifySuccess(JSONObject movieInformations) {
+ try {
+ title.setText(movieInformations.getString("Title"));
+ type.setText(movieInformations.getString("Type"));
+ year.setText(movieInformations.getString("Year"));
+ released.setText(movieInformations.getString("Released"));
+ rated.setText(movieInformations.getString("Rated"));
+ runtime.setText(movieInformations.getString("Runtime"));
+ director.setText(movieInformations.getString("Director"));
+ genre.setText(movieInformations.getString("Genre"));
+ actors.setText(movieInformations.getString("Actors"));
+ plot.setText(movieInformations.getString("Plot"));
+ awards.setText(movieInformations.getString("Awards"));
+ language.setText(movieInformations.getString("Language"));
+ country.setText(movieInformations.getString("Country"));
+ metascore.setText(movieInformations.getString("Metascore"));
+ imdbrating.setText(movieInformations.getString("imdbRating"));
+ imdbvotes.setText(movieInformations.getString("imdbVotes"));
+ writer.setText(movieInformations.getString("Writer"));
+ mImageLoader = CustomVolleyRequestQueue.getInstance(MovieDetails.this).getImageLoader();
+ mImageLoader.get(movieInformations.getString("Poster"), ImageLoader.getImageListener(mNetworkImageView,
+ R.mipmap.ic_launcher, android.R.drawable.ic_dialog_alert));
+ mNetworkImageView.setImageUrl(movieInformations.getString("Poster"), mImageLoader);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void notifyError(String error) {
+
+ }
+ };
+ }
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MoviesList.java b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MoviesList.java
new file mode 100644
index 0000000..5136b57
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/MoviesList.java
@@ -0,0 +1,233 @@
+package android_challenge.com.moviesdatabase.Activity;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
+import android_challenge.com.moviesdatabase.Adapter.MovieListAdapter;
+import android_challenge.com.moviesdatabase.Pojo.Movies;
+import android_challenge.com.moviesdatabase.R;
+import android_challenge.com.moviesdatabase.WebRequests.SearchMovies;
+
+public class MoviesList extends AppCompatActivity implements MovieListAdapter.OnDataSelected, SearchView.OnQueryTextListener {
+
+ private RecyclerView.Adapter adapter;
+ private RecyclerView recyclerView;
+ private Button seemoremovies;
+ private LinearLayoutManager linearLayoutManager;
+ public List moviesList = new ArrayList<>();
+ private TextView notfound;
+ private int page = 1;
+ private int scrolled = 1;
+ private int idscrooled;
+ private LinearLayout linearLayout;
+ private MenuItem item;
+ private SearchView searchView;
+ private String text;
+ private String title = "Star";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_movies_list);
+
+ notfound = (TextView) findViewById(R.id.notfound);
+ recyclerView = (RecyclerView) findViewById(R.id.recycle_view_movie);
+ recyclerView.setHasFixedSize(true);
+ linearLayoutManager = new LinearLayoutManager(this);
+ linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+ recyclerView.setLayoutManager(linearLayoutManager);
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(final RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ if (moviesList.size() == linearLayoutManager.findLastCompletelyVisibleItemPosition() + scrolled) {
+ scrolled = -1;
+ idscrooled = linearLayoutManager.findLastCompletelyVisibleItemPosition();
+ seemoremovies = new Button(MoviesList.this);
+ seemoremovies.setText("Ver mais");
+ seemoremovies.setBackgroundColor(getResources().getColor(R.color.cardview_light_background));
+ seemoremovies.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
+ seemoremovies.setTextSize(16);
+ linearLayout = (LinearLayout) findViewById(R.id.see_more_products);
+ linearLayout.addView(seemoremovies);
+
+ seemoremovies.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ page = page + 1;
+
+ ConnectivityManager connMgr = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+
+ if (networkInfo != null && networkInfo.isConnected()) {
+ SearchMovies searchMovies = new SearchMovies();
+ searchMovies.getMovies(MoviesList.this, title, page, moviesList, adapter, notfound);
+ linearLayout.removeView(seemoremovies);
+ scrolled = 1;
+ } else {
+ new AlertDialog.Builder(MoviesList.this)
+ .setTitle("Sem Internet!")
+ .setMessage("Não tem nenhuma conexão de rede disponível!")
+ .setPositiveButton(R.string.box_msg_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
+ });
+ }
+ if (idscrooled != 0) {
+ if (idscrooled != linearLayoutManager.findLastVisibleItemPosition()) {
+ linearLayout.removeView(seemoremovies);
+ scrolled = 1;
+ }
+ }
+ }
+ });
+
+ adapter = new MovieListAdapter(this, (MovieListAdapter.OnDataSelected) this, moviesList);
+ recyclerView.setAdapter(adapter);
+
+ makerequest();
+
+ }
+ public void makerequest() {
+
+ ConnectivityManager connMgr = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+
+ if (networkInfo != null && networkInfo.isConnected()) {
+ SearchMovies moviesRequests = new SearchMovies();
+ moviesRequests.getMovies(MoviesList.this, this.title, this.page, moviesList, adapter, notfound);
+ } else {
+ new AlertDialog.Builder(MoviesList.this)
+ .setTitle("Sem Internet!")
+ .setMessage("Não tem nenhuma conexão de rede disponível!")
+ .setPositiveButton(R.string.box_msg_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
+
+
+ @Override
+ public void onDataSelected(View view, int position) {
+
+ Movies selectedItem;
+ selectedItem = moviesList.get(position);
+ String imdbID = selectedItem.imdbID;
+
+ Intent intent = new Intent(this, MovieDetails.class);
+ intent.putExtra("ID", imdbID);
+ startActivity(intent);
+
+ }
+
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.search_menu, menu);
+
+ item = menu.findItem(R.id.action_search);
+ item.setVisible(true);
+ searchView = (SearchView) MenuItemCompat.getActionView(item);
+ searchView.setIconifiedByDefault(true);
+ searchView.setQueryHint("Pesquisar filme");
+ searchView.setOnQueryTextListener(MoviesList.this);
+
+ searchView.setOnSearchClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setItemsVisibility(menu, item, false);
+
+ }
+ });
+
+ searchView.setOnCloseListener(new SearchView.OnCloseListener() {
+ @Override
+ public boolean onClose() {
+ setItemsVisibility(menu, item, true);
+ return false;
+ }
+ });
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ private void setItemsVisibility(Menu menu, MenuItem exception, boolean visible) {
+ for (int i=0; i();
+ adapter = new MovieListAdapter(this,(MovieListAdapter.OnDataSelected)this,moviesList);
+ recyclerView.setAdapter(adapter);
+ page = 1;
+ title = text.toString().trim();
+ notfound.setText("");
+
+ ConnectivityManager connMgr = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+
+ if (networkInfo != null && networkInfo.isConnected()) {
+ SearchMovies searchMovies = new SearchMovies();
+ searchMovies.getMovies(MoviesList.this, title, page, moviesList, adapter, notfound);
+ searchView.clearFocus();
+ } else{
+ new AlertDialog.Builder(MoviesList.this)
+ .setTitle("Sem Internet!")
+ .setMessage("Não tem nenhuma conexão de rede disponível!")
+ .setPositiveButton(R.string.box_msg_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ }
+
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Activity/SplashScreen.java b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/SplashScreen.java
new file mode 100644
index 0000000..3ebaa86
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Activity/SplashScreen.java
@@ -0,0 +1,101 @@
+package android_challenge.com.moviesdatabase.Activity;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import android_challenge.com.moviesdatabase.R;
+
+public class SplashScreen extends AppCompatActivity {
+
+ ImageView imageView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ imageView = (ImageView) findViewById(R.id.imageview_logo);
+
+ imageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.drawable.logo_imdb, 600, 600));
+
+ StartAnimations();
+ }
+
+ public static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
+
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+
+ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
+ int reqWidth, int reqHeight) {
+
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+
+ private void StartAnimations(){
+ Animation anim = AnimationUtils.loadAnimation(this, R.anim.alpha);
+ anim.reset();
+ LinearLayout l = (LinearLayout) findViewById(R.id.splashscreen);
+ if (l != null){
+ l.clearAnimation();
+ l.startAnimation(anim);
+ }
+
+ anim = AnimationUtils.loadAnimation(this, R.anim.translate);
+ anim.reset();
+ ImageView iv = (ImageView) findViewById(R.id.imageview_logo);
+ if (iv != null){
+ iv.clearAnimation();
+ iv.startAnimation(anim);
+ }
+
+ int SPLASH_DISPLAY_LENGTH = 6000;
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent (SplashScreen.this, MoviesList.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ SplashScreen.this.finish();
+ }
+ }, SPLASH_DISPLAY_LENGTH);
+ }
+
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Adapter/MovieListAdapter.java b/app/src/main/java/android_challenge/com/moviesdatabase/Adapter/MovieListAdapter.java
new file mode 100644
index 0000000..dc005c8
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Adapter/MovieListAdapter.java
@@ -0,0 +1,100 @@
+package android_challenge.com.moviesdatabase.Adapter;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.android.volley.Response;
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+import org.json.JSONArray;
+import java.util.List;
+import android_challenge.com.moviesdatabase.Pojo.Movies;
+import android_challenge.com.moviesdatabase.R;
+import android_challenge.com.moviesdatabase.WebRequests.CustomVolleyRequestQueue;
+
+/**
+ * Created by Fabio_2 on 10/12/2018.
+ */
+
+public class MovieListAdapter extends RecyclerView.Adapter implements Response.Listener {
+
+ private List moviesList;
+ private Context context;
+ private MovieListAdapter.OnDataSelected onDataSelected;
+ private ImageLoader mImageLoader;
+
+
+ public MovieListAdapter(Context context, MovieListAdapter.OnDataSelected onDataSelected, List moviesList) {
+ this.context = context;
+ this.onDataSelected = onDataSelected;
+ this.moviesList = moviesList;
+ }
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_movie, parent, false);
+ ViewHolder viewHolder = new ViewHolder(view);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(MovieListAdapter.ViewHolder holder, int position) {
+ Movies movies = moviesList.get(position);
+
+ holder.textViewTitleMovie.setText(movies.getTitle());
+ holder.textViewTypeMovie.setText(movies.getType());
+ holder.textViewYearMovie.setText(String.valueOf(movies.getYear()));
+
+ mImageLoader = CustomVolleyRequestQueue.getInstance(context).getImageLoader();
+ mImageLoader.get(movies.getPoster(),ImageLoader.getImageListener(holder.mNetworkImageView,R.mipmap.ic_launcher, android.R.drawable.ic_dialog_alert));
+ holder.mNetworkImageView.setImageUrl(movies.getPoster(),mImageLoader);
+ }
+
+ @Override
+ public int getItemCount() {
+ return moviesList.size();
+ }
+
+
+ @Override
+ public void onResponse(JSONArray response) {
+ }
+
+ public interface OnDataSelected {
+ void onDataSelected(View view, int position);
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder{
+
+ public TextView textViewTitleMovie;
+ public TextView textViewTypeMovie;
+ public TextView textViewYearMovie;
+ public NetworkImageView mNetworkImageView;
+
+ public ViewHolder(View view) {
+ super(view);
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ treatOnDataSelectedIfNecessary(v,getAdapterPosition());
+ }
+ });
+
+ textViewTitleMovie = (TextView)view.findViewById(R.id.title_movie);
+ textViewTypeMovie = (TextView) view.findViewById(R.id.type_movie);
+ textViewYearMovie = (TextView)view.findViewById(R.id.year_movie);
+ mNetworkImageView = (NetworkImageView)view.findViewById(R.id.poster_movie);
+
+ }
+
+ private void treatOnDataSelectedIfNecessary(View view, int position) {
+ if(onDataSelected != null) {
+ onDataSelected.onDataSelected(view, position);
+
+ }
+ }
+
+ }
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Interface/ResultsMovie.java b/app/src/main/java/android_challenge/com/moviesdatabase/Interface/ResultsMovie.java
new file mode 100644
index 0000000..6d6d8a5
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Interface/ResultsMovie.java
@@ -0,0 +1,14 @@
+package android_challenge.com.moviesdatabase.Interface;
+
+import org.json.JSONObject;
+
+/**
+ * Created by Fabio_2 on 12/12/2018.
+ */
+
+public interface ResultsMovie {
+
+ void notifySuccess(JSONObject movieInformations);
+ void notifyError(String error);
+
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/Pojo/Movies.java b/app/src/main/java/android_challenge/com/moviesdatabase/Pojo/Movies.java
new file mode 100644
index 0000000..5e3577c
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/Pojo/Movies.java
@@ -0,0 +1,34 @@
+package android_challenge.com.moviesdatabase.Pojo;
+
+/**
+ * Created by Fabio_2 on 10/12/2018.
+ */
+
+public class Movies {
+
+ public String title;
+ public int year;
+ public String imdbID;
+ public String type;
+ public String poster;
+
+ public Movies(String title, int year, String imdbID, String type, String poster){
+ this.title = title;
+ this.year = year;
+ this.imdbID = imdbID;
+ this.type = type;
+ this.poster = poster;
+
+ }
+
+ public String getTitle(){ return this.title; }
+
+ public int getYear(){ return this.year; }
+
+ public String getImdbID(){ return this.imdbID; }
+
+ public String getType(){ return this.type; }
+
+ public String getPoster(){ return this.poster; }
+
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/CustomVolleyRequestQueue.java b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/CustomVolleyRequestQueue.java
new file mode 100644
index 0000000..9b696cf
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/CustomVolleyRequestQueue.java
@@ -0,0 +1,68 @@
+package android_challenge.com.moviesdatabase.WebRequests;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.LruCache;
+import com.android.volley.Cache;
+import com.android.volley.Network;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.BasicNetwork;
+import com.android.volley.toolbox.DiskBasedCache;
+import com.android.volley.toolbox.HurlStack;
+import com.android.volley.toolbox.ImageLoader;
+
+/**
+ * Created by Fabio_2 on 10/12/2018.
+ */
+
+public class CustomVolleyRequestQueue {
+
+ private static CustomVolleyRequestQueue mInstance;
+ private static Context mCtx;
+ private RequestQueue mRequestQueue;
+ private ImageLoader mImageLoader;
+
+
+ private CustomVolleyRequestQueue(Context context) {
+ mCtx = context;
+ mRequestQueue = getRequestQueue();
+
+ mImageLoader = new ImageLoader(mRequestQueue,
+ new ImageLoader.ImageCache() {
+ private final LruCache
+ cache = new LruCache(20);
+
+ @Override
+ public Bitmap getBitmap(String url) {
+ return cache.get(url);
+ }
+
+ @Override
+ public void putBitmap(String url, Bitmap bitmap) {
+ cache.put(url, bitmap);
+ }
+ });
+ }
+
+ public static synchronized CustomVolleyRequestQueue getInstance(Context context) {
+ if (mInstance == null) {
+ mInstance = new CustomVolleyRequestQueue(context);
+ }
+ return mInstance;
+ }
+
+ public RequestQueue getRequestQueue() {
+ if (mRequestQueue == null) {
+ Cache cache = new DiskBasedCache(mCtx.getCacheDir(), 10 * 1024 * 1024);
+ Network network = new BasicNetwork(new HurlStack());
+ mRequestQueue = new RequestQueue(cache, network);
+ mRequestQueue.start();
+ }
+ return mRequestQueue;
+ }
+
+ public ImageLoader getImageLoader() {
+ return mImageLoader;
+ }
+
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/MovieDetailsRequest.java b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/MovieDetailsRequest.java
new file mode 100644
index 0000000..f078c55
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/MovieDetailsRequest.java
@@ -0,0 +1,106 @@
+package android_challenge.com.moviesdatabase.WebRequests;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import com.android.volley.DefaultRetryPolicy;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.RetryPolicy;
+import com.android.volley.TimeoutError;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.Volley;
+import org.json.JSONObject;
+import android_challenge.com.moviesdatabase.Interface.ResultsMovie;
+import android_challenge.com.moviesdatabase.R;
+
+/**
+ * Created by Fabio_2 on 12/12/2018.
+ */
+
+public class MovieDetailsRequest {
+
+ private String _url = "http://www.omdbapi.com/?";
+ private String _api_key = "58b7ee65";
+
+ public MovieDetailsRequest(){
+
+ }
+
+ private ResultsMovie mResultsMovieCallBack = null;
+
+ public MovieDetailsRequest(ResultsMovie mResultsMovieCallBack1){
+ this.mResultsMovieCallBack = mResultsMovieCallBack1;
+ }
+
+ public void searchByID(final Context context, final String id) {
+
+ String url = "";
+ url = _url + "i=" + id + "&apikey=" + _api_key;
+
+ final ProgressDialog dialog;
+
+ dialog = new ProgressDialog(context);
+ dialog.setMessage("Por favor aguarde...");
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setCancelable(false);
+ dialog.show();
+
+
+ final JsonObjectRequest req = new JsonObjectRequest(Request.Method.GET, url, new JSONObject(),
+ new Response.Listener() {
+
+ @Override
+ public void onResponse(JSONObject response) {
+ dialog.dismiss();
+ try {
+
+ if (mResultsMovieCallBack != null)
+ mResultsMovieCallBack.notifySuccess(response);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+ }, new Response.ErrorListener() {
+
+
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ dialog.dismiss();
+
+ if (error.getClass().equals(TimeoutError.class)) {
+ dialog.dismiss();
+ new AlertDialog.Builder(context)
+ .setTitle("Problema na conexão!")
+ .setMessage("Internet instável! Favor tentar novamente!")
+ .setPositiveButton(R.string.msg_no_internet, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ searchByID(context, id);
+ }
+ })
+ .setNegativeButton(R.string.canceled, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (mResultsMovieCallBack != null) {
+ mResultsMovieCallBack.notifyError("return");
+ }
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
+ });
+ int socketTimeout = 10000;
+ RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, 1, 1);
+ req.setRetryPolicy(policy);
+ Volley.newRequestQueue(context).add(req);
+ }
+}
diff --git a/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/SearchMovies.java b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/SearchMovies.java
new file mode 100644
index 0000000..6e1f8b7
--- /dev/null
+++ b/app/src/main/java/android_challenge/com/moviesdatabase/WebRequests/SearchMovies.java
@@ -0,0 +1,116 @@
+package android_challenge.com.moviesdatabase.WebRequests;
+
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.support.v7.widget.RecyclerView;
+import android.widget.TextView;
+import com.android.volley.DefaultRetryPolicy;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.RetryPolicy;
+import com.android.volley.TimeoutError;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.Volley;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import java.util.List;
+import android_challenge.com.moviesdatabase.Pojo.Movies;
+import android_challenge.com.moviesdatabase.R;
+
+/**
+ * Created by Fabio_2 on 09/12/2018.
+ */
+
+public class SearchMovies {
+
+ private String _url = "http://www.omdbapi.com/?";
+ private String _api_key = "58b7ee65";
+
+ public SearchMovies() {
+
+ }
+
+ public void getMovies(final Context context, final String title, final int page, final List moviesList,
+ final RecyclerView.Adapter adapter, final TextView notfound) {
+
+ String url = "s=" + title + "&page=" + String.valueOf(page) + "&apikey=" + this._api_key;
+ this._url = this._url + url;
+
+
+ final ProgressDialog dialog;
+ if(page == 1){
+ dialog = new ProgressDialog(context);
+ dialog.setMessage("Buscando Filmes...");
+ } else {
+ dialog = new ProgressDialog(context, R.style.MyThemeDialog);
+ dialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
+ }
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setCancelable(false);
+ dialog.show();
+
+ final JsonObjectRequest req = new JsonObjectRequest(Request.Method.GET, this._url, new JSONObject(),
+ new Response.Listener() {
+
+ @Override
+ public void onResponse(JSONObject response) {
+ dialog.dismiss();
+ try {
+
+ if (!response.toString().contains("Search"))
+ notfound.setText("Nenhum filme encontrado!");
+ else {
+ JSONArray jsonArray = response.getJSONArray("Search");
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ String title = jsonObject.getString("Title");
+ int year = Integer.parseInt(jsonObject.getString("Year"));
+ String imdbID = jsonObject.getString("imdbID");
+ String type = jsonObject.getString("Type");
+ String poster = jsonObject.getString("Poster");
+ Movies movies = new Movies(title, year, imdbID, type, poster);
+ moviesList.add(movies);
+ adapter.notifyDataSetChanged();
+ }
+ }
+ }catch (Exception e){
+
+ }
+
+ }
+ }, new Response.ErrorListener() {
+
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ dialog.dismiss();
+ if (error.getClass().equals(TimeoutError.class)) {
+ dialog.dismiss();
+ new AlertDialog.Builder(context)
+ .setTitle("Problema na conexão!")
+ .setMessage("Internet instável! Favor tentar novamente!")
+ .setPositiveButton(R.string.msg_no_internet, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getMovies(context, title, page, moviesList, adapter, notfound);
+ }
+ })
+ .setNegativeButton(R.string.canceled, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
+ });
+ int socketTimeout = 10000;
+ RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, 1, 1);
+ req.setRetryPolicy(policy);
+ Volley.newRequestQueue(context).add(req);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/anim/alpha.xml b/app/src/main/res/anim/alpha.xml
new file mode 100644
index 0000000..8fbf28f
--- /dev/null
+++ b/app/src/main/res/anim/alpha.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/translate.xml b/app/src/main/res/anim/translate.xml
new file mode 100644
index 0000000..f6cf643
--- /dev/null
+++ b/app/src/main/res/anim/translate.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/card_movie.png b/app/src/main/res/drawable/card_movie.png
new file mode 100644
index 0000000..c10739c
Binary files /dev/null and b/app/src/main/res/drawable/card_movie.png differ
diff --git a/app/src/main/res/drawable/fundocinza.png b/app/src/main/res/drawable/fundocinza.png
new file mode 100644
index 0000000..7ba367a
Binary files /dev/null and b/app/src/main/res/drawable/fundocinza.png differ
diff --git a/app/src/main/res/drawable/logo_imdb.jpg b/app/src/main/res/drawable/logo_imdb.jpg
new file mode 100644
index 0000000..f401051
Binary files /dev/null and b/app/src/main/res/drawable/logo_imdb.jpg differ
diff --git a/app/src/main/res/drawable/movie_selected.xml b/app/src/main/res/drawable/movie_selected.xml
new file mode 100644
index 0000000..6a074f5
--- /dev/null
+++ b/app/src/main/res/drawable/movie_selected.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/search_menu.png b/app/src/main/res/drawable/search_menu.png
new file mode 100644
index 0000000..05e8436
Binary files /dev/null and b/app/src/main/res/drawable/search_menu.png differ
diff --git a/app/src/main/res/layout/actionbar_detail.xml b/app/src/main/res/layout/actionbar_detail.xml
new file mode 100644
index 0000000..32f4ca4
--- /dev/null
+++ b/app/src/main/res/layout/actionbar_detail.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..19ccd10
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_movie_details.xml b/app/src/main/res/layout/activity_movie_details.xml
new file mode 100644
index 0000000..c0763e5
--- /dev/null
+++ b/app/src/main/res/layout/activity_movie_details.xml
@@ -0,0 +1,436 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_movies_list.xml b/app/src/main/res/layout/activity_movies_list.xml
new file mode 100644
index 0000000..5707325
--- /dev/null
+++ b/app/src/main/res/layout/activity_movies_list.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/card_movie.xml b/app/src/main/res/layout/card_movie.xml
new file mode 100644
index 0000000..43a904e
--- /dev/null
+++ b/app/src/main/res/layout/card_movie.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/search_menu.xml b/app/src/main/res/menu/search_menu.xml
new file mode 100644
index 0000000..ce0d57b
--- /dev/null
+++ b/app/src/main/res/menu/search_menu.xml
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/imdb.png b/app/src/main/res/mipmap-hdpi/imdb.png
new file mode 100644
index 0000000..d360489
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/imdb.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/imdb.png b/app/src/main/res/mipmap-mdpi/imdb.png
new file mode 100644
index 0000000..d360489
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/imdb.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/imdb.png b/app/src/main/res/mipmap-xhdpi/imdb.png
new file mode 100644
index 0000000..d360489
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/imdb.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/imdb.png b/app/src/main/res/mipmap-xxhdpi/imdb.png
new file mode 100644
index 0000000..d360489
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/imdb.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/imdb.png b/app/src/main/res/mipmap-xxxhdpi/imdb.png
new file mode 100644
index 0000000..d360489
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/imdb.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b001dfd
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,8 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+ #F9E832
+ #212121
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..86af93b
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+ IMDb Movie Listing
+ Ok
+ Tentar Novamente?
+ Cancelar
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..166e3fd
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/android_challenge/com/moviesdatabase/ExampleUnitTest.java b/app/src/test/java/android_challenge/com/moviesdatabase/ExampleUnitTest.java
new file mode 100644
index 0000000..abdc913
--- /dev/null
+++ b/app/src/test/java/android_challenge/com/moviesdatabase/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package android_challenge.com.moviesdatabase;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..b78a0b8
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..851c774
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Dec 09 23:29:21 BRST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..046dc4d
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':volley'
diff --git a/volley/.gitignore b/volley/.gitignore
new file mode 100644
index 0000000..8889923
--- /dev/null
+++ b/volley/.gitignore
@@ -0,0 +1,9 @@
+bin
+gen
+.gradle
+build
+.settings
+target
+*.iml
+.idea
+local.properties
diff --git a/volley/Android.mk b/volley/Android.mk
new file mode 100644
index 0000000..dbe5194
--- /dev/null
+++ b/volley/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := volley
+LOCAL_SDK_VERSION := 17
+LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Include this library in the build server's output directory
+# TODO: Not yet.
+#$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):volley.jar)
+
+# Include build files in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/volley/bintray.gradle b/volley/bintray.gradle
new file mode 100644
index 0000000..f07693e
--- /dev/null
+++ b/volley/bintray.gradle
@@ -0,0 +1,87 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2"
+ }
+}
+
+// apply the plugin with its class name rather than its Id to work around gradle limitation of
+// not being able to find the plugin by Id despite the dependencies being added right above. Gradle
+// is currently not capable of loading plugins by Id if the dependency is anywhere else than
+// in the main project build.gradle. This file is "imported" into the project's build.gradle
+// through a "apply from:".
+apply plugin: com.jfrog.bintray.gradle.BintrayPlugin
+apply plugin: 'maven-publish'
+
+project.ext.group = 'com.android.volley'
+project.ext.archivesBaseName = 'volley'
+project.ext.version = '1.0.0'
+project.ext.pomDesc = 'Volley Android library'
+
+task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+}
+
+task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+
+artifacts {
+ archives javadocJar
+ archives sourcesJar
+}
+
+publishing {
+ publications {
+ library(MavenPublication) {
+ groupId project.ext.group
+ artifactId project.ext.archivesBaseName
+ version project.ext.version
+
+ // Release AAR, Sources, and JavaDoc
+ artifact "$buildDir/outputs/aar/volley-release.aar"
+ artifact sourcesJar
+ artifact javadocJar
+ }
+ }
+}
+
+bintray {
+ user = System.env.BINTRAY_USER
+ key = System.env.BINTRAY_USER_KEY
+
+ publications = [ 'library' ]
+
+ publish = project.hasProperty("release")
+ pkg {
+ userOrg = 'android'
+ repo = 'android-utils'
+ group = project.ext.group
+ name = project.ext.group + '.' + project.ext.archivesBaseName
+ desc = project.ext.pomDesc
+ licenses = [ 'Apache-2.0' ]
+ websiteUrl = 'https://tools.android.com'
+ issueTrackerUrl = 'https://code.google.com/p/android/'
+ vcsUrl = 'https://android.googlesource.com/platform/frameworks/volley.git'
+ labels = ['android', 'volley', 'network']
+ publicDownloadNumbers = true
+
+ version {
+ name = project.ext.version
+ desc = project.ext.pomDesc + ' version ' + project.ext.version
+ gpg {
+ sign = true
+ passphrase = System.env.GPG_PASSPHRASE
+ }
+ }
+ }
+}
diff --git a/volley/build.gradle b/volley/build.gradle
new file mode 100644
index 0000000..966b588
--- /dev/null
+++ b/volley/build.gradle
@@ -0,0 +1,38 @@
+// NOTE: The only changes that belong in this file are the definitions
+// of tool versions (gradle plugin, compile SDK, build tools), so that
+// Volley can be built via gradle as a standalone project.
+//
+// Any other changes to the build config belong in rules.gradle, which
+// is used by projects that depend on Volley but define their own
+// tools versions across all dependencies to ensure a consistent build.
+//
+// Most users should just add this line to settings.gradle:
+// include(":volley")
+//
+// If you have a more complicated Gradle setup you can choose to use
+// this instead:
+// include(":volley")
+// project(':volley').buildFileName = 'rules.gradle'
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.1'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+repositories {
+ jcenter()
+}
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion = '25.0.0'
+}
+
+apply from: 'rules.gradle'
+apply from: 'bintray.gradle'
diff --git a/volley/build.xml b/volley/build.xml
new file mode 100644
index 0000000..219c63c
--- /dev/null
+++ b/volley/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/volley/custom_rules.xml b/volley/custom_rules.xml
new file mode 100644
index 0000000..1b94e5d
--- /dev/null
+++ b/volley/custom_rules.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/volley/pom.xml b/volley/pom.xml
new file mode 100644
index 0000000..7c37e0f
--- /dev/null
+++ b/volley/pom.xml
@@ -0,0 +1,168 @@
+
+ 4.0.0
+
+ com.android.volley
+ volley
+ 1.0-SNAPSHOT
+ jar
+
+ volley
+ http://android.com
+
+
+ UTF-8
+
+ 1.6
+
+
+
+
+ com.google.android
+ android
+ 4.1.1.4
+
+
+ junit
+ junit
+ 4.10
+ test
+
+
+ org.robolectric
+ robolectric
+ 3.0
+ test
+
+
+ org.mockito
+ mockito-core
+ 1.9.5
+ test
+
+
+
+
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ 3.8.1
+
+
+ 19
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.0
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
+
+
+ debug
+
+ true
+
+ performDebugBuild
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.18.1
+
+
+ default-test
+
+ ${surefireArgLine}
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+ 0.7.2.201409121644
+
+
+ pre-unit-test
+
+ prepare-agent
+
+
+ ${project.build.directory}/surefire-reports/jacoco-ut.exec
+ surefireArgLine
+
+
+
+ jacoco-report
+ post-integration-test
+
+ report
+ check
+
+
+ ${project.build.directory}/surefire-reports/jacoco-ut.exec
+ ${project.build.directory}/jacoco-report
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/volley/proguard-project.txt b/volley/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/volley/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/volley/proguard.cfg b/volley/proguard.cfg
new file mode 100644
index 0000000..b1cdf17
--- /dev/null
+++ b/volley/proguard.cfg
@@ -0,0 +1,40 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+ native ;
+}
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
diff --git a/volley/rules.gradle b/volley/rules.gradle
new file mode 100644
index 0000000..04dd681
--- /dev/null
+++ b/volley/rules.gradle
@@ -0,0 +1,12 @@
+// See build.gradle for an explanation of what this file is.
+
+apply plugin: 'com.android.library'
+
+// Check if the android plugin version supports unit testing.
+if (configurations.findByName("testCompile")) {
+ dependencies {
+ testCompile "junit:junit:4.10"
+ testCompile "org.mockito:mockito-core:1.9.5"
+ testCompile "org.robolectric:robolectric:3.0"
+ }
+}
diff --git a/volley/src/main/AndroidManifest.xml b/volley/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..16eec15
--- /dev/null
+++ b/volley/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/volley/src/main/java/com/android/volley/AuthFailureError.java b/volley/src/main/java/com/android/volley/AuthFailureError.java
new file mode 100644
index 0000000..87c811d
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/AuthFailureError.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.content.Intent;
+
+/**
+ * Error indicating that there was an authentication failure when performing a Request.
+ */
+@SuppressWarnings("serial")
+public class AuthFailureError extends VolleyError {
+ /** An intent that can be used to resolve this exception. (Brings up the password dialog.) */
+ private Intent mResolutionIntent;
+
+ public AuthFailureError() { }
+
+ public AuthFailureError(Intent intent) {
+ mResolutionIntent = intent;
+ }
+
+ public AuthFailureError(NetworkResponse response) {
+ super(response);
+ }
+
+ public AuthFailureError(String message) {
+ super(message);
+ }
+
+ public AuthFailureError(String message, Exception reason) {
+ super(message, reason);
+ }
+
+ public Intent getResolutionIntent() {
+ return mResolutionIntent;
+ }
+
+ @Override
+ public String getMessage() {
+ if (mResolutionIntent != null) {
+ return "User needs to (re)enter credentials.";
+ }
+ return super.getMessage();
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/Cache.java b/volley/src/main/java/com/android/volley/Cache.java
new file mode 100644
index 0000000..f1ec757
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/Cache.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * An interface for a cache keyed by a String with a byte array as data.
+ */
+public interface Cache {
+ /**
+ * Retrieves an entry from the cache.
+ * @param key Cache key
+ * @return An {@link Entry} or null in the event of a cache miss
+ */
+ public Entry get(String key);
+
+ /**
+ * Adds or replaces an entry to the cache.
+ * @param key Cache key
+ * @param entry Data to store and metadata for cache coherency, TTL, etc.
+ */
+ public void put(String key, Entry entry);
+
+ /**
+ * Performs any potentially long-running actions needed to initialize the cache;
+ * will be called from a worker thread.
+ */
+ public void initialize();
+
+ /**
+ * Invalidates an entry in the cache.
+ * @param key Cache key
+ * @param fullExpire True to fully expire the entry, false to soft expire
+ */
+ public void invalidate(String key, boolean fullExpire);
+
+ /**
+ * Removes an entry from the cache.
+ * @param key Cache key
+ */
+ public void remove(String key);
+
+ /**
+ * Empties the cache.
+ */
+ public void clear();
+
+ /**
+ * Data and metadata for an entry returned by the cache.
+ */
+ public static class Entry {
+ /** The data returned from cache. */
+ public byte[] data;
+
+ /** ETag for cache coherency. */
+ public String etag;
+
+ /** Date of this response as reported by the server. */
+ public long serverDate;
+
+ /** The last modified date for the requested object. */
+ public long lastModified;
+
+ /** TTL for this record. */
+ public long ttl;
+
+ /** Soft TTL for this record. */
+ public long softTtl;
+
+ /** Immutable response headers as received from server; must be non-null. */
+ public Map responseHeaders = Collections.emptyMap();
+
+ /** True if the entry is expired. */
+ public boolean isExpired() {
+ return this.ttl < System.currentTimeMillis();
+ }
+
+ /** True if a refresh is needed from the original data source. */
+ public boolean refreshNeeded() {
+ return this.softTtl < System.currentTimeMillis();
+ }
+ }
+
+}
diff --git a/volley/src/main/java/com/android/volley/CacheDispatcher.java b/volley/src/main/java/com/android/volley/CacheDispatcher.java
new file mode 100644
index 0000000..18d219b
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/CacheDispatcher.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.os.Process;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Provides a thread for performing cache triage on a queue of requests.
+ *
+ * Requests added to the specified cache queue are resolved from cache.
+ * Any deliverable response is posted back to the caller via a
+ * {@link ResponseDelivery}. Cache misses and responses that require
+ * refresh are enqueued on the specified network queue for processing
+ * by a {@link NetworkDispatcher}.
+ */
+public class CacheDispatcher extends Thread {
+
+ private static final boolean DEBUG = VolleyLog.DEBUG;
+
+ /** The queue of requests coming in for triage. */
+ private final BlockingQueue> mCacheQueue;
+
+ /** The queue of requests going out to the network. */
+ private final BlockingQueue> mNetworkQueue;
+
+ /** The cache to read from. */
+ private final Cache mCache;
+
+ /** For posting responses. */
+ private final ResponseDelivery mDelivery;
+
+ /** Used for telling us to die. */
+ private volatile boolean mQuit = false;
+
+ /**
+ * Creates a new cache triage dispatcher thread. You must call {@link #start()}
+ * in order to begin processing.
+ *
+ * @param cacheQueue Queue of incoming requests for triage
+ * @param networkQueue Queue to post requests that require network to
+ * @param cache Cache interface to use for resolution
+ * @param delivery Delivery interface to use for posting responses
+ */
+ public CacheDispatcher(
+ BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
+ Cache cache, ResponseDelivery delivery) {
+ mCacheQueue = cacheQueue;
+ mNetworkQueue = networkQueue;
+ mCache = cache;
+ mDelivery = delivery;
+ }
+
+ /**
+ * Forces this dispatcher to quit immediately. If any requests are still in
+ * the queue, they are not guaranteed to be processed.
+ */
+ public void quit() {
+ mQuit = true;
+ interrupt();
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) VolleyLog.v("start new dispatcher");
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ // Make a blocking call to initialize the cache.
+ mCache.initialize();
+
+ while (true) {
+ try {
+ // Get a request from the cache triage queue, blocking until
+ // at least one is available.
+ final Request> request = mCacheQueue.take();
+ request.addMarker("cache-queue-take");
+
+ // If the request has been canceled, don't bother dispatching it.
+ if (request.isCanceled()) {
+ request.finish("cache-discard-canceled");
+ continue;
+ }
+
+ // Attempt to retrieve this item from cache.
+ Cache.Entry entry = mCache.get(request.getCacheKey());
+ if (entry == null) {
+ request.addMarker("cache-miss");
+ // Cache miss; send off to the network dispatcher.
+ mNetworkQueue.put(request);
+ continue;
+ }
+
+ // If it is completely expired, just send it to the network.
+ if (entry.isExpired()) {
+ request.addMarker("cache-hit-expired");
+ request.setCacheEntry(entry);
+ mNetworkQueue.put(request);
+ continue;
+ }
+
+ // We have a cache hit; parse its data for delivery back to the request.
+ request.addMarker("cache-hit");
+ Response> response = request.parseNetworkResponse(
+ new NetworkResponse(entry.data, entry.responseHeaders));
+ request.addMarker("cache-hit-parsed");
+
+ if (!entry.refreshNeeded()) {
+ // Completely unexpired cache hit. Just deliver the response.
+ mDelivery.postResponse(request, response);
+ } else {
+ // Soft-expired cache hit. We can deliver the cached response,
+ // but we need to also send the request to the network for
+ // refreshing.
+ request.addMarker("cache-hit-refresh-needed");
+ request.setCacheEntry(entry);
+
+ // Mark the response as intermediate.
+ response.intermediate = true;
+
+ // Post the intermediate response back to the user and have
+ // the delivery then forward the request along to the network.
+ mDelivery.postResponse(request, response, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mNetworkQueue.put(request);
+ } catch (InterruptedException e) {
+ // Not much we can do about this.
+ }
+ }
+ });
+ }
+
+ } catch (InterruptedException e) {
+ // We may have been interrupted because it was time to quit.
+ if (mQuit) {
+ return;
+ }
+ continue;
+ }
+ }
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/ClientError.java b/volley/src/main/java/com/android/volley/ClientError.java
new file mode 100644
index 0000000..a8c8141
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/ClientError.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Indicates that the server responded with an error response indicating that the client has erred.
+ *
+ * For backwards compatibility, extends ServerError which used to be thrown for all server errors,
+ * including 4xx error codes indicating a client error.
+ */
+@SuppressWarnings("serial")
+public class ClientError extends ServerError {
+ public ClientError(NetworkResponse networkResponse) {
+ super(networkResponse);
+ }
+
+ public ClientError() {
+ super();
+ }
+}
+
diff --git a/volley/src/main/java/com/android/volley/DefaultRetryPolicy.java b/volley/src/main/java/com/android/volley/DefaultRetryPolicy.java
new file mode 100644
index 0000000..d8abab0
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/DefaultRetryPolicy.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Default retry policy for requests.
+ */
+public class DefaultRetryPolicy implements RetryPolicy {
+ /** The current timeout in milliseconds. */
+ private int mCurrentTimeoutMs;
+
+ /** The current retry count. */
+ private int mCurrentRetryCount;
+
+ /** The maximum number of attempts. */
+ private final int mMaxNumRetries;
+
+ /** The backoff multiplier for the policy. */
+ private final float mBackoffMultiplier;
+
+ /** The default socket timeout in milliseconds */
+ public static final int DEFAULT_TIMEOUT_MS = 2500;
+
+ /** The default number of retries */
+ public static final int DEFAULT_MAX_RETRIES = 1;
+
+ /** The default backoff multiplier */
+ public static final float DEFAULT_BACKOFF_MULT = 1f;
+
+ /**
+ * Constructs a new retry policy using the default timeouts.
+ */
+ public DefaultRetryPolicy() {
+ this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
+ }
+
+ /**
+ * Constructs a new retry policy.
+ * @param initialTimeoutMs The initial timeout for the policy.
+ * @param maxNumRetries The maximum number of retries.
+ * @param backoffMultiplier Backoff multiplier for the policy.
+ */
+ public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
+ mCurrentTimeoutMs = initialTimeoutMs;
+ mMaxNumRetries = maxNumRetries;
+ mBackoffMultiplier = backoffMultiplier;
+ }
+
+ /**
+ * Returns the current timeout.
+ */
+ @Override
+ public int getCurrentTimeout() {
+ return mCurrentTimeoutMs;
+ }
+
+ /**
+ * Returns the current retry count.
+ */
+ @Override
+ public int getCurrentRetryCount() {
+ return mCurrentRetryCount;
+ }
+
+ /**
+ * Returns the backoff multiplier for the policy.
+ */
+ public float getBackoffMultiplier() {
+ return mBackoffMultiplier;
+ }
+
+ /**
+ * Prepares for the next retry by applying a backoff to the timeout.
+ * @param error The error code of the last attempt.
+ */
+ @Override
+ public void retry(VolleyError error) throws VolleyError {
+ mCurrentRetryCount++;
+ mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
+ if (!hasAttemptRemaining()) {
+ throw error;
+ }
+ }
+
+ /**
+ * Returns true if this policy has attempts remaining, false otherwise.
+ */
+ protected boolean hasAttemptRemaining() {
+ return mCurrentRetryCount <= mMaxNumRetries;
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/ExecutorDelivery.java b/volley/src/main/java/com/android/volley/ExecutorDelivery.java
new file mode 100644
index 0000000..1babfcd
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/ExecutorDelivery.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.os.Handler;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Delivers responses and errors.
+ */
+public class ExecutorDelivery implements ResponseDelivery {
+ /** Used for posting responses, typically to the main thread. */
+ private final Executor mResponsePoster;
+
+ /**
+ * Creates a new response delivery interface.
+ * @param handler {@link Handler} to post responses on
+ */
+ public ExecutorDelivery(final Handler handler) {
+ // Make an Executor that just wraps the handler.
+ mResponsePoster = new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ handler.post(command);
+ }
+ };
+ }
+
+ /**
+ * Creates a new response delivery interface, mockable version
+ * for testing.
+ * @param executor For running delivery tasks
+ */
+ public ExecutorDelivery(Executor executor) {
+ mResponsePoster = executor;
+ }
+
+ @Override
+ public void postResponse(Request> request, Response> response) {
+ postResponse(request, response, null);
+ }
+
+ @Override
+ public void postResponse(Request> request, Response> response, Runnable runnable) {
+ request.markDelivered();
+ request.addMarker("post-response");
+ mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
+ }
+
+ @Override
+ public void postError(Request> request, VolleyError error) {
+ request.addMarker("post-error");
+ Response> response = Response.error(error);
+ mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
+ }
+
+ /**
+ * A Runnable used for delivering network responses to a listener on the
+ * main thread.
+ */
+ @SuppressWarnings("rawtypes")
+ private class ResponseDeliveryRunnable implements Runnable {
+ private final Request mRequest;
+ private final Response mResponse;
+ private final Runnable mRunnable;
+
+ public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
+ mRequest = request;
+ mResponse = response;
+ mRunnable = runnable;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void run() {
+ // If this request has canceled, finish it and don't deliver.
+ if (mRequest.isCanceled()) {
+ mRequest.finish("canceled-at-delivery");
+ return;
+ }
+
+ // Deliver a normal response or error, depending.
+ if (mResponse.isSuccess()) {
+ mRequest.deliverResponse(mResponse.result);
+ } else {
+ mRequest.deliverError(mResponse.error);
+ }
+
+ // If this is an intermediate response, add a marker, otherwise we're done
+ // and the request can be finished.
+ if (mResponse.intermediate) {
+ mRequest.addMarker("intermediate-response");
+ } else {
+ mRequest.finish("done");
+ }
+
+ // If we have been provided a post-delivery runnable, run it.
+ if (mRunnable != null) {
+ mRunnable.run();
+ }
+ }
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/Network.java b/volley/src/main/java/com/android/volley/Network.java
new file mode 100644
index 0000000..ab45830
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/Network.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * An interface for performing requests.
+ */
+public interface Network {
+ /**
+ * Performs the specified request.
+ * @param request Request to process
+ * @return A {@link NetworkResponse} with data and caching metadata; will never be null
+ * @throws VolleyError on errors
+ */
+ public NetworkResponse performRequest(Request> request) throws VolleyError;
+}
diff --git a/volley/src/main/java/com/android/volley/NetworkDispatcher.java b/volley/src/main/java/com/android/volley/NetworkDispatcher.java
new file mode 100644
index 0000000..beb7861
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/NetworkDispatcher.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.annotation.TargetApi;
+import android.net.TrafficStats;
+import android.os.Build;
+import android.os.Process;
+import android.os.SystemClock;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Provides a thread for performing network dispatch from a queue of requests.
+ *
+ * Requests added to the specified queue are processed from the network via a
+ * specified {@link Network} interface. Responses are committed to cache, if
+ * eligible, using a specified {@link Cache} interface. Valid responses and
+ * errors are posted back to the caller via a {@link ResponseDelivery}.
+ */
+public class NetworkDispatcher extends Thread {
+ /** The queue of requests to service. */
+ private final BlockingQueue> mQueue;
+ /** The network interface for processing requests. */
+ private final Network mNetwork;
+ /** The cache to write to. */
+ private final Cache mCache;
+ /** For posting responses and errors. */
+ private final ResponseDelivery mDelivery;
+ /** Used for telling us to die. */
+ private volatile boolean mQuit = false;
+
+ /**
+ * Creates a new network dispatcher thread. You must call {@link #start()}
+ * in order to begin processing.
+ *
+ * @param queue Queue of incoming requests for triage
+ * @param network Network interface to use for performing requests
+ * @param cache Cache interface to use for writing responses to cache
+ * @param delivery Delivery interface to use for posting responses
+ */
+ public NetworkDispatcher(BlockingQueue> queue,
+ Network network, Cache cache,
+ ResponseDelivery delivery) {
+ mQueue = queue;
+ mNetwork = network;
+ mCache = cache;
+ mDelivery = delivery;
+ }
+
+ /**
+ * Forces this dispatcher to quit immediately. If any requests are still in
+ * the queue, they are not guaranteed to be processed.
+ */
+ public void quit() {
+ mQuit = true;
+ interrupt();
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void addTrafficStatsTag(Request> request) {
+ // Tag the request (if API >= 14)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
+ }
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ while (true) {
+ long startTimeMs = SystemClock.elapsedRealtime();
+ Request> request;
+ try {
+ // Take a request from the queue.
+ request = mQueue.take();
+ } catch (InterruptedException e) {
+ // We may have been interrupted because it was time to quit.
+ if (mQuit) {
+ return;
+ }
+ continue;
+ }
+
+ try {
+ request.addMarker("network-queue-take");
+
+ // If the request was cancelled already, do not perform the
+ // network request.
+ if (request.isCanceled()) {
+ request.finish("network-discard-cancelled");
+ continue;
+ }
+
+ addTrafficStatsTag(request);
+
+ // Perform the network request.
+ NetworkResponse networkResponse = mNetwork.performRequest(request);
+ request.addMarker("network-http-complete");
+
+ // If the server returned 304 AND we delivered a response already,
+ // we're done -- don't deliver a second identical response.
+ if (networkResponse.notModified && request.hasHadResponseDelivered()) {
+ request.finish("not-modified");
+ continue;
+ }
+
+ // Parse the response here on the worker thread.
+ Response> response = request.parseNetworkResponse(networkResponse);
+ request.addMarker("network-parse-complete");
+
+ // Write to cache if applicable.
+ // TODO: Only update cache metadata instead of entire record for 304s.
+ if (request.shouldCache() && response.cacheEntry != null) {
+ mCache.put(request.getCacheKey(), response.cacheEntry);
+ request.addMarker("network-cache-written");
+ }
+
+ // Post the response back.
+ request.markDelivered();
+ mDelivery.postResponse(request, response);
+ } catch (VolleyError volleyError) {
+ volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
+ parseAndDeliverNetworkError(request, volleyError);
+ } catch (Exception e) {
+ VolleyLog.e(e, "Unhandled exception %s", e.toString());
+ VolleyError volleyError = new VolleyError(e);
+ volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
+ mDelivery.postError(request, volleyError);
+ }
+ }
+ }
+
+ private void parseAndDeliverNetworkError(Request> request, VolleyError error) {
+ error = request.parseNetworkError(error);
+ mDelivery.postError(request, error);
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/NetworkError.java b/volley/src/main/java/com/android/volley/NetworkError.java
new file mode 100644
index 0000000..40b41c5
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/NetworkError.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Indicates that there was a network error when performing a Volley request.
+ */
+@SuppressWarnings("serial")
+public class NetworkError extends VolleyError {
+ public NetworkError() {
+ super();
+ }
+
+ public NetworkError(Throwable cause) {
+ super(cause);
+ }
+
+ public NetworkError(NetworkResponse networkResponse) {
+ super(networkResponse);
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/NetworkResponse.java b/volley/src/main/java/com/android/volley/NetworkResponse.java
new file mode 100644
index 0000000..a787fa7
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/NetworkResponse.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import org.apache.http.HttpStatus;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Data and headers returned from {@link Network#performRequest(Request)}.
+ */
+public class NetworkResponse {
+ /**
+ * Creates a new network response.
+ * @param statusCode the HTTP status code
+ * @param data Response body
+ * @param headers Headers returned with this response, or null for none
+ * @param notModified True if the server returned a 304 and the data was already in cache
+ * @param networkTimeMs Round-trip network time to receive network response
+ */
+ public NetworkResponse(int statusCode, byte[] data, Map headers,
+ boolean notModified, long networkTimeMs) {
+ this.statusCode = statusCode;
+ this.data = data;
+ this.headers = headers;
+ this.notModified = notModified;
+ this.networkTimeMs = networkTimeMs;
+ }
+
+ public NetworkResponse(int statusCode, byte[] data, Map headers,
+ boolean notModified) {
+ this(statusCode, data, headers, notModified, 0);
+ }
+
+ public NetworkResponse(byte[] data) {
+ this(HttpStatus.SC_OK, data, Collections.emptyMap(), false, 0);
+ }
+
+ public NetworkResponse(byte[] data, Map headers) {
+ this(HttpStatus.SC_OK, data, headers, false, 0);
+ }
+
+ /** The HTTP status code. */
+ public final int statusCode;
+
+ /** Raw data from this response. */
+ public final byte[] data;
+
+ /** Response headers. */
+ public final Map headers;
+
+ /** True if the server returned a 304 (Not Modified). */
+ public final boolean notModified;
+
+ /** Network roundtrip time in milliseconds. */
+ public final long networkTimeMs;
+}
+
diff --git a/volley/src/main/java/com/android/volley/NoConnectionError.java b/volley/src/main/java/com/android/volley/NoConnectionError.java
new file mode 100644
index 0000000..fc23156
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/NoConnectionError.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Error indicating that no connection could be established when performing a Volley request.
+ */
+@SuppressWarnings("serial")
+public class NoConnectionError extends NetworkError {
+ public NoConnectionError() {
+ super();
+ }
+
+ public NoConnectionError(Throwable reason) {
+ super(reason);
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/ParseError.java b/volley/src/main/java/com/android/volley/ParseError.java
new file mode 100644
index 0000000..959d8fb
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/ParseError.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Indicates that the server's response could not be parsed.
+ */
+@SuppressWarnings("serial")
+public class ParseError extends VolleyError {
+ public ParseError() { }
+
+ public ParseError(NetworkResponse networkResponse) {
+ super(networkResponse);
+ }
+
+ public ParseError(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/Request.java b/volley/src/main/java/com/android/volley/Request.java
new file mode 100644
index 0000000..8200f6e
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/Request.java
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+
+import com.android.volley.VolleyLog.MarkerLog;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Base class for all network requests.
+ *
+ * @param The type of parsed response this request expects.
+ */
+public abstract class Request implements Comparable> {
+
+ /**
+ * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
+ */
+ private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
+
+ /**
+ * Supported request methods.
+ */
+ public interface Method {
+ int DEPRECATED_GET_OR_POST = -1;
+ int GET = 0;
+ int POST = 1;
+ int PUT = 2;
+ int DELETE = 3;
+ int HEAD = 4;
+ int OPTIONS = 5;
+ int TRACE = 6;
+ int PATCH = 7;
+ }
+
+ /** An event log tracing the lifetime of this request; for debugging. */
+ private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
+
+ /**
+ * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
+ * TRACE, and PATCH.
+ */
+ private final int mMethod;
+
+ /** URL of this request. */
+ private final String mUrl;
+
+ /** Default tag for {@link TrafficStats}. */
+ private final int mDefaultTrafficStatsTag;
+
+ /** Listener interface for errors. */
+ private final Response.ErrorListener mErrorListener;
+
+ /** Sequence number of this request, used to enforce FIFO ordering. */
+ private Integer mSequence;
+
+ /** The request queue this request is associated with. */
+ private RequestQueue mRequestQueue;
+
+ /** Whether or not responses to this request should be cached. */
+ private boolean mShouldCache = true;
+
+ /** Whether or not this request has been canceled. */
+ private boolean mCanceled = false;
+
+ /** Whether or not a response has been delivered for this request yet. */
+ private boolean mResponseDelivered = false;
+
+ /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
+ private boolean mShouldRetryServerErrors = false;
+
+ /** The retry policy for this request. */
+ private RetryPolicy mRetryPolicy;
+
+ /**
+ * When a request can be retrieved from cache but must be refreshed from
+ * the network, the cache entry will be stored here so that in the event of
+ * a "Not Modified" response, we can be sure it hasn't been evicted from cache.
+ */
+ private Cache.Entry mCacheEntry = null;
+
+ /** An opaque token tagging this request; used for bulk cancellation. */
+ private Object mTag;
+
+ /**
+ * Creates a new request with the given URL and error listener. Note that
+ * the normal response listener is not provided here as delivery of responses
+ * is provided by subclasses, who have a better idea of how to deliver an
+ * already-parsed response.
+ *
+ * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
+ */
+ @Deprecated
+ public Request(String url, Response.ErrorListener listener) {
+ this(Method.DEPRECATED_GET_OR_POST, url, listener);
+ }
+
+ /**
+ * Creates a new request with the given method (one of the values from {@link Method}),
+ * URL, and error listener. Note that the normal response listener is not provided here as
+ * delivery of responses is provided by subclasses, who have a better idea of how to deliver
+ * an already-parsed response.
+ */
+ public Request(int method, String url, Response.ErrorListener listener) {
+ mMethod = method;
+ mUrl = url;
+ mErrorListener = listener;
+ setRetryPolicy(new DefaultRetryPolicy());
+
+ mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
+ }
+
+ /**
+ * Return the method for this request. Can be one of the values in {@link Method}.
+ */
+ public int getMethod() {
+ return mMethod;
+ }
+
+ /**
+ * Set a tag on this request. Can be used to cancel all requests with this
+ * tag by {@link RequestQueue#cancelAll(Object)}.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public Request> setTag(Object tag) {
+ mTag = tag;
+ return this;
+ }
+
+ /**
+ * Returns this request's tag.
+ * @see Request#setTag(Object)
+ */
+ public Object getTag() {
+ return mTag;
+ }
+
+ /**
+ * @return this request's {@link com.android.volley.Response.ErrorListener}.
+ */
+ public Response.ErrorListener getErrorListener() {
+ return mErrorListener;
+ }
+
+ /**
+ * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)}
+ */
+ public int getTrafficStatsTag() {
+ return mDefaultTrafficStatsTag;
+ }
+
+ /**
+ * @return The hashcode of the URL's host component, or 0 if there is none.
+ */
+ private static int findDefaultTrafficStatsTag(String url) {
+ if (!TextUtils.isEmpty(url)) {
+ Uri uri = Uri.parse(url);
+ if (uri != null) {
+ String host = uri.getHost();
+ if (host != null) {
+ return host.hashCode();
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the retry policy for this request.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public Request> setRetryPolicy(RetryPolicy retryPolicy) {
+ mRetryPolicy = retryPolicy;
+ return this;
+ }
+
+ /**
+ * Adds an event to this request's event log; for debugging.
+ */
+ public void addMarker(String tag) {
+ if (MarkerLog.ENABLED) {
+ mEventLog.add(tag, Thread.currentThread().getId());
+ }
+ }
+
+ /**
+ * Notifies the request queue that this request has finished (successfully or with error).
+ *
+ *
Also dumps all events from this request's event log; for debugging.
+ */
+ void finish(final String tag) {
+ if (mRequestQueue != null) {
+ mRequestQueue.finish(this);
+ }
+ if (MarkerLog.ENABLED) {
+ final long threadId = Thread.currentThread().getId();
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ // If we finish marking off of the main thread, we need to
+ // actually do it on the main thread to ensure correct ordering.
+ Handler mainThread = new Handler(Looper.getMainLooper());
+ mainThread.post(new Runnable() {
+ @Override
+ public void run() {
+ mEventLog.add(tag, threadId);
+ mEventLog.finish(this.toString());
+ }
+ });
+ return;
+ }
+
+ mEventLog.add(tag, threadId);
+ mEventLog.finish(this.toString());
+ }
+ }
+
+ /**
+ * Associates this request with the given queue. The request queue will be notified when this
+ * request has finished.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public Request> setRequestQueue(RequestQueue requestQueue) {
+ mRequestQueue = requestQueue;
+ return this;
+ }
+
+ /**
+ * Sets the sequence number of this request. Used by {@link RequestQueue}.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public final Request> setSequence(int sequence) {
+ mSequence = sequence;
+ return this;
+ }
+
+ /**
+ * Returns the sequence number of this request.
+ */
+ public final int getSequence() {
+ if (mSequence == null) {
+ throw new IllegalStateException("getSequence called before setSequence");
+ }
+ return mSequence;
+ }
+
+ /**
+ * Returns the URL of this request.
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Returns the cache key for this request. By default, this is the URL.
+ */
+ public String getCacheKey() {
+ return getUrl();
+ }
+
+ /**
+ * Annotates this request with an entry retrieved for it from cache.
+ * Used for cache coherency support.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public Request> setCacheEntry(Cache.Entry entry) {
+ mCacheEntry = entry;
+ return this;
+ }
+
+ /**
+ * Returns the annotated cache entry, or null if there isn't one.
+ */
+ public Cache.Entry getCacheEntry() {
+ return mCacheEntry;
+ }
+
+ /**
+ * Mark this request as canceled. No callback will be delivered.
+ */
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ /**
+ * Returns true if this request has been canceled.
+ */
+ public boolean isCanceled() {
+ return mCanceled;
+ }
+
+ /**
+ * Returns a list of extra HTTP headers to go along with this request. Can
+ * throw {@link AuthFailureError} as authentication may be required to
+ * provide these values.
+ * @throws AuthFailureError In the event of auth failure
+ */
+ public Map getHeaders() throws AuthFailureError {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Returns a Map of POST parameters to be used for this request, or null if
+ * a simple GET should be used. Can throw {@link AuthFailureError} as
+ * authentication may be required to provide these values.
+ *
+ *
Note that only one of getPostParams() and getPostBody() can return a non-null
+ * value.
+ * @throws AuthFailureError In the event of auth failure
+ *
+ * @deprecated Use {@link #getParams()} instead.
+ */
+ @Deprecated
+ protected Map getPostParams() throws AuthFailureError {
+ return getParams();
+ }
+
+ /**
+ * Returns which encoding should be used when converting POST parameters returned by
+ * {@link #getPostParams()} into a raw POST body.
+ *
+ *
This controls both encodings:
+ *
+ *
The string encoding used when converting parameter names and values into bytes prior
+ * to URL encoding them.
+ *
The string encoding used when converting the URL encoded parameters into a raw
+ * byte array.
+ *
+ *
+ * @deprecated Use {@link #getParamsEncoding()} instead.
+ */
+ @Deprecated
+ protected String getPostParamsEncoding() {
+ return getParamsEncoding();
+ }
+
+ /**
+ * @deprecated Use {@link #getBodyContentType()} instead.
+ */
+ @Deprecated
+ public String getPostBodyContentType() {
+ return getBodyContentType();
+ }
+
+ /**
+ * Returns the raw POST body to be sent.
+ *
+ * @throws AuthFailureError In the event of auth failure
+ *
+ * @deprecated Use {@link #getBody()} instead.
+ */
+ @Deprecated
+ public byte[] getPostBody() throws AuthFailureError {
+ // Note: For compatibility with legacy clients of volley, this implementation must remain
+ // here instead of simply calling the getBody() function because this function must
+ // call getPostParams() and getPostParamsEncoding() since legacy clients would have
+ // overridden these two member functions for POST requests.
+ Map postParams = getPostParams();
+ if (postParams != null && postParams.size() > 0) {
+ return encodeParameters(postParams, getPostParamsEncoding());
+ }
+ return null;
+ }
+
+ /**
+ * Returns a Map of parameters to be used for a POST or PUT request. Can throw
+ * {@link AuthFailureError} as authentication may be required to provide these values.
+ *
+ *
Note that you can directly override {@link #getBody()} for custom data.
+ *
+ * @throws AuthFailureError in the event of auth failure
+ */
+ protected Map getParams() throws AuthFailureError {
+ return null;
+ }
+
+ /**
+ * Returns which encoding should be used when converting POST or PUT parameters returned by
+ * {@link #getParams()} into a raw POST or PUT body.
+ *
+ *
This controls both encodings:
+ *
+ *
The string encoding used when converting parameter names and values into bytes prior
+ * to URL encoding them.
+ *
The string encoding used when converting the URL encoded parameters into a raw
+ * byte array.
+ *
+ */
+ protected String getParamsEncoding() {
+ return DEFAULT_PARAMS_ENCODING;
+ }
+
+ /**
+ * Returns the content type of the POST or PUT body.
+ */
+ public String getBodyContentType() {
+ return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
+ }
+
+ /**
+ * Returns the raw POST or PUT body to be sent.
+ *
+ *
By default, the body consists of the request parameters in
+ * application/x-www-form-urlencoded format. When overriding this method, consider overriding
+ * {@link #getBodyContentType()} as well to match the new body format.
+ *
+ * @throws AuthFailureError in the event of auth failure
+ */
+ public byte[] getBody() throws AuthFailureError {
+ Map params = getParams();
+ if (params != null && params.size() > 0) {
+ return encodeParameters(params, getParamsEncoding());
+ }
+ return null;
+ }
+
+ /**
+ * Converts params into an application/x-www-form-urlencoded encoded string.
+ */
+ private byte[] encodeParameters(Map params, String paramsEncoding) {
+ StringBuilder encodedParams = new StringBuilder();
+ try {
+ for (Map.Entry entry : params.entrySet()) {
+ encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
+ encodedParams.append('=');
+ encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
+ encodedParams.append('&');
+ }
+ return encodedParams.toString().getBytes(paramsEncoding);
+ } catch (UnsupportedEncodingException uee) {
+ throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
+ }
+ }
+
+ /**
+ * Set whether or not responses to this request should be cached.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public final Request> setShouldCache(boolean shouldCache) {
+ mShouldCache = shouldCache;
+ return this;
+ }
+
+ /**
+ * Returns true if responses to this request should be cached.
+ */
+ public final boolean shouldCache() {
+ return mShouldCache;
+ }
+
+ /**
+ * Sets whether or not the request should be retried in the event of an HTTP 5xx (server) error.
+ *
+ * @return This Request object to allow for chaining.
+ */
+ public final Request> setShouldRetryServerErrors(boolean shouldRetryServerErrors) {
+ mShouldRetryServerErrors = shouldRetryServerErrors;
+ return this;
+ }
+
+ /**
+ * Returns true if this request should be retried in the event of an HTTP 5xx (server) error.
+ */
+ public final boolean shouldRetryServerErrors() {
+ return mShouldRetryServerErrors;
+ }
+
+ /**
+ * Priority values. Requests will be processed from higher priorities to
+ * lower priorities, in FIFO order.
+ */
+ public enum Priority {
+ LOW,
+ NORMAL,
+ HIGH,
+ IMMEDIATE
+ }
+
+ /**
+ * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
+ */
+ public Priority getPriority() {
+ return Priority.NORMAL;
+ }
+
+ /**
+ * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
+ * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
+ * attempts remaining, this will cause delivery of a {@link TimeoutError} error.
+ */
+ public final int getTimeoutMs() {
+ return mRetryPolicy.getCurrentTimeout();
+ }
+
+ /**
+ * Returns the retry policy that should be used for this request.
+ */
+ public RetryPolicy getRetryPolicy() {
+ return mRetryPolicy;
+ }
+
+ /**
+ * Mark this request as having a response delivered on it. This can be used
+ * later in the request's lifetime for suppressing identical responses.
+ */
+ public void markDelivered() {
+ mResponseDelivered = true;
+ }
+
+ /**
+ * Returns true if this request has had a response delivered for it.
+ */
+ public boolean hasHadResponseDelivered() {
+ return mResponseDelivered;
+ }
+
+ /**
+ * Subclasses must implement this to parse the raw network response
+ * and return an appropriate response type. This method will be
+ * called from a worker thread. The response will not be delivered
+ * if you return null.
+ * @param response Response from the network
+ * @return The parsed response, or null in the case of an error
+ */
+ abstract protected Response parseNetworkResponse(NetworkResponse response);
+
+ /**
+ * Subclasses can override this method to parse 'networkError' and return a more specific error.
+ *
+ *
The default implementation just returns the passed 'networkError'.
+ *
+ * @param volleyError the error retrieved from the network
+ * @return an NetworkError augmented with additional information
+ */
+ protected VolleyError parseNetworkError(VolleyError volleyError) {
+ return volleyError;
+ }
+
+ /**
+ * Subclasses must implement this to perform delivery of the parsed
+ * response to their listeners. The given response is guaranteed to
+ * be non-null; responses that fail to parse are not delivered.
+ * @param response The parsed response returned by
+ * {@link #parseNetworkResponse(NetworkResponse)}
+ */
+ abstract protected void deliverResponse(T response);
+
+ /**
+ * Delivers error message to the ErrorListener that the Request was
+ * initialized with.
+ *
+ * @param error Error details
+ */
+ public void deliverError(VolleyError error) {
+ if (mErrorListener != null) {
+ mErrorListener.onErrorResponse(error);
+ }
+ }
+
+ /**
+ * Our comparator sorts from high to low priority, and secondarily by
+ * sequence number to provide FIFO ordering.
+ */
+ @Override
+ public int compareTo(Request other) {
+ Priority left = this.getPriority();
+ Priority right = other.getPriority();
+
+ // High-priority requests are "lesser" so they are sorted to the front.
+ // Equal priorities are sorted by sequence number to provide FIFO ordering.
+ return left == right ?
+ this.mSequence - other.mSequence :
+ right.ordinal() - left.ordinal();
+ }
+
+ @Override
+ public String toString() {
+ String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
+ return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " "
+ + getPriority() + " " + mSequence;
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/RequestQueue.java b/volley/src/main/java/com/android/volley/RequestQueue.java
new file mode 100644
index 0000000..4324590
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/RequestQueue.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A request dispatch queue with a thread pool of dispatchers.
+ *
+ * Calling {@link #add(Request)} will enqueue the given Request for dispatch,
+ * resolving from either cache or network on a worker thread, and then delivering
+ * a parsed response on the main thread.
+ */
+public class RequestQueue {
+
+ /** Callback interface for completed requests. */
+ public static interface RequestFinishedListener {
+ /** Called when a request has finished processing. */
+ public void onRequestFinished(Request request);
+ }
+
+ /** Used for generating monotonically-increasing sequence numbers for requests. */
+ private AtomicInteger mSequenceGenerator = new AtomicInteger();
+
+ /**
+ * Staging area for requests that already have a duplicate request in flight.
+ *
+ *
+ *
containsKey(cacheKey) indicates that there is a request in flight for the given cache
+ * key.
+ *
get(cacheKey) returns waiting requests for the given cache key. The in flight request
+ * is not contained in that list. Is null if no requests are staged.
+ *
+ */
+ private final Map>> mWaitingRequests =
+ new HashMap>>();
+
+ /**
+ * The set of all requests currently being processed by this RequestQueue. A Request
+ * will be in this set if it is waiting in any queue or currently being processed by
+ * any dispatcher.
+ */
+ private final Set> mCurrentRequests = new HashSet>();
+
+ /** The cache triage queue. */
+ private final PriorityBlockingQueue> mCacheQueue =
+ new PriorityBlockingQueue>();
+
+ /** The queue of requests that are actually going out to the network. */
+ private final PriorityBlockingQueue> mNetworkQueue =
+ new PriorityBlockingQueue>();
+
+ /** Number of network request dispatcher threads to start. */
+ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
+
+ /** Cache interface for retrieving and storing responses. */
+ private final Cache mCache;
+
+ /** Network interface for performing requests. */
+ private final Network mNetwork;
+
+ /** Response delivery mechanism. */
+ private final ResponseDelivery mDelivery;
+
+ /** The network dispatchers. */
+ private NetworkDispatcher[] mDispatchers;
+
+ /** The cache dispatcher. */
+ private CacheDispatcher mCacheDispatcher;
+
+ private List mFinishedListeners =
+ new ArrayList();
+
+ /**
+ * Creates the worker pool. Processing will not begin until {@link #start()} is called.
+ *
+ * @param cache A Cache to use for persisting responses to disk
+ * @param network A Network interface for performing HTTP requests
+ * @param threadPoolSize Number of network dispatcher threads to create
+ * @param delivery A ResponseDelivery interface for posting responses and errors
+ */
+ public RequestQueue(Cache cache, Network network, int threadPoolSize,
+ ResponseDelivery delivery) {
+ mCache = cache;
+ mNetwork = network;
+ mDispatchers = new NetworkDispatcher[threadPoolSize];
+ mDelivery = delivery;
+ }
+
+ /**
+ * Creates the worker pool. Processing will not begin until {@link #start()} is called.
+ *
+ * @param cache A Cache to use for persisting responses to disk
+ * @param network A Network interface for performing HTTP requests
+ * @param threadPoolSize Number of network dispatcher threads to create
+ */
+ public RequestQueue(Cache cache, Network network, int threadPoolSize) {
+ this(cache, network, threadPoolSize,
+ new ExecutorDelivery(new Handler(Looper.getMainLooper())));
+ }
+
+ /**
+ * Creates the worker pool. Processing will not begin until {@link #start()} is called.
+ *
+ * @param cache A Cache to use for persisting responses to disk
+ * @param network A Network interface for performing HTTP requests
+ */
+ public RequestQueue(Cache cache, Network network) {
+ this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
+ }
+
+ /**
+ * Starts the dispatchers in this queue.
+ */
+ public void start() {
+ stop(); // Make sure any currently running dispatchers are stopped.
+ // Create the cache dispatcher and start it.
+ mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
+ mCacheDispatcher.start();
+
+ // Create network dispatchers (and corresponding threads) up to the pool size.
+ for (int i = 0; i < mDispatchers.length; i++) {
+ NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
+ mCache, mDelivery);
+ mDispatchers[i] = networkDispatcher;
+ networkDispatcher.start();
+ }
+ }
+
+ /**
+ * Stops the cache and network dispatchers.
+ */
+ public void stop() {
+ if (mCacheDispatcher != null) {
+ mCacheDispatcher.quit();
+ }
+ for (int i = 0; i < mDispatchers.length; i++) {
+ if (mDispatchers[i] != null) {
+ mDispatchers[i].quit();
+ }
+ }
+ }
+
+ /**
+ * Gets a sequence number.
+ */
+ public int getSequenceNumber() {
+ return mSequenceGenerator.incrementAndGet();
+ }
+
+ /**
+ * Gets the {@link Cache} instance being used.
+ */
+ public Cache getCache() {
+ return mCache;
+ }
+
+ /**
+ * A simple predicate or filter interface for Requests, for use by
+ * {@link RequestQueue#cancelAll(RequestFilter)}.
+ */
+ public interface RequestFilter {
+ public boolean apply(Request> request);
+ }
+
+ /**
+ * Cancels all requests in this queue for which the given filter applies.
+ * @param filter The filtering function to use
+ */
+ public void cancelAll(RequestFilter filter) {
+ synchronized (mCurrentRequests) {
+ for (Request> request : mCurrentRequests) {
+ if (filter.apply(request)) {
+ request.cancel();
+ }
+ }
+ }
+ }
+
+ /**
+ * Cancels all requests in this queue with the given tag. Tag must be non-null
+ * and equality is by identity.
+ */
+ public void cancelAll(final Object tag) {
+ if (tag == null) {
+ throw new IllegalArgumentException("Cannot cancelAll with a null tag");
+ }
+ cancelAll(new RequestFilter() {
+ @Override
+ public boolean apply(Request> request) {
+ return request.getTag() == tag;
+ }
+ });
+ }
+
+ /**
+ * Adds a Request to the dispatch queue.
+ * @param request The request to service
+ * @return The passed-in request
+ */
+ public Request add(Request request) {
+ // Tag the request as belonging to this queue and add it to the set of current requests.
+ request.setRequestQueue(this);
+ synchronized (mCurrentRequests) {
+ mCurrentRequests.add(request);
+ }
+
+ // Process requests in the order they are added.
+ request.setSequence(getSequenceNumber());
+ request.addMarker("add-to-queue");
+
+ // If the request is uncacheable, skip the cache queue and go straight to the network.
+ if (!request.shouldCache()) {
+ mNetworkQueue.add(request);
+ return request;
+ }
+
+ // Insert request into stage if there's already a request with the same cache key in flight.
+ synchronized (mWaitingRequests) {
+ String cacheKey = request.getCacheKey();
+ if (mWaitingRequests.containsKey(cacheKey)) {
+ // There is already a request in flight. Queue up.
+ Queue> stagedRequests = mWaitingRequests.get(cacheKey);
+ if (stagedRequests == null) {
+ stagedRequests = new LinkedList>();
+ }
+ stagedRequests.add(request);
+ mWaitingRequests.put(cacheKey, stagedRequests);
+ if (VolleyLog.DEBUG) {
+ VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
+ }
+ } else {
+ // Insert 'null' queue for this cacheKey, indicating there is now a request in
+ // flight.
+ mWaitingRequests.put(cacheKey, null);
+ mCacheQueue.add(request);
+ }
+ return request;
+ }
+ }
+
+ /**
+ * Called from {@link Request#finish(String)}, indicating that processing of the given request
+ * has finished.
+ *
+ *
Releases waiting requests for request.getCacheKey() if
+ * request.shouldCache().
+ */
+ void finish(Request request) {
+ // Remove from the set of requests currently being processed.
+ synchronized (mCurrentRequests) {
+ mCurrentRequests.remove(request);
+ }
+ synchronized (mFinishedListeners) {
+ for (RequestFinishedListener listener : mFinishedListeners) {
+ listener.onRequestFinished(request);
+ }
+ }
+
+ if (request.shouldCache()) {
+ synchronized (mWaitingRequests) {
+ String cacheKey = request.getCacheKey();
+ Queue> waitingRequests = mWaitingRequests.remove(cacheKey);
+ if (waitingRequests != null) {
+ if (VolleyLog.DEBUG) {
+ VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
+ waitingRequests.size(), cacheKey);
+ }
+ // Process all queued up requests. They won't be considered as in flight, but
+ // that's not a problem as the cache has been primed by 'request'.
+ mCacheQueue.addAll(waitingRequests);
+ }
+ }
+ }
+ }
+
+ public void addRequestFinishedListener(RequestFinishedListener listener) {
+ synchronized (mFinishedListeners) {
+ mFinishedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove a RequestFinishedListener. Has no effect if listener was not previously added.
+ */
+ public void removeRequestFinishedListener(RequestFinishedListener listener) {
+ synchronized (mFinishedListeners) {
+ mFinishedListeners.remove(listener);
+ }
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/Response.java b/volley/src/main/java/com/android/volley/Response.java
new file mode 100644
index 0000000..1165595
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/Response.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Encapsulates a parsed response for delivery.
+ *
+ * @param Parsed type of this response
+ */
+public class Response {
+
+ /** Callback interface for delivering parsed responses. */
+ public interface Listener {
+ /** Called when a response is received. */
+ public void onResponse(T response);
+ }
+
+ /** Callback interface for delivering error responses. */
+ public interface ErrorListener {
+ /**
+ * Callback method that an error has been occurred with the
+ * provided error code and optional user-readable message.
+ */
+ public void onErrorResponse(VolleyError error);
+ }
+
+ /** Returns a successful response containing the parsed result. */
+ public static Response success(T result, Cache.Entry cacheEntry) {
+ return new Response(result, cacheEntry);
+ }
+
+ /**
+ * Returns a failed response containing the given error code and an optional
+ * localized message displayed to the user.
+ */
+ public static Response error(VolleyError error) {
+ return new Response(error);
+ }
+
+ /** Parsed response, or null in the case of error. */
+ public final T result;
+
+ /** Cache metadata for this response, or null in the case of error. */
+ public final Cache.Entry cacheEntry;
+
+ /** Detailed error information if errorCode != OK. */
+ public final VolleyError error;
+
+ /** True if this response was a soft-expired one and a second one MAY be coming. */
+ public boolean intermediate = false;
+
+ /**
+ * Returns whether this response is considered successful.
+ */
+ public boolean isSuccess() {
+ return error == null;
+ }
+
+
+ private Response(T result, Cache.Entry cacheEntry) {
+ this.result = result;
+ this.cacheEntry = cacheEntry;
+ this.error = null;
+ }
+
+ private Response(VolleyError error) {
+ this.result = null;
+ this.cacheEntry = null;
+ this.error = error;
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/ResponseDelivery.java b/volley/src/main/java/com/android/volley/ResponseDelivery.java
new file mode 100644
index 0000000..87706af
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/ResponseDelivery.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+public interface ResponseDelivery {
+ /**
+ * Parses a response from the network or cache and delivers it.
+ */
+ public void postResponse(Request> request, Response> response);
+
+ /**
+ * Parses a response from the network or cache and delivers it. The provided
+ * Runnable will be executed after delivery.
+ */
+ public void postResponse(Request> request, Response> response, Runnable runnable);
+
+ /**
+ * Posts an error for the given request.
+ */
+ public void postError(Request> request, VolleyError error);
+}
diff --git a/volley/src/main/java/com/android/volley/RetryPolicy.java b/volley/src/main/java/com/android/volley/RetryPolicy.java
new file mode 100644
index 0000000..0dd198b
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/RetryPolicy.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Retry policy for a request.
+ */
+public interface RetryPolicy {
+
+ /**
+ * Returns the current timeout (used for logging).
+ */
+ public int getCurrentTimeout();
+
+ /**
+ * Returns the current retry count (used for logging).
+ */
+ public int getCurrentRetryCount();
+
+ /**
+ * Prepares for the next retry by applying a backoff to the timeout.
+ * @param error The error code of the last attempt.
+ * @throws VolleyError In the event that the retry could not be performed (for example if we
+ * ran out of attempts), the passed in error is thrown.
+ */
+ public void retry(VolleyError error) throws VolleyError;
+}
diff --git a/volley/src/main/java/com/android/volley/ServerError.java b/volley/src/main/java/com/android/volley/ServerError.java
new file mode 100644
index 0000000..7b33c33
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/ServerError.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Indicates that the server responded with an error response.
+ */
+@SuppressWarnings("serial")
+public class ServerError extends VolleyError {
+ public ServerError(NetworkResponse networkResponse) {
+ super(networkResponse);
+ }
+
+ public ServerError() {
+ super();
+ }
+}
+
diff --git a/volley/src/main/java/com/android/volley/TimeoutError.java b/volley/src/main/java/com/android/volley/TimeoutError.java
new file mode 100644
index 0000000..0b5d6ac
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/TimeoutError.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Indicates that the connection or the socket timed out.
+ */
+@SuppressWarnings("serial")
+public class TimeoutError extends VolleyError { }
diff --git a/volley/src/main/java/com/android/volley/VolleyError.java b/volley/src/main/java/com/android/volley/VolleyError.java
new file mode 100644
index 0000000..1471d40
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/VolleyError.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+/**
+ * Exception style class encapsulating Volley errors
+ */
+@SuppressWarnings("serial")
+public class VolleyError extends Exception {
+ public final NetworkResponse networkResponse;
+ private long networkTimeMs;
+
+ public VolleyError() {
+ networkResponse = null;
+ }
+
+ public VolleyError(NetworkResponse response) {
+ networkResponse = response;
+ }
+
+ public VolleyError(String exceptionMessage) {
+ super(exceptionMessage);
+ networkResponse = null;
+ }
+
+ public VolleyError(String exceptionMessage, Throwable reason) {
+ super(exceptionMessage, reason);
+ networkResponse = null;
+ }
+
+ public VolleyError(Throwable cause) {
+ super(cause);
+ networkResponse = null;
+ }
+
+ /* package */ void setNetworkTimeMs(long networkTimeMs) {
+ this.networkTimeMs = networkTimeMs;
+ }
+
+ public long getNetworkTimeMs() {
+ return networkTimeMs;
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/VolleyLog.java b/volley/src/main/java/com/android/volley/VolleyLog.java
new file mode 100644
index 0000000..ffe9eb8
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/VolleyLog.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Logging helper class.
+ *
+ * to see Volley logs call:
+ * {@code /platform-tools/adb shell setprop log.tag.Volley VERBOSE}
+ */
+public class VolleyLog {
+ public static String TAG = "Volley";
+
+ public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
+
+ /**
+ * Customize the log tag for your application, so that other apps
+ * using Volley don't mix their logs with yours.
+ *
+ * Enable the log property for your tag before starting your app:
+ *
+ * {@code adb shell setprop log.tag.<tag>}
+ */
+ public static void setTag(String tag) {
+ d("Changing log tag to %s", tag);
+ TAG = tag;
+
+ // Reinitialize the DEBUG "constant"
+ DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
+ }
+
+ public static void v(String format, Object... args) {
+ if (DEBUG) {
+ Log.v(TAG, buildMessage(format, args));
+ }
+ }
+
+ public static void d(String format, Object... args) {
+ Log.d(TAG, buildMessage(format, args));
+ }
+
+ public static void e(String format, Object... args) {
+ Log.e(TAG, buildMessage(format, args));
+ }
+
+ public static void e(Throwable tr, String format, Object... args) {
+ Log.e(TAG, buildMessage(format, args), tr);
+ }
+
+ public static void wtf(String format, Object... args) {
+ Log.wtf(TAG, buildMessage(format, args));
+ }
+
+ public static void wtf(Throwable tr, String format, Object... args) {
+ Log.wtf(TAG, buildMessage(format, args), tr);
+ }
+
+ /**
+ * Formats the caller's provided message and prepends useful info like
+ * calling thread ID and method name.
+ */
+ private static String buildMessage(String format, Object... args) {
+ String msg = (args == null) ? format : String.format(Locale.US, format, args);
+ StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
+
+ String caller = "";
+ // Walk up the stack looking for the first caller outside of VolleyLog.
+ // It will be at least two frames up, so start there.
+ for (int i = 2; i < trace.length; i++) {
+ Class> clazz = trace[i].getClass();
+ if (!clazz.equals(VolleyLog.class)) {
+ String callingClass = trace[i].getClassName();
+ callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
+ callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
+
+ caller = callingClass + "." + trace[i].getMethodName();
+ break;
+ }
+ }
+ return String.format(Locale.US, "[%d] %s: %s",
+ Thread.currentThread().getId(), caller, msg);
+ }
+
+ /**
+ * A simple event log with records containing a name, thread ID, and timestamp.
+ */
+ static class MarkerLog {
+ public static final boolean ENABLED = VolleyLog.DEBUG;
+
+ /** Minimum duration from first marker to last in an marker log to warrant logging. */
+ private static final long MIN_DURATION_FOR_LOGGING_MS = 0;
+
+ private static class Marker {
+ public final String name;
+ public final long thread;
+ public final long time;
+
+ public Marker(String name, long thread, long time) {
+ this.name = name;
+ this.thread = thread;
+ this.time = time;
+ }
+ }
+
+ private final List mMarkers = new ArrayList();
+ private boolean mFinished = false;
+
+ /** Adds a marker to this log with the specified name. */
+ public synchronized void add(String name, long threadId) {
+ if (mFinished) {
+ throw new IllegalStateException("Marker added to finished log");
+ }
+
+ mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Closes the log, dumping it to logcat if the time difference between
+ * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}.
+ * @param header Header string to print above the marker log.
+ */
+ public synchronized void finish(String header) {
+ mFinished = true;
+
+ long duration = getTotalDuration();
+ if (duration <= MIN_DURATION_FOR_LOGGING_MS) {
+ return;
+ }
+
+ long prevTime = mMarkers.get(0).time;
+ d("(%-4d ms) %s", duration, header);
+ for (Marker marker : mMarkers) {
+ long thisTime = marker.time;
+ d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name);
+ prevTime = thisTime;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ // Catch requests that have been collected (and hence end-of-lifed)
+ // but had no debugging output printed for them.
+ if (!mFinished) {
+ finish("Request on the loose");
+ e("Marker log finalized without finish() - uncaught exit point for request");
+ }
+ }
+
+ /** Returns the time difference between the first and last events in this log. */
+ private long getTotalDuration() {
+ if (mMarkers.size() == 0) {
+ return 0;
+ }
+
+ long first = mMarkers.get(0).time;
+ long last = mMarkers.get(mMarkers.size() - 1).time;
+ return last - first;
+ }
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java b/volley/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java
new file mode 100644
index 0000000..18f8597
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import com.android.volley.AuthFailureError;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * An Authenticator that uses {@link AccountManager} to get auth
+ * tokens of a specified type for a specified account.
+ */
+public class AndroidAuthenticator implements Authenticator {
+ private final AccountManager mAccountManager;
+ private final Account mAccount;
+ private final String mAuthTokenType;
+ private final boolean mNotifyAuthFailure;
+
+ /**
+ * Creates a new authenticator.
+ * @param context Context for accessing AccountManager
+ * @param account Account to authenticate as
+ * @param authTokenType Auth token type passed to AccountManager
+ */
+ public AndroidAuthenticator(Context context, Account account, String authTokenType) {
+ this(context, account, authTokenType, false);
+ }
+
+ /**
+ * Creates a new authenticator.
+ * @param context Context for accessing AccountManager
+ * @param account Account to authenticate as
+ * @param authTokenType Auth token type passed to AccountManager
+ * @param notifyAuthFailure Whether to raise a notification upon auth failure
+ */
+ public AndroidAuthenticator(Context context, Account account, String authTokenType,
+ boolean notifyAuthFailure) {
+ this(AccountManager.get(context), account, authTokenType, notifyAuthFailure);
+ }
+
+ // Visible for testing. Allows injection of a mock AccountManager.
+ AndroidAuthenticator(AccountManager accountManager, Account account,
+ String authTokenType, boolean notifyAuthFailure) {
+ mAccountManager = accountManager;
+ mAccount = account;
+ mAuthTokenType = authTokenType;
+ mNotifyAuthFailure = notifyAuthFailure;
+ }
+
+ /**
+ * Returns the Account being used by this authenticator.
+ */
+ public Account getAccount() {
+ return mAccount;
+ }
+
+ /**
+ * Returns the Auth Token Type used by this authenticator.
+ */
+ public String getAuthTokenType() {
+ return mAuthTokenType;
+ }
+
+ // TODO: Figure out what to do about notifyAuthFailure
+ @SuppressWarnings("deprecation")
+ @Override
+ public String getAuthToken() throws AuthFailureError {
+ AccountManagerFuture future = mAccountManager.getAuthToken(mAccount,
+ mAuthTokenType, mNotifyAuthFailure, null, null);
+ Bundle result;
+ try {
+ result = future.getResult();
+ } catch (Exception e) {
+ throw new AuthFailureError("Error while retrieving auth token", e);
+ }
+ String authToken = null;
+ if (future.isDone() && !future.isCancelled()) {
+ if (result.containsKey(AccountManager.KEY_INTENT)) {
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ throw new AuthFailureError(intent);
+ }
+ authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
+ }
+ if (authToken == null) {
+ throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType);
+ }
+
+ return authToken;
+ }
+
+ @Override
+ public void invalidateAuthToken(String authToken) {
+ mAccountManager.invalidateAuthToken(mAccount.type, authToken);
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/toolbox/Authenticator.java b/volley/src/main/java/com/android/volley/toolbox/Authenticator.java
new file mode 100644
index 0000000..d9f5e3c
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/toolbox/Authenticator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import com.android.volley.AuthFailureError;
+
+/**
+ * An interface for interacting with auth tokens.
+ */
+public interface Authenticator {
+ /**
+ * Synchronously retrieves an auth token.
+ *
+ * @throws AuthFailureError If authentication did not succeed
+ */
+ public String getAuthToken() throws AuthFailureError;
+
+ /**
+ * Invalidates the provided auth token.
+ */
+ public void invalidateAuthToken(String authToken);
+}
diff --git a/volley/src/main/java/com/android/volley/toolbox/BasicNetwork.java b/volley/src/main/java/com/android/volley/toolbox/BasicNetwork.java
new file mode 100644
index 0000000..37c35ec
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/toolbox/BasicNetwork.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import android.os.SystemClock;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Cache;
+import com.android.volley.Cache.Entry;
+import com.android.volley.ClientError;
+import com.android.volley.Network;
+import com.android.volley.NetworkError;
+import com.android.volley.NetworkResponse;
+import com.android.volley.NoConnectionError;
+import com.android.volley.Request;
+import com.android.volley.RetryPolicy;
+import com.android.volley.ServerError;
+import com.android.volley.TimeoutError;
+import com.android.volley.VolleyError;
+import com.android.volley.VolleyLog;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.impl.cookie.DateUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A network performing Volley requests over an {@link HttpStack}.
+ */
+public class BasicNetwork implements Network {
+ protected static final boolean DEBUG = VolleyLog.DEBUG;
+
+ private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
+
+ private static int DEFAULT_POOL_SIZE = 4096;
+
+ protected final HttpStack mHttpStack;
+
+ protected final ByteArrayPool mPool;
+
+ /**
+ * @param httpStack HTTP stack to be used
+ */
+ public BasicNetwork(HttpStack httpStack) {
+ // If a pool isn't passed in, then build a small default pool that will give us a lot of
+ // benefit and not use too much memory.
+ this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
+ }
+
+ /**
+ * @param httpStack HTTP stack to be used
+ * @param pool a buffer pool that improves GC performance in copy operations
+ */
+ public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
+ mHttpStack = httpStack;
+ mPool = pool;
+ }
+
+ @Override
+ public NetworkResponse performRequest(Request> request) throws VolleyError {
+ long requestStart = SystemClock.elapsedRealtime();
+ while (true) {
+ HttpResponse httpResponse = null;
+ byte[] responseContents = null;
+ Map responseHeaders = Collections.emptyMap();
+ try {
+ // Gather headers.
+ Map headers = new HashMap();
+ addCacheHeaders(headers, request.getCacheEntry());
+ httpResponse = mHttpStack.performRequest(request, headers);
+ StatusLine statusLine = httpResponse.getStatusLine();
+ int statusCode = statusLine.getStatusCode();
+
+ responseHeaders = convertHeaders(httpResponse.getAllHeaders());
+ // Handle cache validation.
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
+
+ Entry entry = request.getCacheEntry();
+ if (entry == null) {
+ return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
+ responseHeaders, true,
+ SystemClock.elapsedRealtime() - requestStart);
+ }
+
+ // A HTTP 304 response does not have all header fields. We
+ // have to use the header fields from the cache entry plus
+ // the new ones from the response.
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
+ entry.responseHeaders.putAll(responseHeaders);
+ return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
+ entry.responseHeaders, true,
+ SystemClock.elapsedRealtime() - requestStart);
+ }
+
+ // Some responses such as 204s do not have content. We must check.
+ if (httpResponse.getEntity() != null) {
+ responseContents = entityToBytes(httpResponse.getEntity());
+ } else {
+ // Add 0 byte response as a way of honestly representing a
+ // no-content request.
+ responseContents = new byte[0];
+ }
+
+ // if the request is slow, log it.
+ long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
+ logSlowRequests(requestLifetime, request, responseContents, statusLine);
+
+ if (statusCode < 200 || statusCode > 299) {
+ throw new IOException();
+ }
+ return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
+ SystemClock.elapsedRealtime() - requestStart);
+ } catch (SocketTimeoutException e) {
+ attemptRetryOnException("socket", request, new TimeoutError());
+ } catch (ConnectTimeoutException e) {
+ attemptRetryOnException("connection", request, new TimeoutError());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Bad URL " + request.getUrl(), e);
+ } catch (IOException e) {
+ int statusCode;
+ if (httpResponse != null) {
+ statusCode = httpResponse.getStatusLine().getStatusCode();
+ } else {
+ throw new NoConnectionError(e);
+ }
+ VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
+ NetworkResponse networkResponse;
+ if (responseContents != null) {
+ networkResponse = new NetworkResponse(statusCode, responseContents,
+ responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
+ if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
+ statusCode == HttpStatus.SC_FORBIDDEN) {
+ attemptRetryOnException("auth",
+ request, new AuthFailureError(networkResponse));
+ } else if (statusCode >= 400 && statusCode <= 499) {
+ // Don't retry other client errors.
+ throw new ClientError(networkResponse);
+ } else if (statusCode >= 500 && statusCode <= 599) {
+ if (request.shouldRetryServerErrors()) {
+ attemptRetryOnException("server",
+ request, new ServerError(networkResponse));
+ } else {
+ throw new ServerError(networkResponse);
+ }
+ } else {
+ // 3xx? No reason to retry.
+ throw new ServerError(networkResponse);
+ }
+ } else {
+ attemptRetryOnException("network", request, new NetworkError());
+ }
+ }
+ }
+ }
+
+ /**
+ * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
+ */
+ private void logSlowRequests(long requestLifetime, Request> request,
+ byte[] responseContents, StatusLine statusLine) {
+ if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
+ VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
+ "[rc=%d], [retryCount=%s]", request, requestLifetime,
+ responseContents != null ? responseContents.length : "null",
+ statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
+ }
+ }
+
+ /**
+ * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
+ * request's retry policy, a timeout exception is thrown.
+ * @param request The request to use.
+ */
+ private static void attemptRetryOnException(String logPrefix, Request> request,
+ VolleyError exception) throws VolleyError {
+ RetryPolicy retryPolicy = request.getRetryPolicy();
+ int oldTimeout = request.getTimeoutMs();
+
+ try {
+ retryPolicy.retry(exception);
+ } catch (VolleyError e) {
+ request.addMarker(
+ String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
+ throw e;
+ }
+ request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
+ }
+
+ private void addCacheHeaders(Map headers, Cache.Entry entry) {
+ // If there's no cache entry, we're done.
+ if (entry == null) {
+ return;
+ }
+
+ if (entry.etag != null) {
+ headers.put("If-None-Match", entry.etag);
+ }
+
+ if (entry.lastModified > 0) {
+ Date refTime = new Date(entry.lastModified);
+ headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
+ }
+ }
+
+ protected void logError(String what, String url, long start) {
+ long now = SystemClock.elapsedRealtime();
+ VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
+ }
+
+ /** Reads the contents of HttpEntity into a byte[]. */
+ private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
+ PoolingByteArrayOutputStream bytes =
+ new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
+ byte[] buffer = null;
+ try {
+ InputStream in = entity.getContent();
+ if (in == null) {
+ throw new ServerError();
+ }
+ buffer = mPool.getBuf(1024);
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ }
+ return bytes.toByteArray();
+ } finally {
+ try {
+ // Close the InputStream and release the resources by "consuming the content".
+ entity.consumeContent();
+ } catch (IOException e) {
+ // This can happen if there was an exception above that left the entity in
+ // an invalid state.
+ VolleyLog.v("Error occured when calling consumingContent");
+ }
+ mPool.returnBuf(buffer);
+ bytes.close();
+ }
+ }
+
+ /**
+ * Converts Headers[] to Map.
+ */
+ protected static Map convertHeaders(Header[] headers) {
+ Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < headers.length; i++) {
+ result.put(headers[i].getName(), headers[i].getValue());
+ }
+ return result;
+ }
+}
diff --git a/volley/src/main/java/com/android/volley/toolbox/ByteArrayPool.java b/volley/src/main/java/com/android/volley/toolbox/ByteArrayPool.java
new file mode 100644
index 0000000..af95076
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/toolbox/ByteArrayPool.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to
+ * supply those buffers to consumers who need to use them for a short period of time and then
+ * dispose of them. Simply creating and disposing such buffers in the conventional manner can
+ * considerable heap churn and garbage collection delays on Android, which lacks good management of
+ * short-lived heap objects. It may be advantageous to trade off some memory in the form of a
+ * permanently allocated pool of buffers in order to gain heap performance improvements; that is
+ * what this class does.
+ *
+ * A good candidate user for this class is something like an I/O system that uses large temporary
+ * byte[] buffers to copy data around. In these use cases, often the consumer wants
+ * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks
+ * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into
+ * account and also to maximize the odds of being able to reuse a recycled buffer, this class is
+ * free to return buffers larger than the requested size. The caller needs to be able to gracefully
+ * deal with getting buffers any size over the minimum.
+ *
+ * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this
+ * class will allocate a new buffer and return it.
+ *
+ * This class has no special ownership of buffers it creates; the caller is free to take a buffer
+ * it receives from this pool, use it permanently, and never return it to the pool; additionally,
+ * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there
+ * are no other lingering references to it.
+ *
+ * This class ensures that the total size of the buffers in its recycling pool never exceeds a
+ * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit,
+ * least-recently-used buffers are disposed.
+ */
+public class ByteArrayPool {
+ /** The buffer pool, arranged both by last use and by buffer size */
+ private List mBuffersByLastUse = new LinkedList();
+ private List mBuffersBySize = new ArrayList(64);
+
+ /** The total size of the buffers in the pool */
+ private int mCurrentSize = 0;
+
+ /**
+ * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
+ * under this limit.
+ */
+ private final int mSizeLimit;
+
+ /** Compares buffers by size */
+ protected static final Comparator BUF_COMPARATOR = new Comparator() {
+ @Override
+ public int compare(byte[] lhs, byte[] rhs) {
+ return lhs.length - rhs.length;
+ }
+ };
+
+ /**
+ * @param sizeLimit the maximum size of the pool, in bytes
+ */
+ public ByteArrayPool(int sizeLimit) {
+ mSizeLimit = sizeLimit;
+ }
+
+ /**
+ * Returns a buffer from the pool if one is available in the requested size, or allocates a new
+ * one if a pooled one is not available.
+ *
+ * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
+ * larger.
+ * @return a byte[] buffer is always returned.
+ */
+ public synchronized byte[] getBuf(int len) {
+ for (int i = 0; i < mBuffersBySize.size(); i++) {
+ byte[] buf = mBuffersBySize.get(i);
+ if (buf.length >= len) {
+ mCurrentSize -= buf.length;
+ mBuffersBySize.remove(i);
+ mBuffersByLastUse.remove(buf);
+ return buf;
+ }
+ }
+ return new byte[len];
+ }
+
+ /**
+ * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
+ * size.
+ *
+ * @param buf the buffer to return to the pool.
+ */
+ public synchronized void returnBuf(byte[] buf) {
+ if (buf == null || buf.length > mSizeLimit) {
+ return;
+ }
+ mBuffersByLastUse.add(buf);
+ int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
+ if (pos < 0) {
+ pos = -pos - 1;
+ }
+ mBuffersBySize.add(pos, buf);
+ mCurrentSize += buf.length;
+ trim();
+ }
+
+ /**
+ * Removes buffers from the pool until it is under its size limit.
+ */
+ private synchronized void trim() {
+ while (mCurrentSize > mSizeLimit) {
+ byte[] buf = mBuffersByLastUse.remove(0);
+ mBuffersBySize.remove(buf);
+ mCurrentSize -= buf.length;
+ }
+ }
+
+}
diff --git a/volley/src/main/java/com/android/volley/toolbox/ClearCacheRequest.java b/volley/src/main/java/com/android/volley/toolbox/ClearCacheRequest.java
new file mode 100644
index 0000000..a3478bf
--- /dev/null
+++ b/volley/src/main/java/com/android/volley/toolbox/ClearCacheRequest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import com.android.volley.Cache;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Request;
+import com.android.volley.Response;
+
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * A synthetic request used for clearing the cache.
+ */
+public class ClearCacheRequest extends Request