├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ ├── android │ │ └── volley │ │ │ ├── AuthFailureError.java │ │ │ ├── Cache.java │ │ │ ├── CacheDispatcher.java │ │ │ ├── DefaultRetryPolicy.java │ │ │ ├── ExecutorDelivery.java │ │ │ ├── Network.java │ │ │ ├── NetworkDispatcher.java │ │ │ ├── NetworkError.java │ │ │ ├── NetworkResponse.java │ │ │ ├── NoConnectionError.java │ │ │ ├── ParseError.java │ │ │ ├── Request.java │ │ │ ├── RequestQueue.java │ │ │ ├── Response.java │ │ │ ├── ResponseDelivery.java │ │ │ ├── RetryPolicy.java │ │ │ ├── ServerError.java │ │ │ ├── TimeoutError.java │ │ │ ├── VolleyError.java │ │ │ ├── VolleyLog.java │ │ │ └── toolbox │ │ │ ├── AndroidAuthenticator.java │ │ │ ├── Authenticator.java │ │ │ ├── BasicNetwork.java │ │ │ ├── ByteArrayPool.java │ │ │ ├── ClearCacheRequest.java │ │ │ ├── DiskBasedCache.java │ │ │ ├── HttpClientStack.java │ │ │ ├── HttpHeaderParser.java │ │ │ ├── HttpStack.java │ │ │ ├── HurlStack.java │ │ │ ├── ImageLoader.java │ │ │ ├── ImageRequest.java │ │ │ ├── JsonArrayRequest.java │ │ │ ├── JsonObjectRequest.java │ │ │ ├── JsonRequest.java │ │ │ ├── NetworkImageView.java │ │ │ ├── NoCache.java │ │ │ ├── PoolingByteArrayOutputStream.java │ │ │ ├── RequestFuture.java │ │ │ ├── StringRequest.java │ │ │ └── Volley.java │ │ └── vinaysshenoy │ │ └── okulusdemo │ │ ├── DemoApplication.java │ │ ├── MainActivity.java │ │ ├── fragments │ │ ├── ComparisonFragment.java │ │ ├── NetworkFragment.java │ │ └── RoundedRectanglesFragment.java │ │ └── utils │ │ └── RawBitmapManager.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-nodpi │ ├── img_for_uri.jpg │ └── img_res.jpg │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_main.xml │ ├── fragment_comparison.xml │ ├── fragment_rounded_rectangles.xml │ ├── layout_image_1.xml │ ├── layout_image_2.xml │ ├── layout_image_3.xml │ ├── layout_image_4.xml │ ├── layout_image_5.xml │ └── layout_image_6.xml │ ├── menu │ └── main.xml │ ├── raw │ ├── img_1.jpg │ ├── img_2.jpg │ ├── img_3.jpg │ ├── img_4.jpg │ ├── img_5.jpg │ └── img_6.jpg │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── maventask.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── vinaysshenoy │ │ └── okulus │ │ ├── OkulusDrawable.java │ │ └── OkulusImageView.java │ └── res │ └── values │ └── attrs.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | .DS_Store 26 | 27 | #IntelliJ 28 | .idea/ 29 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | okulus(BETA) 2 | ============ 3 | Custom Imageview for Android that allows for setting shapes/borders/shadows efficiently. This is currently in Beta as some minor issues are getting ironed out. 4 | 5 | Demo app is coming on the Play Store soon! 6 | 7 | The basic concept for drawing Bitmaps with a shape without creating a new Bitmap is based on [Romain Guy](https://plus.google.com/+RomainGuy)'s article on drawing [Bitmaps with Rounded Corners](http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/). 8 | 9 | ### Usage 10 | 1. Clone the repository and add the library to your project. 11 | 12 | 2. Add the custom namespace attribute to your layout 13 | ```xml 14 | xmlns:okulus="http://schemas.android.com/apk/res/com.vinaysshenoy.okulus 15 | ``` 16 | 17 | 3. Add `OkulusImageView` to your layout and set the attributes you need 18 | ```xml 19 | 30 | ``` 31 | 32 | 4. Call `setImageBitmap()` and you're done! 33 | ```java 34 | OkulusImageView imageView = findViewById(R.id.image_1); 35 | imageView.setImageBitmap(bitmap); 36 | ``` 37 | 38 | ### Custom Attributes 39 | 1. `okulus_cornerRadius(dimension)` - Sets the corner radius used for adding the rounded corners. Set it to 50% of the width(for a square image) to make it a full circle. Default `0dp`. 40 | 2. `okulus_fullCircle(boolean)` - If this is set to `true`, the entire Bitmap will be drawn as a circle. The width and height will be set to whichever is smaller among them, and `cornerRadius` attribute will be ignored. Default `false`. 41 | 3. `okulus_borderWidth(dimension)` - Sets the width of the border to be drawn. Will be capped at `5dp`. 42 | 4. `okulus_borderColor(color)` - Sets the color of the border to draw. Default color is `#FF000000`. 43 | 5. `okulus_shadowWidth(dimension)` - Sets the width of the shadow to draw. Will be capped at `3dp`. 44 | 6. `okulus_shadowColor(color)` - Sets the color of the shadow to draw. Default is `#B3444444`. 45 | 7. `okulus_shadowRadius(float)` - Defines how sharp the shadow is. Set it to a small value(~0.5) for sharp shadows, and larger values for soft shadows. Default `0.5` 46 | 8. `okulus_touchSelectorColor(color)` - Defines the colour of the color overlayed on the view when it is touched. This is ignored if `touchSelectorEnabled` is `false`. Default `#66444444` 47 | 9. `okulus_touchSelectorEnabled(boolean)` - Defines whether the touch selectors should be drawn or not. Default is `false` 48 | 49 | ### Pros 50 | 1. No extra memory used for creating the reshaped Bitmap 51 | 2. Zero overdraw 52 | 3. Any combination of shapes - Rounded Rects, Rects, Squares, Circles are possible with borders + shadow 53 | 54 | ### Limitations 55 | 1. Supports only fixed dimensions. `wrap_content` cannot be used. 56 | 2. Does not respect the `scaleType` attribute of `ImageView`. Scaled Bitmaps need to be provided. 57 | 3. Shadows cannot be used without borders 58 | 4. Attributes can only be set through XML 59 | 5. Shadows are currently drawn to the right and bottom of the View and cannot be changed. 60 | 61 | ## Roadmap 62 | ### Version 1.0 63 | 1. Adding `get()` and `set()` attributes for `OkulusImageView` for setting attributes through code 64 | 2. Respecting ImageView's `scaleType` attribute 65 | 66 | ### Future(in descending order of priority) 67 | 1. Adding support for `wrap_content` 68 | 2. Adding support for any configuration of shadows 69 | 3. Adding support for color filters to easily configure effects like Sepia, Grayscale etc. 70 | 4. Adding support for Image transitions when changing the image content 71 | 5. Adding support for custom shapes 72 | 6. ? 73 | 74 | ### License 75 | Copyright 2014 Vinay S Shenoy 76 | 77 | Licensed under the Apache License, Version 2.0 (the "License"); 78 | you may not use this file except in compliance with the License. 79 | You may obtain a copy of the License at 80 | 81 | http://www.apache.org/licenses/LICENSE-2.0 82 | 83 | Unless required by applicable law or agreed to in writing, software 84 | distributed under the License is distributed on an "AS IS" BASIS, 85 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 86 | See the License for the specific language governing permissions and 87 | limitations under the License. 88 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.2' 6 | 7 | /* Required for volley sample */ 8 | useLibrary 'org.apache.http.legacy' 9 | 10 | defaultConfig { 11 | applicationId "com.vinaysshenoy.okulusdemo" 12 | minSdkVersion 9 13 | targetSdkVersion 23 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | compile project(':library') 28 | compile 'com.android.support:appcompat-v7:23.1.1' 29 | compile 'com.squareup.picasso:picasso:2.4.0' 30 | compile 'com.github.bumptech.glide:glide:3.4.0' 31 | compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/vinay.shenoy/Desktop/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/AuthFailureError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.content.Intent; 20 | 21 | import com.android.volley.NetworkResponse; 22 | import com.android.volley.VolleyError; 23 | 24 | /** 25 | * Error indicating that there was an authentication failure when performing a Request. 26 | */ 27 | @SuppressWarnings("serial") 28 | public class AuthFailureError extends VolleyError { 29 | /** An intent that can be used to resolve this exception. (Brings up the password dialog.) */ 30 | private Intent mResolutionIntent; 31 | 32 | public AuthFailureError() { } 33 | 34 | public AuthFailureError(Intent intent) { 35 | mResolutionIntent = intent; 36 | } 37 | 38 | public AuthFailureError(NetworkResponse response) { 39 | super(response); 40 | } 41 | 42 | public AuthFailureError(String message) { 43 | super(message); 44 | } 45 | 46 | public AuthFailureError(String message, Exception reason) { 47 | super(message, reason); 48 | } 49 | 50 | public Intent getResolutionIntent() { 51 | return mResolutionIntent; 52 | } 53 | 54 | @Override 55 | public String getMessage() { 56 | if (mResolutionIntent != null) { 57 | return "User needs to (re)enter credentials."; 58 | } 59 | return super.getMessage(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | /** 23 | * An interface for a cache keyed by a String with a byte array as data. 24 | */ 25 | public interface Cache { 26 | /** 27 | * Retrieves an entry from the cache. 28 | * @param key Cache key 29 | * @return An {@link com.android.volley.Cache.Entry} or null in the event of a cache miss 30 | */ 31 | public Entry get(String key); 32 | 33 | /** 34 | * Adds or replaces an entry to the cache. 35 | * @param key Cache key 36 | * @param entry Data to store and metadata for cache coherency, TTL, etc. 37 | */ 38 | public void put(String key, Entry entry); 39 | 40 | /** 41 | * Performs any potentially long-running actions needed to initialize the cache; 42 | * will be called from a worker thread. 43 | */ 44 | public void initialize(); 45 | 46 | /** 47 | * Invalidates an entry in the cache. 48 | * @param key Cache key 49 | * @param fullExpire True to fully expire the entry, false to soft expire 50 | */ 51 | public void invalidate(String key, boolean fullExpire); 52 | 53 | /** 54 | * Removes an entry from the cache. 55 | * @param key Cache key 56 | */ 57 | public void remove(String key); 58 | 59 | /** 60 | * Empties the cache. 61 | */ 62 | public void clear(); 63 | 64 | /** 65 | * Data and metadata for an entry returned by the cache. 66 | */ 67 | public static class Entry { 68 | /** The data returned from cache. */ 69 | public byte[] data; 70 | 71 | /** ETag for cache coherency. */ 72 | public String etag; 73 | 74 | /** Date of this response as reported by the server. */ 75 | public long serverDate; 76 | 77 | /** TTL for this record. */ 78 | public long ttl; 79 | 80 | /** Soft TTL for this record. */ 81 | public long softTtl; 82 | 83 | /** Immutable response headers as received from server; must be non-null. */ 84 | public Map responseHeaders = Collections.emptyMap(); 85 | 86 | /** True if the entry is expired. */ 87 | public boolean isExpired() { 88 | return this.ttl < System.currentTimeMillis(); 89 | } 90 | 91 | /** True if a refresh is needed from the original data source. */ 92 | public boolean refreshNeeded() { 93 | return this.softTtl < System.currentTimeMillis(); 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/CacheDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.os.Process; 20 | 21 | import java.util.concurrent.BlockingQueue; 22 | 23 | /** 24 | * Provides a thread for performing cache triage on a queue of requests. 25 | * 26 | * Requests added to the specified cache queue are resolved from cache. 27 | * Any deliverable response is posted back to the caller via a 28 | * {@link ResponseDelivery}. Cache misses and responses that require 29 | * refresh are enqueued on the specified network queue for processing 30 | * by a {@link NetworkDispatcher}. 31 | */ 32 | public class CacheDispatcher extends Thread { 33 | 34 | private static final boolean DEBUG = VolleyLog.DEBUG; 35 | 36 | /** The queue of requests coming in for triage. */ 37 | private final BlockingQueue> mCacheQueue; 38 | 39 | /** The queue of requests going out to the network. */ 40 | private final BlockingQueue> mNetworkQueue; 41 | 42 | /** The cache to read from. */ 43 | private final Cache mCache; 44 | 45 | /** For posting responses. */ 46 | private final ResponseDelivery mDelivery; 47 | 48 | /** Used for telling us to die. */ 49 | private volatile boolean mQuit = false; 50 | 51 | /** 52 | * Creates a new cache triage dispatcher thread. You must call {@link #start()} 53 | * in order to begin processing. 54 | * 55 | * @param cacheQueue Queue of incoming requests for triage 56 | * @param networkQueue Queue to post requests that require network to 57 | * @param cache Cache interface to use for resolution 58 | * @param delivery Delivery interface to use for posting responses 59 | */ 60 | public CacheDispatcher( 61 | BlockingQueue> cacheQueue, BlockingQueue> networkQueue, 62 | Cache cache, ResponseDelivery delivery) { 63 | mCacheQueue = cacheQueue; 64 | mNetworkQueue = networkQueue; 65 | mCache = cache; 66 | mDelivery = delivery; 67 | } 68 | 69 | /** 70 | * Forces this dispatcher to quit immediately. If any requests are still in 71 | * the queue, they are not guaranteed to be processed. 72 | */ 73 | public void quit() { 74 | mQuit = true; 75 | interrupt(); 76 | } 77 | 78 | @Override 79 | public void run() { 80 | if (DEBUG) VolleyLog.v("start new dispatcher"); 81 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 82 | 83 | // Make a blocking call to initialize the cache. 84 | mCache.initialize(); 85 | 86 | while (true) { 87 | try { 88 | // Get a request from the cache triage queue, blocking until 89 | // at least one is available. 90 | final Request request = mCacheQueue.take(); 91 | request.addMarker("cache-queue-take"); 92 | 93 | // If the request has been canceled, don't bother dispatching it. 94 | if (request.isCanceled()) { 95 | request.finish("cache-discard-canceled"); 96 | continue; 97 | } 98 | 99 | // Attempt to retrieve this item from cache. 100 | Cache.Entry entry = mCache.get(request.getCacheKey()); 101 | if (entry == null) { 102 | request.addMarker("cache-miss"); 103 | // Cache miss; send off to the network dispatcher. 104 | mNetworkQueue.put(request); 105 | continue; 106 | } 107 | 108 | // If it is completely expired, just send it to the network. 109 | if (entry.isExpired()) { 110 | request.addMarker("cache-hit-expired"); 111 | request.setCacheEntry(entry); 112 | mNetworkQueue.put(request); 113 | continue; 114 | } 115 | 116 | // We have a cache hit; parse its data for delivery back to the request. 117 | request.addMarker("cache-hit"); 118 | Response response = request.parseNetworkResponse( 119 | new NetworkResponse(entry.data, entry.responseHeaders)); 120 | request.addMarker("cache-hit-parsed"); 121 | 122 | if (!entry.refreshNeeded()) { 123 | // Completely unexpired cache hit. Just deliver the response. 124 | mDelivery.postResponse(request, response); 125 | } else { 126 | // Soft-expired cache hit. We can deliver the cached response, 127 | // but we need to also send the request to the network for 128 | // refreshing. 129 | request.addMarker("cache-hit-refresh-needed"); 130 | request.setCacheEntry(entry); 131 | 132 | // Mark the response as intermediate. 133 | response.intermediate = true; 134 | 135 | // Post the intermediate response back to the user and have 136 | // the delivery then forward the request along to the network. 137 | mDelivery.postResponse(request, response, new Runnable() { 138 | @Override 139 | public void run() { 140 | try { 141 | mNetworkQueue.put(request); 142 | } catch (InterruptedException e) { 143 | // Not much we can do about this. 144 | } 145 | } 146 | }); 147 | } 148 | 149 | } catch (InterruptedException e) { 150 | // We may have been interrupted because it was time to quit. 151 | if (mQuit) { 152 | return; 153 | } 154 | continue; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/DefaultRetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Default retry policy for requests. 21 | */ 22 | public class DefaultRetryPolicy implements RetryPolicy { 23 | /** The current timeout in milliseconds. */ 24 | private int mCurrentTimeoutMs; 25 | 26 | /** The current retry count. */ 27 | private int mCurrentRetryCount; 28 | 29 | /** The maximum number of attempts. */ 30 | private final int mMaxNumRetries; 31 | 32 | /** The backoff multiplier for the policy. */ 33 | private final float mBackoffMultiplier; 34 | 35 | /** The default socket timeout in milliseconds */ 36 | public static final int DEFAULT_TIMEOUT_MS = 2500; 37 | 38 | /** The default number of retries */ 39 | public static final int DEFAULT_MAX_RETRIES = 1; 40 | 41 | /** The default backoff multiplier */ 42 | public static final float DEFAULT_BACKOFF_MULT = 1f; 43 | 44 | /** 45 | * Constructs a new retry policy using the default timeouts. 46 | */ 47 | public DefaultRetryPolicy() { 48 | this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); 49 | } 50 | 51 | /** 52 | * Constructs a new retry policy. 53 | * @param initialTimeoutMs The initial timeout for the policy. 54 | * @param maxNumRetries The maximum number of retries. 55 | * @param backoffMultiplier Backoff multiplier for the policy. 56 | */ 57 | public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { 58 | mCurrentTimeoutMs = initialTimeoutMs; 59 | mMaxNumRetries = maxNumRetries; 60 | mBackoffMultiplier = backoffMultiplier; 61 | } 62 | 63 | /** 64 | * Returns the current timeout. 65 | */ 66 | @Override 67 | public int getCurrentTimeout() { 68 | return mCurrentTimeoutMs; 69 | } 70 | 71 | /** 72 | * Returns the current retry count. 73 | */ 74 | @Override 75 | public int getCurrentRetryCount() { 76 | return mCurrentRetryCount; 77 | } 78 | 79 | /** 80 | * Returns the backoff multiplier for the policy. 81 | */ 82 | public float getBackoffMultiplier() { 83 | return mBackoffMultiplier; 84 | } 85 | 86 | /** 87 | * Prepares for the next retry by applying a backoff to the timeout. 88 | * @param error The error code of the last attempt. 89 | */ 90 | @Override 91 | public void retry(VolleyError error) throws VolleyError { 92 | mCurrentRetryCount++; 93 | mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); 94 | if (!hasAttemptRemaining()) { 95 | throw error; 96 | } 97 | } 98 | 99 | /** 100 | * Returns true if this policy has attempts remaining, false otherwise. 101 | */ 102 | protected boolean hasAttemptRemaining() { 103 | return mCurrentRetryCount <= mMaxNumRetries; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/ExecutorDelivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.os.Handler; 20 | 21 | import java.util.concurrent.Executor; 22 | 23 | /** 24 | * Delivers responses and errors. 25 | */ 26 | public class ExecutorDelivery implements ResponseDelivery { 27 | /** Used for posting responses, typically to the main thread. */ 28 | private final Executor mResponsePoster; 29 | 30 | /** 31 | * Creates a new response delivery interface. 32 | * @param handler {@link android.os.Handler} to post responses on 33 | */ 34 | public ExecutorDelivery(final Handler handler) { 35 | // Make an Executor that just wraps the handler. 36 | mResponsePoster = new Executor() { 37 | @Override 38 | public void execute(Runnable command) { 39 | handler.post(command); 40 | } 41 | }; 42 | } 43 | 44 | /** 45 | * Creates a new response delivery interface, mockable version 46 | * for testing. 47 | * @param executor For running delivery tasks 48 | */ 49 | public ExecutorDelivery(Executor executor) { 50 | mResponsePoster = executor; 51 | } 52 | 53 | @Override 54 | public void postResponse(Request request, Response response) { 55 | postResponse(request, response, null); 56 | } 57 | 58 | @Override 59 | public void postResponse(Request request, Response response, Runnable runnable) { 60 | request.markDelivered(); 61 | request.addMarker("post-response"); 62 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); 63 | } 64 | 65 | @Override 66 | public void postError(Request request, VolleyError error) { 67 | request.addMarker("post-error"); 68 | Response response = Response.error(error); 69 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); 70 | } 71 | 72 | /** 73 | * A Runnable used for delivering network responses to a listener on the 74 | * main thread. 75 | */ 76 | @SuppressWarnings("rawtypes") 77 | private class ResponseDeliveryRunnable implements Runnable { 78 | private final Request mRequest; 79 | private final Response mResponse; 80 | private final Runnable mRunnable; 81 | 82 | public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { 83 | mRequest = request; 84 | mResponse = response; 85 | mRunnable = runnable; 86 | } 87 | 88 | @SuppressWarnings("unchecked") 89 | @Override 90 | public void run() { 91 | // If this request has canceled, finish it and don't deliver. 92 | if (mRequest.isCanceled()) { 93 | mRequest.finish("canceled-at-delivery"); 94 | return; 95 | } 96 | 97 | // Deliver a normal response or error, depending. 98 | if (mResponse.isSuccess()) { 99 | mRequest.deliverResponse(mResponse.result); 100 | } else { 101 | mRequest.deliverError(mResponse.error); 102 | } 103 | 104 | // If this is an intermediate response, add a marker, otherwise we're done 105 | // and the request can be finished. 106 | if (mResponse.intermediate) { 107 | mRequest.addMarker("intermediate-response"); 108 | } else { 109 | mRequest.finish("done"); 110 | } 111 | 112 | // If we have been provided a post-delivery runnable, run it. 113 | if (mRunnable != null) { 114 | mRunnable.run(); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/Network.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * An interface for performing requests. 21 | */ 22 | public interface Network { 23 | /** 24 | * Performs the specified request. 25 | * @param request Request to process 26 | * @return A {@link NetworkResponse} with data and caching metadata; will never be null 27 | * @throws VolleyError on errors 28 | */ 29 | public NetworkResponse performRequest(Request request) throws VolleyError; 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/NetworkDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.annotation.TargetApi; 20 | import android.net.TrafficStats; 21 | import android.os.Build; 22 | import android.os.Process; 23 | import android.os.SystemClock; 24 | 25 | import java.util.concurrent.BlockingQueue; 26 | 27 | /** 28 | * Provides a thread for performing network dispatch from a queue of requests. 29 | * 30 | * Requests added to the specified queue are processed from the network via a 31 | * specified {@link com.android.volley.Network} interface. Responses are committed to cache, if 32 | * eligible, using a specified {@link com.android.volley.Cache} interface. Valid responses and 33 | * errors are posted back to the caller via a {@link com.android.volley.ResponseDelivery}. 34 | */ 35 | public class NetworkDispatcher extends Thread { 36 | /** The queue of requests to service. */ 37 | private final BlockingQueue> mQueue; 38 | /** The network interface for processing requests. */ 39 | private final Network mNetwork; 40 | /** The cache to write to. */ 41 | private final Cache mCache; 42 | /** For posting responses and errors. */ 43 | private final ResponseDelivery mDelivery; 44 | /** Used for telling us to die. */ 45 | private volatile boolean mQuit = false; 46 | 47 | /** 48 | * Creates a new network dispatcher thread. You must call {@link #start()} 49 | * in order to begin processing. 50 | * 51 | * @param queue Queue of incoming requests for triage 52 | * @param network Network interface to use for performing requests 53 | * @param cache Cache interface to use for writing responses to cache 54 | * @param delivery Delivery interface to use for posting responses 55 | */ 56 | public NetworkDispatcher(BlockingQueue> queue, 57 | Network network, Cache cache, 58 | ResponseDelivery delivery) { 59 | mQueue = queue; 60 | mNetwork = network; 61 | mCache = cache; 62 | mDelivery = delivery; 63 | } 64 | 65 | /** 66 | * Forces this dispatcher to quit immediately. If any requests are still in 67 | * the queue, they are not guaranteed to be processed. 68 | */ 69 | public void quit() { 70 | mQuit = true; 71 | interrupt(); 72 | } 73 | 74 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 75 | private void addTrafficStatsTag(Request request) { 76 | // Tag the request (if API >= 14) 77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 78 | TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); 79 | } 80 | } 81 | 82 | @Override 83 | public void run() { 84 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 85 | while (true) { 86 | long startTimeMs = SystemClock.elapsedRealtime(); 87 | Request request; 88 | try { 89 | // Take a request from the queue. 90 | request = mQueue.take(); 91 | } catch (InterruptedException e) { 92 | // We may have been interrupted because it was time to quit. 93 | if (mQuit) { 94 | return; 95 | } 96 | continue; 97 | } 98 | 99 | try { 100 | request.addMarker("network-queue-take"); 101 | 102 | // If the request was cancelled already, do not perform the 103 | // network request. 104 | if (request.isCanceled()) { 105 | request.finish("network-discard-cancelled"); 106 | continue; 107 | } 108 | 109 | addTrafficStatsTag(request); 110 | 111 | // Perform the network request. 112 | NetworkResponse networkResponse = mNetwork.performRequest(request); 113 | request.addMarker("network-http-complete"); 114 | 115 | // If the server returned 304 AND we delivered a response already, 116 | // we're done -- don't deliver a second identical response. 117 | if (networkResponse.notModified && request.hasHadResponseDelivered()) { 118 | request.finish("not-modified"); 119 | continue; 120 | } 121 | 122 | // Parse the response here on the worker thread. 123 | Response response = request.parseNetworkResponse(networkResponse); 124 | request.addMarker("network-parse-complete"); 125 | 126 | // Write to cache if applicable. 127 | // TODO: Only update cache metadata instead of entire record for 304s. 128 | if (request.shouldCache() && response.cacheEntry != null) { 129 | mCache.put(request.getCacheKey(), response.cacheEntry); 130 | request.addMarker("network-cache-written"); 131 | } 132 | 133 | // Post the response back. 134 | request.markDelivered(); 135 | mDelivery.postResponse(request, response); 136 | } catch (VolleyError volleyError) { 137 | volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); 138 | parseAndDeliverNetworkError(request, volleyError); 139 | } catch (Exception e) { 140 | VolleyLog.e(e, "Unhandled exception %s", e.toString()); 141 | VolleyError volleyError = new VolleyError(e); 142 | volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); 143 | mDelivery.postError(request, volleyError); 144 | } 145 | } 146 | } 147 | 148 | private void parseAndDeliverNetworkError(Request request, VolleyError error) { 149 | error = request.parseNetworkError(error); 150 | mDelivery.postError(request, error); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/NetworkError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.VolleyError; 21 | 22 | /** 23 | * Indicates that there was a network error when performing a Volley request. 24 | */ 25 | @SuppressWarnings("serial") 26 | public class NetworkError extends VolleyError { 27 | public NetworkError() { 28 | super(); 29 | } 30 | 31 | public NetworkError(Throwable cause) { 32 | super(cause); 33 | } 34 | 35 | public NetworkError(NetworkResponse networkResponse) { 36 | super(networkResponse); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/NetworkResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | 20 | import org.apache.http.HttpStatus; 21 | 22 | import java.util.Collections; 23 | import java.util.Map; 24 | 25 | /** 26 | * Data and headers returned from {@link com.android.volley.Network#performRequest(com.android.volley.Request)}. 27 | */ 28 | public class NetworkResponse { 29 | /** 30 | * Creates a new network response. 31 | * @param statusCode the HTTP status code 32 | * @param data Response body 33 | * @param headers Headers returned with this response, or null for none 34 | * @param notModified True if the server returned a 304 and the data was already in cache 35 | * @param networkTimeMs Round-trip network time to receive network response 36 | */ 37 | public NetworkResponse(int statusCode, byte[] data, Map headers, 38 | boolean notModified, long networkTimeMs) { 39 | this.statusCode = statusCode; 40 | this.data = data; 41 | this.headers = headers; 42 | this.notModified = notModified; 43 | this.networkTimeMs = networkTimeMs; 44 | } 45 | 46 | public NetworkResponse(int statusCode, byte[] data, Map headers, 47 | boolean notModified) { 48 | this(statusCode, data, headers, notModified, 0); 49 | } 50 | 51 | public NetworkResponse(byte[] data) { 52 | this(HttpStatus.SC_OK, data, Collections.emptyMap(), false, 0); 53 | } 54 | 55 | public NetworkResponse(byte[] data, Map headers) { 56 | this(HttpStatus.SC_OK, data, headers, false, 0); 57 | } 58 | 59 | /** The HTTP status code. */ 60 | public final int statusCode; 61 | 62 | /** Raw data from this response. */ 63 | public final byte[] data; 64 | 65 | /** Response headers. */ 66 | public final Map headers; 67 | 68 | /** True if the server returned a 304 (Not Modified). */ 69 | public final boolean notModified; 70 | 71 | /** Network roundtrip time in milliseconds. */ 72 | public final long networkTimeMs; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/NoConnectionError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Error indicating that no connection could be established when performing a Volley request. 21 | */ 22 | @SuppressWarnings("serial") 23 | public class NoConnectionError extends NetworkError { 24 | public NoConnectionError() { 25 | super(); 26 | } 27 | 28 | public NoConnectionError(Throwable reason) { 29 | super(reason); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/ParseError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.VolleyError; 21 | 22 | /** 23 | * Indicates that the server's response could not be parsed. 24 | */ 25 | @SuppressWarnings("serial") 26 | public class ParseError extends VolleyError { 27 | public ParseError() { } 28 | 29 | public ParseError(NetworkResponse networkResponse) { 30 | super(networkResponse); 31 | } 32 | 33 | public ParseError(Throwable cause) { 34 | super(cause); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/RequestQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.os.Handler; 20 | import android.os.Looper; 21 | 22 | import java.util.HashMap; 23 | import java.util.HashSet; 24 | import java.util.LinkedList; 25 | import java.util.Map; 26 | import java.util.Queue; 27 | import java.util.Set; 28 | import java.util.concurrent.PriorityBlockingQueue; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | 31 | /** 32 | * A request dispatch queue with a thread pool of dispatchers. 33 | * 34 | * Calling {@link #add(com.android.volley.Request)} will enqueue the given Request for dispatch, 35 | * resolving from either cache or network on a worker thread, and then delivering 36 | * a parsed response on the main thread. 37 | */ 38 | public class RequestQueue { 39 | 40 | /** Used for generating monotonically-increasing sequence numbers for requests. */ 41 | private AtomicInteger mSequenceGenerator = new AtomicInteger(); 42 | 43 | /** 44 | * Staging area for requests that already have a duplicate request in flight. 45 | * 46 | *
    47 | *
  • containsKey(cacheKey) indicates that there is a request in flight for the given cache 48 | * key.
  • 49 | *
  • get(cacheKey) returns waiting requests for the given cache key. The in flight request 50 | * is not contained in that list. Is null if no requests are staged.
  • 51 | *
52 | */ 53 | private final Map>> mWaitingRequests = 54 | new HashMap>>(); 55 | 56 | /** 57 | * The set of all requests currently being processed by this RequestQueue. A Request 58 | * will be in this set if it is waiting in any queue or currently being processed by 59 | * any dispatcher. 60 | */ 61 | private final Set> mCurrentRequests = new HashSet>(); 62 | 63 | /** The cache triage queue. */ 64 | private final PriorityBlockingQueue> mCacheQueue = 65 | new PriorityBlockingQueue>(); 66 | 67 | /** The queue of requests that are actually going out to the network. */ 68 | private final PriorityBlockingQueue> mNetworkQueue = 69 | new PriorityBlockingQueue>(); 70 | 71 | /** Number of network request dispatcher threads to start. */ 72 | private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; 73 | 74 | /** Cache interface for retrieving and storing responses. */ 75 | private final Cache mCache; 76 | 77 | /** Network interface for performing requests. */ 78 | private final Network mNetwork; 79 | 80 | /** Response delivery mechanism. */ 81 | private final ResponseDelivery mDelivery; 82 | 83 | /** The network dispatchers. */ 84 | private NetworkDispatcher[] mDispatchers; 85 | 86 | /** The cache dispatcher. */ 87 | private CacheDispatcher mCacheDispatcher; 88 | 89 | /** 90 | * Creates the worker pool. Processing will not begin until {@link #start()} is called. 91 | * 92 | * @param cache A Cache to use for persisting responses to disk 93 | * @param network A Network interface for performing HTTP requests 94 | * @param threadPoolSize Number of network dispatcher threads to create 95 | * @param delivery A ResponseDelivery interface for posting responses and errors 96 | */ 97 | public RequestQueue(Cache cache, Network network, int threadPoolSize, 98 | ResponseDelivery delivery) { 99 | mCache = cache; 100 | mNetwork = network; 101 | mDispatchers = new NetworkDispatcher[threadPoolSize]; 102 | mDelivery = delivery; 103 | } 104 | 105 | /** 106 | * Creates the worker pool. Processing will not begin until {@link #start()} is called. 107 | * 108 | * @param cache A Cache to use for persisting responses to disk 109 | * @param network A Network interface for performing HTTP requests 110 | * @param threadPoolSize Number of network dispatcher threads to create 111 | */ 112 | public RequestQueue(Cache cache, Network network, int threadPoolSize) { 113 | this(cache, network, threadPoolSize, 114 | new ExecutorDelivery(new Handler(Looper.getMainLooper()))); 115 | } 116 | 117 | /** 118 | * Creates the worker pool. Processing will not begin until {@link #start()} is called. 119 | * 120 | * @param cache A Cache to use for persisting responses to disk 121 | * @param network A Network interface for performing HTTP requests 122 | */ 123 | public RequestQueue(Cache cache, Network network) { 124 | this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); 125 | } 126 | 127 | /** 128 | * Starts the dispatchers in this queue. 129 | */ 130 | public void start() { 131 | stop(); // Make sure any currently running dispatchers are stopped. 132 | // Create the cache dispatcher and start it. 133 | mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); 134 | mCacheDispatcher.start(); 135 | 136 | // Create network dispatchers (and corresponding threads) up to the pool size. 137 | for (int i = 0; i < mDispatchers.length; i++) { 138 | NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, 139 | mCache, mDelivery); 140 | mDispatchers[i] = networkDispatcher; 141 | networkDispatcher.start(); 142 | } 143 | } 144 | 145 | /** 146 | * Stops the cache and network dispatchers. 147 | */ 148 | public void stop() { 149 | if (mCacheDispatcher != null) { 150 | mCacheDispatcher.quit(); 151 | } 152 | for (int i = 0; i < mDispatchers.length; i++) { 153 | if (mDispatchers[i] != null) { 154 | mDispatchers[i].quit(); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Gets a sequence number. 161 | */ 162 | public int getSequenceNumber() { 163 | return mSequenceGenerator.incrementAndGet(); 164 | } 165 | 166 | /** 167 | * Gets the {@link com.android.volley.Cache} instance being used. 168 | */ 169 | public Cache getCache() { 170 | return mCache; 171 | } 172 | 173 | /** 174 | * A simple predicate or filter interface for Requests, for use by 175 | * {@link com.android.volley.RequestQueue#cancelAll(com.android.volley.RequestQueue.RequestFilter)}. 176 | */ 177 | public interface RequestFilter { 178 | public boolean apply(Request request); 179 | } 180 | 181 | /** 182 | * Cancels all requests in this queue for which the given filter applies. 183 | * @param filter The filtering function to use 184 | */ 185 | public void cancelAll(RequestFilter filter) { 186 | synchronized (mCurrentRequests) { 187 | for (Request request : mCurrentRequests) { 188 | if (filter.apply(request)) { 189 | request.cancel(); 190 | } 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * Cancels all requests in this queue with the given tag. Tag must be non-null 197 | * and equality is by identity. 198 | */ 199 | public void cancelAll(final Object tag) { 200 | if (tag == null) { 201 | throw new IllegalArgumentException("Cannot cancelAll with a null tag"); 202 | } 203 | cancelAll(new RequestFilter() { 204 | @Override 205 | public boolean apply(Request request) { 206 | return request.getTag() == tag; 207 | } 208 | }); 209 | } 210 | 211 | /** 212 | * Adds a Request to the dispatch queue. 213 | * @param request The request to service 214 | * @return The passed-in request 215 | */ 216 | public Request add(Request request) { 217 | // Tag the request as belonging to this queue and add it to the set of current requests. 218 | request.setRequestQueue(this); 219 | synchronized (mCurrentRequests) { 220 | mCurrentRequests.add(request); 221 | } 222 | 223 | // Process requests in the order they are added. 224 | request.setSequence(getSequenceNumber()); 225 | request.addMarker("add-to-queue"); 226 | 227 | // If the request is uncacheable, skip the cache queue and go straight to the network. 228 | if (!request.shouldCache()) { 229 | mNetworkQueue.add(request); 230 | return request; 231 | } 232 | 233 | // Insert request into stage if there's already a request with the same cache key in flight. 234 | synchronized (mWaitingRequests) { 235 | String cacheKey = request.getCacheKey(); 236 | if (mWaitingRequests.containsKey(cacheKey)) { 237 | // There is already a request in flight. Queue up. 238 | Queue> stagedRequests = mWaitingRequests.get(cacheKey); 239 | if (stagedRequests == null) { 240 | stagedRequests = new LinkedList>(); 241 | } 242 | stagedRequests.add(request); 243 | mWaitingRequests.put(cacheKey, stagedRequests); 244 | if (VolleyLog.DEBUG) { 245 | VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); 246 | } 247 | } else { 248 | // Insert 'null' queue for this cacheKey, indicating there is now a request in 249 | // flight. 250 | mWaitingRequests.put(cacheKey, null); 251 | mCacheQueue.add(request); 252 | } 253 | return request; 254 | } 255 | } 256 | 257 | /** 258 | * Called from {@link com.android.volley.Request#finish(String)}, indicating that processing of the given request 259 | * has finished. 260 | * 261 | *

Releases waiting requests for request.getCacheKey() if 262 | * request.shouldCache().

263 | */ 264 | void finish(Request request) { 265 | // Remove from the set of requests currently being processed. 266 | synchronized (mCurrentRequests) { 267 | mCurrentRequests.remove(request); 268 | } 269 | 270 | if (request.shouldCache()) { 271 | synchronized (mWaitingRequests) { 272 | String cacheKey = request.getCacheKey(); 273 | Queue> waitingRequests = mWaitingRequests.remove(cacheKey); 274 | if (waitingRequests != null) { 275 | if (VolleyLog.DEBUG) { 276 | VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", 277 | waitingRequests.size(), cacheKey); 278 | } 279 | // Process all queued up requests. They won't be considered as in flight, but 280 | // that's not a problem as the cache has been primed by 'request'. 281 | mCacheQueue.addAll(waitingRequests); 282 | } 283 | } 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Encapsulates a parsed response for delivery. 21 | * 22 | * @param Parsed type of this response 23 | */ 24 | public class Response { 25 | 26 | /** Callback interface for delivering parsed responses. */ 27 | public interface Listener { 28 | /** Called when a response is received. */ 29 | public void onResponse(T response); 30 | } 31 | 32 | /** Callback interface for delivering error responses. */ 33 | public interface ErrorListener { 34 | /** 35 | * Callback method that an error has been occurred with the 36 | * provided error code and optional user-readable message. 37 | */ 38 | public void onErrorResponse(VolleyError error); 39 | } 40 | 41 | /** Returns a successful response containing the parsed result. */ 42 | public static Response success(T result, Cache.Entry cacheEntry) { 43 | return new Response(result, cacheEntry); 44 | } 45 | 46 | /** 47 | * Returns a failed response containing the given error code and an optional 48 | * localized message displayed to the user. 49 | */ 50 | public static Response error(VolleyError error) { 51 | return new Response(error); 52 | } 53 | 54 | /** Parsed response, or null in the case of error. */ 55 | public final T result; 56 | 57 | /** Cache metadata for this response, or null in the case of error. */ 58 | public final Cache.Entry cacheEntry; 59 | 60 | /** Detailed error information if errorCode != OK. */ 61 | public final VolleyError error; 62 | 63 | /** True if this response was a soft-expired one and a second one MAY be coming. */ 64 | public boolean intermediate = false; 65 | 66 | /** 67 | * Returns whether this response is considered successful. 68 | */ 69 | public boolean isSuccess() { 70 | return error == null; 71 | } 72 | 73 | 74 | private Response(T result, Cache.Entry cacheEntry) { 75 | this.result = result; 76 | this.cacheEntry = cacheEntry; 77 | this.error = null; 78 | } 79 | 80 | private Response(VolleyError error) { 81 | this.result = null; 82 | this.cacheEntry = null; 83 | this.error = error; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/ResponseDelivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | public interface ResponseDelivery { 20 | /** 21 | * Parses a response from the network or cache and delivers it. 22 | */ 23 | public void postResponse(Request request, Response response); 24 | 25 | /** 26 | * Parses a response from the network or cache and delivers it. The provided 27 | * Runnable will be executed after delivery. 28 | */ 29 | public void postResponse(Request request, Response response, Runnable runnable); 30 | 31 | /** 32 | * Posts an error for the given request. 33 | */ 34 | public void postError(Request request, VolleyError error); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/RetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Retry policy for a request. 21 | */ 22 | public interface RetryPolicy { 23 | 24 | /** 25 | * Returns the current timeout (used for logging). 26 | */ 27 | public int getCurrentTimeout(); 28 | 29 | /** 30 | * Returns the current retry count (used for logging). 31 | */ 32 | public int getCurrentRetryCount(); 33 | 34 | /** 35 | * Prepares for the next retry by applying a backoff to the timeout. 36 | * @param error The error code of the last attempt. 37 | * @throws VolleyError In the event that the retry could not be performed (for example if we 38 | * ran out of attempts), the passed in error is thrown. 39 | */ 40 | public void retry(VolleyError error) throws VolleyError; 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/ServerError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.VolleyError; 21 | 22 | /** 23 | * Indicates that the server responded with an error response. 24 | */ 25 | @SuppressWarnings("serial") 26 | public class ServerError extends VolleyError { 27 | public ServerError(NetworkResponse networkResponse) { 28 | super(networkResponse); 29 | } 30 | 31 | public ServerError() { 32 | super(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/TimeoutError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Indicates that the connection or the socket timed out. 21 | */ 22 | @SuppressWarnings("serial") 23 | public class TimeoutError extends VolleyError { } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/VolleyError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | /** 20 | * Exception style class encapsulating Volley errors 21 | */ 22 | @SuppressWarnings("serial") 23 | public class VolleyError extends Exception { 24 | public final NetworkResponse networkResponse; 25 | private long networkTimeMs; 26 | 27 | public VolleyError() { 28 | networkResponse = null; 29 | } 30 | 31 | public VolleyError(NetworkResponse response) { 32 | networkResponse = response; 33 | } 34 | 35 | public VolleyError(String exceptionMessage) { 36 | super(exceptionMessage); 37 | networkResponse = null; 38 | } 39 | 40 | public VolleyError(String exceptionMessage, Throwable reason) { 41 | super(exceptionMessage, reason); 42 | networkResponse = null; 43 | } 44 | 45 | public VolleyError(Throwable cause) { 46 | super(cause); 47 | networkResponse = null; 48 | } 49 | 50 | /* package */ void setNetworkTimeMs(long networkTimeMs) { 51 | this.networkTimeMs = networkTimeMs; 52 | } 53 | 54 | public long getNetworkTimeMs() { 55 | return networkTimeMs; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/VolleyLog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley; 18 | 19 | import android.os.SystemClock; 20 | import android.util.Log; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Locale; 25 | 26 | /** Logging helper class. */ 27 | public class VolleyLog { 28 | public static String TAG = "Volley"; 29 | 30 | public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); 31 | 32 | /** 33 | * Customize the log tag for your application, so that other apps 34 | * using Volley don't mix their logs with yours. 35 | *
36 | * Enable the log property for your tag before starting your app: 37 | *
38 | * {@code adb shell setprop log.tag.<tag>} 39 | */ 40 | public static void setTag(String tag) { 41 | d("Changing log tag to %s", tag); 42 | TAG = tag; 43 | 44 | // Reinitialize the DEBUG "constant" 45 | DEBUG = Log.isLoggable(TAG, Log.VERBOSE); 46 | } 47 | 48 | public static void v(String format, Object... args) { 49 | if (DEBUG) { 50 | Log.v(TAG, buildMessage(format, args)); 51 | } 52 | } 53 | 54 | public static void d(String format, Object... args) { 55 | Log.d(TAG, buildMessage(format, args)); 56 | } 57 | 58 | public static void e(String format, Object... args) { 59 | Log.e(TAG, buildMessage(format, args)); 60 | } 61 | 62 | public static void e(Throwable tr, String format, Object... args) { 63 | Log.e(TAG, buildMessage(format, args), tr); 64 | } 65 | 66 | public static void wtf(String format, Object... args) { 67 | Log.wtf(TAG, buildMessage(format, args)); 68 | } 69 | 70 | public static void wtf(Throwable tr, String format, Object... args) { 71 | Log.wtf(TAG, buildMessage(format, args), tr); 72 | } 73 | 74 | /** 75 | * Formats the caller's provided message and prepends useful info like 76 | * calling thread ID and method name. 77 | */ 78 | private static String buildMessage(String format, Object... args) { 79 | String msg = (args == null) ? format : String.format(Locale.US, format, args); 80 | StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace(); 81 | 82 | String caller = ""; 83 | // Walk up the stack looking for the first caller outside of VolleyLog. 84 | // It will be at least two frames up, so start there. 85 | for (int i = 2; i < trace.length; i++) { 86 | Class clazz = trace[i].getClass(); 87 | if (!clazz.equals(VolleyLog.class)) { 88 | String callingClass = trace[i].getClassName(); 89 | callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1); 90 | callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1); 91 | 92 | caller = callingClass + "." + trace[i].getMethodName(); 93 | break; 94 | } 95 | } 96 | return String.format(Locale.US, "[%d] %s: %s", 97 | Thread.currentThread().getId(), caller, msg); 98 | } 99 | 100 | /** 101 | * A simple event log with records containing a name, thread ID, and timestamp. 102 | */ 103 | static class MarkerLog { 104 | public static final boolean ENABLED = VolleyLog.DEBUG; 105 | 106 | /** Minimum duration from first marker to last in an marker log to warrant logging. */ 107 | private static final long MIN_DURATION_FOR_LOGGING_MS = 0; 108 | 109 | private static class Marker { 110 | public final String name; 111 | public final long thread; 112 | public final long time; 113 | 114 | public Marker(String name, long thread, long time) { 115 | this.name = name; 116 | this.thread = thread; 117 | this.time = time; 118 | } 119 | } 120 | 121 | private final List mMarkers = new ArrayList(); 122 | private boolean mFinished = false; 123 | 124 | /** Adds a marker to this log with the specified name. */ 125 | public synchronized void add(String name, long threadId) { 126 | if (mFinished) { 127 | throw new IllegalStateException("Marker added to finished log"); 128 | } 129 | 130 | mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime())); 131 | } 132 | 133 | /** 134 | * Closes the log, dumping it to logcat if the time difference between 135 | * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}. 136 | * @param header Header string to print above the marker log. 137 | */ 138 | public synchronized void finish(String header) { 139 | mFinished = true; 140 | 141 | long duration = getTotalDuration(); 142 | if (duration <= MIN_DURATION_FOR_LOGGING_MS) { 143 | return; 144 | } 145 | 146 | long prevTime = mMarkers.get(0).time; 147 | d("(%-4d ms) %s", duration, header); 148 | for (Marker marker : mMarkers) { 149 | long thisTime = marker.time; 150 | d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name); 151 | prevTime = thisTime; 152 | } 153 | } 154 | 155 | @Override 156 | protected void finalize() throws Throwable { 157 | // Catch requests that have been collected (and hence end-of-lifed) 158 | // but had no debugging output printed for them. 159 | if (!mFinished) { 160 | finish("Request on the loose"); 161 | e("Marker log finalized without finish() - uncaught exit point for request"); 162 | } 163 | } 164 | 165 | /** Returns the time difference between the first and last events in this log. */ 166 | private long getTotalDuration() { 167 | if (mMarkers.size() == 0) { 168 | return 0; 169 | } 170 | 171 | long first = mMarkers.get(0).time; 172 | long last = mMarkers.get(mMarkers.size() - 1).time; 173 | return last - first; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/AndroidAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.AuthFailureError; 20 | 21 | import android.accounts.Account; 22 | import android.accounts.AccountManager; 23 | import android.accounts.AccountManagerFuture; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.os.Bundle; 27 | 28 | /** 29 | * An Authenticator that uses {@link android.accounts.AccountManager} to get auth 30 | * tokens of a specified type for a specified account. 31 | */ 32 | public class AndroidAuthenticator implements Authenticator { 33 | private final Context mContext; 34 | private final Account mAccount; 35 | private final String mAuthTokenType; 36 | private final boolean mNotifyAuthFailure; 37 | 38 | /** 39 | * Creates a new authenticator. 40 | * @param context Context for accessing AccountManager 41 | * @param account Account to authenticate as 42 | * @param authTokenType Auth token type passed to AccountManager 43 | */ 44 | public AndroidAuthenticator(Context context, Account account, String authTokenType) { 45 | this(context, account, authTokenType, false); 46 | } 47 | 48 | /** 49 | * Creates a new authenticator. 50 | * @param context Context for accessing AccountManager 51 | * @param account Account to authenticate as 52 | * @param authTokenType Auth token type passed to AccountManager 53 | * @param notifyAuthFailure Whether to raise a notification upon auth failure 54 | */ 55 | public AndroidAuthenticator(Context context, Account account, String authTokenType, 56 | boolean notifyAuthFailure) { 57 | mContext = context; 58 | mAccount = account; 59 | mAuthTokenType = authTokenType; 60 | mNotifyAuthFailure = notifyAuthFailure; 61 | } 62 | 63 | /** 64 | * Returns the Account being used by this authenticator. 65 | */ 66 | public Account getAccount() { 67 | return mAccount; 68 | } 69 | 70 | // TODO: Figure out what to do about notifyAuthFailure 71 | @SuppressWarnings("deprecation") 72 | @Override 73 | public String getAuthToken() throws AuthFailureError { 74 | final AccountManager accountManager = AccountManager.get(mContext); 75 | AccountManagerFuture future = accountManager.getAuthToken(mAccount, 76 | mAuthTokenType, mNotifyAuthFailure, null, null); 77 | Bundle result; 78 | try { 79 | result = future.getResult(); 80 | } catch (Exception e) { 81 | throw new AuthFailureError("Error while retrieving auth token", e); 82 | } 83 | String authToken = null; 84 | if (future.isDone() && !future.isCancelled()) { 85 | if (result.containsKey(AccountManager.KEY_INTENT)) { 86 | Intent intent = result.getParcelable(AccountManager.KEY_INTENT); 87 | throw new AuthFailureError(intent); 88 | } 89 | authToken = result.getString(AccountManager.KEY_AUTHTOKEN); 90 | } 91 | if (authToken == null) { 92 | throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType); 93 | } 94 | 95 | return authToken; 96 | } 97 | 98 | @Override 99 | public void invalidateAuthToken(String authToken) { 100 | AccountManager.get(mContext).invalidateAuthToken(mAccount.type, authToken); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/Authenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.AuthFailureError; 20 | 21 | /** 22 | * An interface for interacting with auth tokens. 23 | */ 24 | public interface Authenticator { 25 | /** 26 | * Synchronously retrieves an auth token. 27 | * 28 | * @throws AuthFailureError If authentication did not succeed 29 | */ 30 | public String getAuthToken() throws AuthFailureError; 31 | 32 | /** 33 | * Invalidates the provided auth token. 34 | */ 35 | public void invalidateAuthToken(String authToken); 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/ByteArrayPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | 25 | /** 26 | * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to 27 | * supply those buffers to consumers who need to use them for a short period of time and then 28 | * dispose of them. Simply creating and disposing such buffers in the conventional manner can 29 | * considerable heap churn and garbage collection delays on Android, which lacks good management of 30 | * short-lived heap objects. It may be advantageous to trade off some memory in the form of a 31 | * permanently allocated pool of buffers in order to gain heap performance improvements; that is 32 | * what this class does. 33 | *

34 | * A good candidate user for this class is something like an I/O system that uses large temporary 35 | * byte[] buffers to copy data around. In these use cases, often the consumer wants 36 | * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks 37 | * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into 38 | * account and also to maximize the odds of being able to reuse a recycled buffer, this class is 39 | * free to return buffers larger than the requested size. The caller needs to be able to gracefully 40 | * deal with getting buffers any size over the minimum. 41 | *

42 | * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this 43 | * class will allocate a new buffer and return it. 44 | *

45 | * This class has no special ownership of buffers it creates; the caller is free to take a buffer 46 | * it receives from this pool, use it permanently, and never return it to the pool; additionally, 47 | * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there 48 | * are no other lingering references to it. 49 | *

50 | * This class ensures that the total size of the buffers in its recycling pool never exceeds a 51 | * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit, 52 | * least-recently-used buffers are disposed. 53 | */ 54 | public class ByteArrayPool { 55 | /** The buffer pool, arranged both by last use and by buffer size */ 56 | private List mBuffersByLastUse = new LinkedList(); 57 | private List mBuffersBySize = new ArrayList(64); 58 | 59 | /** The total size of the buffers in the pool */ 60 | private int mCurrentSize = 0; 61 | 62 | /** 63 | * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay 64 | * under this limit. 65 | */ 66 | private final int mSizeLimit; 67 | 68 | /** Compares buffers by size */ 69 | protected static final Comparator BUF_COMPARATOR = new Comparator() { 70 | @Override 71 | public int compare(byte[] lhs, byte[] rhs) { 72 | return lhs.length - rhs.length; 73 | } 74 | }; 75 | 76 | /** 77 | * @param sizeLimit the maximum size of the pool, in bytes 78 | */ 79 | public ByteArrayPool(int sizeLimit) { 80 | mSizeLimit = sizeLimit; 81 | } 82 | 83 | /** 84 | * Returns a buffer from the pool if one is available in the requested size, or allocates a new 85 | * one if a pooled one is not available. 86 | * 87 | * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be 88 | * larger. 89 | * @return a byte[] buffer is always returned. 90 | */ 91 | public synchronized byte[] getBuf(int len) { 92 | for (int i = 0; i < mBuffersBySize.size(); i++) { 93 | byte[] buf = mBuffersBySize.get(i); 94 | if (buf.length >= len) { 95 | mCurrentSize -= buf.length; 96 | mBuffersBySize.remove(i); 97 | mBuffersByLastUse.remove(buf); 98 | return buf; 99 | } 100 | } 101 | return new byte[len]; 102 | } 103 | 104 | /** 105 | * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted 106 | * size. 107 | * 108 | * @param buf the buffer to return to the pool. 109 | */ 110 | public synchronized void returnBuf(byte[] buf) { 111 | if (buf == null || buf.length > mSizeLimit) { 112 | return; 113 | } 114 | mBuffersByLastUse.add(buf); 115 | int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); 116 | if (pos < 0) { 117 | pos = -pos - 1; 118 | } 119 | mBuffersBySize.add(pos, buf); 120 | mCurrentSize += buf.length; 121 | trim(); 122 | } 123 | 124 | /** 125 | * Removes buffers from the pool until it is under its size limit. 126 | */ 127 | private synchronized void trim() { 128 | while (mCurrentSize > mSizeLimit) { 129 | byte[] buf = mBuffersByLastUse.remove(0); 130 | mBuffersBySize.remove(buf); 131 | mCurrentSize -= buf.length; 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/ClearCacheRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.Cache; 20 | import com.android.volley.NetworkResponse; 21 | import com.android.volley.Request; 22 | import com.android.volley.Response; 23 | 24 | import android.os.Handler; 25 | import android.os.Looper; 26 | 27 | /** 28 | * A synthetic request used for clearing the cache. 29 | */ 30 | public class ClearCacheRequest extends Request { 31 | private final Cache mCache; 32 | private final Runnable mCallback; 33 | 34 | /** 35 | * Creates a synthetic request for clearing the cache. 36 | * @param cache Cache to clear 37 | * @param callback Callback to make on the main thread once the cache is clear, 38 | * or null for none 39 | */ 40 | public ClearCacheRequest(Cache cache, Runnable callback) { 41 | super(Method.GET, null, null); 42 | mCache = cache; 43 | mCallback = callback; 44 | } 45 | 46 | @Override 47 | public boolean isCanceled() { 48 | // This is a little bit of a hack, but hey, why not. 49 | mCache.clear(); 50 | if (mCallback != null) { 51 | Handler handler = new Handler(Looper.getMainLooper()); 52 | handler.postAtFrontOfQueue(mCallback); 53 | } 54 | return true; 55 | } 56 | 57 | @Override 58 | public Priority getPriority() { 59 | return Priority.IMMEDIATE; 60 | } 61 | 62 | @Override 63 | protected Response parseNetworkResponse(NetworkResponse response) { 64 | return null; 65 | } 66 | 67 | @Override 68 | protected void deliverResponse(Object response) { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/HttpClientStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.AuthFailureError; 20 | import com.android.volley.Request; 21 | import com.android.volley.Request.Method; 22 | 23 | import org.apache.http.HttpEntity; 24 | import org.apache.http.HttpResponse; 25 | import org.apache.http.NameValuePair; 26 | import org.apache.http.client.HttpClient; 27 | import org.apache.http.client.methods.HttpDelete; 28 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 29 | import org.apache.http.client.methods.HttpGet; 30 | import org.apache.http.client.methods.HttpHead; 31 | import org.apache.http.client.methods.HttpOptions; 32 | import org.apache.http.client.methods.HttpPost; 33 | import org.apache.http.client.methods.HttpPut; 34 | import org.apache.http.client.methods.HttpTrace; 35 | import org.apache.http.client.methods.HttpUriRequest; 36 | import org.apache.http.entity.ByteArrayEntity; 37 | import org.apache.http.message.BasicNameValuePair; 38 | import org.apache.http.params.HttpConnectionParams; 39 | import org.apache.http.params.HttpParams; 40 | 41 | import java.io.IOException; 42 | import java.net.URI; 43 | import java.util.ArrayList; 44 | import java.util.List; 45 | import java.util.Map; 46 | 47 | /** 48 | * An HttpStack that performs request over an {@link org.apache.http.client.HttpClient}. 49 | */ 50 | public class HttpClientStack implements HttpStack { 51 | protected final HttpClient mClient; 52 | 53 | private final static String HEADER_CONTENT_TYPE = "Content-Type"; 54 | 55 | public HttpClientStack(HttpClient client) { 56 | mClient = client; 57 | } 58 | 59 | private static void addHeaders(HttpUriRequest httpRequest, Map headers) { 60 | for (String key : headers.keySet()) { 61 | httpRequest.setHeader(key, headers.get(key)); 62 | } 63 | } 64 | 65 | @SuppressWarnings("unused") 66 | private static List getPostParameterPairs(Map postParams) { 67 | List result = new ArrayList(postParams.size()); 68 | for (String key : postParams.keySet()) { 69 | result.add(new BasicNameValuePair(key, postParams.get(key))); 70 | } 71 | return result; 72 | } 73 | 74 | @Override 75 | public HttpResponse performRequest(Request request, Map additionalHeaders) 76 | throws IOException, AuthFailureError { 77 | HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); 78 | addHeaders(httpRequest, additionalHeaders); 79 | addHeaders(httpRequest, request.getHeaders()); 80 | onPrepareRequest(httpRequest); 81 | HttpParams httpParams = httpRequest.getParams(); 82 | int timeoutMs = request.getTimeoutMs(); 83 | // TODO: Reevaluate this connection timeout based on more wide-scale 84 | // data collection and possibly different for wifi vs. 3G. 85 | HttpConnectionParams.setConnectionTimeout(httpParams, 5000); 86 | HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); 87 | return mClient.execute(httpRequest); 88 | } 89 | 90 | /** 91 | * Creates the appropriate subclass of HttpUriRequest for passed in request. 92 | */ 93 | @SuppressWarnings("deprecation") 94 | /* protected */ static HttpUriRequest createHttpRequest(Request request, 95 | Map additionalHeaders) throws AuthFailureError { 96 | switch (request.getMethod()) { 97 | case Method.DEPRECATED_GET_OR_POST: { 98 | // This is the deprecated way that needs to be handled for backwards compatibility. 99 | // If the request's post body is null, then the assumption is that the request is 100 | // GET. Otherwise, it is assumed that the request is a POST. 101 | byte[] postBody = request.getPostBody(); 102 | if (postBody != null) { 103 | HttpPost postRequest = new HttpPost(request.getUrl()); 104 | postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); 105 | HttpEntity entity; 106 | entity = new ByteArrayEntity(postBody); 107 | postRequest.setEntity(entity); 108 | return postRequest; 109 | } else { 110 | return new HttpGet(request.getUrl()); 111 | } 112 | } 113 | case Method.GET: 114 | return new HttpGet(request.getUrl()); 115 | case Method.DELETE: 116 | return new HttpDelete(request.getUrl()); 117 | case Method.POST: { 118 | HttpPost postRequest = new HttpPost(request.getUrl()); 119 | postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); 120 | setEntityIfNonEmptyBody(postRequest, request); 121 | return postRequest; 122 | } 123 | case Method.PUT: { 124 | HttpPut putRequest = new HttpPut(request.getUrl()); 125 | putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); 126 | setEntityIfNonEmptyBody(putRequest, request); 127 | return putRequest; 128 | } 129 | case Method.HEAD: 130 | return new HttpHead(request.getUrl()); 131 | case Method.OPTIONS: 132 | return new HttpOptions(request.getUrl()); 133 | case Method.TRACE: 134 | return new HttpTrace(request.getUrl()); 135 | case Method.PATCH: { 136 | HttpPatch patchRequest = new HttpPatch(request.getUrl()); 137 | patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); 138 | setEntityIfNonEmptyBody(patchRequest, request); 139 | return patchRequest; 140 | } 141 | default: 142 | throw new IllegalStateException("Unknown request method."); 143 | } 144 | } 145 | 146 | private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, 147 | Request request) throws AuthFailureError { 148 | byte[] body = request.getBody(); 149 | if (body != null) { 150 | HttpEntity entity = new ByteArrayEntity(body); 151 | httpRequest.setEntity(entity); 152 | } 153 | } 154 | 155 | /** 156 | * Called before the request is executed using the underlying HttpClient. 157 | * 158 | *

Overwrite in subclasses to augment the request.

159 | */ 160 | protected void onPrepareRequest(HttpUriRequest request) throws IOException { 161 | // Nothing. 162 | } 163 | 164 | /** 165 | * The HttpPatch class does not exist in the Android framework, so this has been defined here. 166 | */ 167 | public static final class HttpPatch extends HttpEntityEnclosingRequestBase { 168 | 169 | public final static String METHOD_NAME = "PATCH"; 170 | 171 | public HttpPatch() { 172 | super(); 173 | } 174 | 175 | public HttpPatch(final URI uri) { 176 | super(); 177 | setURI(uri); 178 | } 179 | 180 | /** 181 | * @throws IllegalArgumentException if the uri is invalid. 182 | */ 183 | public HttpPatch(final String uri) { 184 | super(); 185 | setURI(URI.create(uri)); 186 | } 187 | 188 | @Override 189 | public String getMethod() { 190 | return METHOD_NAME; 191 | } 192 | 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.Cache; 20 | import com.android.volley.NetworkResponse; 21 | 22 | import org.apache.http.impl.cookie.DateParseException; 23 | import org.apache.http.impl.cookie.DateUtils; 24 | import org.apache.http.protocol.HTTP; 25 | 26 | import java.util.Map; 27 | 28 | /** 29 | * Utility methods for parsing HTTP headers. 30 | */ 31 | public class HttpHeaderParser { 32 | 33 | /** 34 | * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. 35 | * 36 | * @param response The network response to parse headers from 37 | * @return a cache entry for the given response, or null if the response is not cacheable. 38 | */ 39 | public static Cache.Entry parseCacheHeaders(NetworkResponse response) { 40 | long now = System.currentTimeMillis(); 41 | 42 | Map headers = response.headers; 43 | 44 | long serverDate = 0; 45 | long serverExpires = 0; 46 | long softExpire = 0; 47 | long maxAge = 0; 48 | boolean hasCacheControl = false; 49 | 50 | String serverEtag = null; 51 | String headerValue; 52 | 53 | headerValue = headers.get("Date"); 54 | if (headerValue != null) { 55 | serverDate = parseDateAsEpoch(headerValue); 56 | } 57 | 58 | headerValue = headers.get("Cache-Control"); 59 | if (headerValue != null) { 60 | hasCacheControl = true; 61 | String[] tokens = headerValue.split(","); 62 | for (int i = 0; i < tokens.length; i++) { 63 | String token = tokens[i].trim(); 64 | if (token.equals("no-cache") || token.equals("no-store")) { 65 | return null; 66 | } else if (token.startsWith("max-age=")) { 67 | try { 68 | maxAge = Long.parseLong(token.substring(8)); 69 | } catch (Exception e) { 70 | } 71 | } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { 72 | maxAge = 0; 73 | } 74 | } 75 | } 76 | 77 | headerValue = headers.get("Expires"); 78 | if (headerValue != null) { 79 | serverExpires = parseDateAsEpoch(headerValue); 80 | } 81 | 82 | serverEtag = headers.get("ETag"); 83 | 84 | // Cache-Control takes precedence over an Expires header, even if both exist and Expires 85 | // is more restrictive. 86 | if (hasCacheControl) { 87 | softExpire = now + maxAge * 1000; 88 | } else if (serverDate > 0 && serverExpires >= serverDate) { 89 | // Default semantic for Expire header in HTTP specification is softExpire. 90 | softExpire = now + (serverExpires - serverDate); 91 | } 92 | 93 | Cache.Entry entry = new Cache.Entry(); 94 | entry.data = response.data; 95 | entry.etag = serverEtag; 96 | entry.softTtl = softExpire; 97 | entry.ttl = entry.softTtl; 98 | entry.serverDate = serverDate; 99 | entry.responseHeaders = headers; 100 | 101 | return entry; 102 | } 103 | 104 | /** 105 | * Parse date in RFC1123 format, and return its value as epoch 106 | */ 107 | public static long parseDateAsEpoch(String dateStr) { 108 | try { 109 | // Parse date in RFC1123 format if this header contains one 110 | return DateUtils.parseDate(dateStr).getTime(); 111 | } catch (DateParseException e) { 112 | // Date in invalid format, fallback to 0 113 | return 0; 114 | } 115 | } 116 | 117 | /** 118 | * Returns the charset specified in the Content-Type of this header, 119 | * or the HTTP default (ISO-8859-1) if none can be found. 120 | */ 121 | public static String parseCharset(Map headers) { 122 | String contentType = headers.get(HTTP.CONTENT_TYPE); 123 | if (contentType != null) { 124 | String[] params = contentType.split(";"); 125 | for (int i = 1; i < params.length; i++) { 126 | String[] pair = params[i].trim().split("="); 127 | if (pair.length == 2) { 128 | if (pair[0].equals("charset")) { 129 | return pair[1]; 130 | } 131 | } 132 | } 133 | } 134 | 135 | return HTTP.DEFAULT_CONTENT_CHARSET; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/HttpStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.AuthFailureError; 20 | import com.android.volley.Request; 21 | 22 | import org.apache.http.HttpResponse; 23 | 24 | import java.io.IOException; 25 | import java.util.Map; 26 | 27 | /** 28 | * An HTTP stack abstraction. 29 | */ 30 | public interface HttpStack { 31 | /** 32 | * Performs an HTTP request with the given parameters. 33 | * 34 | *

A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, 35 | * and the Content-Type header is set to request.getPostBodyContentType().

36 | * 37 | * @param request the request to perform 38 | * @param additionalHeaders additional headers to be sent together with 39 | * {@link Request#getHeaders()} 40 | * @return the HTTP response 41 | */ 42 | public HttpResponse performRequest(Request request, Map additionalHeaders) 43 | throws IOException, AuthFailureError; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/HurlStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.AuthFailureError; 20 | import com.android.volley.Request; 21 | import com.android.volley.Request.Method; 22 | 23 | import org.apache.http.Header; 24 | import org.apache.http.HttpEntity; 25 | import org.apache.http.HttpResponse; 26 | import org.apache.http.ProtocolVersion; 27 | import org.apache.http.StatusLine; 28 | import org.apache.http.entity.BasicHttpEntity; 29 | import org.apache.http.message.BasicHeader; 30 | import org.apache.http.message.BasicHttpResponse; 31 | import org.apache.http.message.BasicStatusLine; 32 | 33 | import java.io.DataOutputStream; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.net.HttpURLConnection; 37 | import java.net.URL; 38 | import java.util.HashMap; 39 | import java.util.List; 40 | import java.util.Map; 41 | import java.util.Map.Entry; 42 | 43 | import javax.net.ssl.HttpsURLConnection; 44 | import javax.net.ssl.SSLSocketFactory; 45 | 46 | /** 47 | * An {@link com.android.volley.toolbox.HttpStack} based on {@link java.net.HttpURLConnection}. 48 | */ 49 | public class HurlStack implements HttpStack { 50 | 51 | private static final String HEADER_CONTENT_TYPE = "Content-Type"; 52 | 53 | /** 54 | * An interface for transforming URLs before use. 55 | */ 56 | public interface UrlRewriter { 57 | /** 58 | * Returns a URL to use instead of the provided one, or null to indicate 59 | * this URL should not be used at all. 60 | */ 61 | public String rewriteUrl(String originalUrl); 62 | } 63 | 64 | private final UrlRewriter mUrlRewriter; 65 | private final SSLSocketFactory mSslSocketFactory; 66 | 67 | public HurlStack() { 68 | this(null); 69 | } 70 | 71 | /** 72 | * @param urlRewriter Rewriter to use for request URLs 73 | */ 74 | public HurlStack(UrlRewriter urlRewriter) { 75 | this(urlRewriter, null); 76 | } 77 | 78 | /** 79 | * @param urlRewriter Rewriter to use for request URLs 80 | * @param sslSocketFactory SSL factory to use for HTTPS connections 81 | */ 82 | public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { 83 | mUrlRewriter = urlRewriter; 84 | mSslSocketFactory = sslSocketFactory; 85 | } 86 | 87 | @Override 88 | public HttpResponse performRequest(Request request, Map additionalHeaders) 89 | throws IOException, AuthFailureError { 90 | String url = request.getUrl(); 91 | HashMap map = new HashMap(); 92 | map.putAll(request.getHeaders()); 93 | map.putAll(additionalHeaders); 94 | if (mUrlRewriter != null) { 95 | String rewritten = mUrlRewriter.rewriteUrl(url); 96 | if (rewritten == null) { 97 | throw new IOException("URL blocked by rewriter: " + url); 98 | } 99 | url = rewritten; 100 | } 101 | URL parsedUrl = new URL(url); 102 | HttpURLConnection connection = openConnection(parsedUrl, request); 103 | for (String headerName : map.keySet()) { 104 | connection.addRequestProperty(headerName, map.get(headerName)); 105 | } 106 | setConnectionParametersForRequest(connection, request); 107 | // Initialize HttpResponse with data from the HttpURLConnection. 108 | ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); 109 | int responseCode = connection.getResponseCode(); 110 | if (responseCode == -1) { 111 | // -1 is returned by getResponseCode() if the response code could not be retrieved. 112 | // Signal to the caller that something was wrong with the connection. 113 | throw new IOException("Could not retrieve response code from HttpUrlConnection."); 114 | } 115 | StatusLine responseStatus = new BasicStatusLine(protocolVersion, 116 | connection.getResponseCode(), connection.getResponseMessage()); 117 | BasicHttpResponse response = new BasicHttpResponse(responseStatus); 118 | response.setEntity(entityFromConnection(connection)); 119 | for (Entry> header : connection.getHeaderFields().entrySet()) { 120 | if (header.getKey() != null) { 121 | Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); 122 | response.addHeader(h); 123 | } 124 | } 125 | return response; 126 | } 127 | 128 | /** 129 | * Initializes an {@link org.apache.http.HttpEntity} from the given {@link java.net.HttpURLConnection}. 130 | * @param connection 131 | * @return an HttpEntity populated with data from connection. 132 | */ 133 | private static HttpEntity entityFromConnection(HttpURLConnection connection) { 134 | BasicHttpEntity entity = new BasicHttpEntity(); 135 | InputStream inputStream; 136 | try { 137 | inputStream = connection.getInputStream(); 138 | } catch (IOException ioe) { 139 | inputStream = connection.getErrorStream(); 140 | } 141 | entity.setContent(inputStream); 142 | entity.setContentLength(connection.getContentLength()); 143 | entity.setContentEncoding(connection.getContentEncoding()); 144 | entity.setContentType(connection.getContentType()); 145 | return entity; 146 | } 147 | 148 | /** 149 | * Create an {@link java.net.HttpURLConnection} for the specified {@code url}. 150 | */ 151 | protected HttpURLConnection createConnection(URL url) throws IOException { 152 | return (HttpURLConnection) url.openConnection(); 153 | } 154 | 155 | /** 156 | * Opens an {@link java.net.HttpURLConnection} with parameters. 157 | * @param url 158 | * @return an open connection 159 | * @throws java.io.IOException 160 | */ 161 | private HttpURLConnection openConnection(URL url, Request request) throws IOException { 162 | HttpURLConnection connection = createConnection(url); 163 | 164 | int timeoutMs = request.getTimeoutMs(); 165 | connection.setConnectTimeout(timeoutMs); 166 | connection.setReadTimeout(timeoutMs); 167 | connection.setUseCaches(false); 168 | connection.setDoInput(true); 169 | 170 | // use caller-provided custom SslSocketFactory, if any, for HTTPS 171 | if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { 172 | ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); 173 | } 174 | 175 | return connection; 176 | } 177 | 178 | @SuppressWarnings("deprecation") 179 | /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, 180 | Request request) throws IOException, AuthFailureError { 181 | switch (request.getMethod()) { 182 | case Method.DEPRECATED_GET_OR_POST: 183 | // This is the deprecated way that needs to be handled for backwards compatibility. 184 | // If the request's post body is null, then the assumption is that the request is 185 | // GET. Otherwise, it is assumed that the request is a POST. 186 | byte[] postBody = request.getPostBody(); 187 | if (postBody != null) { 188 | // Prepare output. There is no need to set Content-Length explicitly, 189 | // since this is handled by HttpURLConnection using the size of the prepared 190 | // output stream. 191 | connection.setDoOutput(true); 192 | connection.setRequestMethod("POST"); 193 | connection.addRequestProperty(HEADER_CONTENT_TYPE, 194 | request.getPostBodyContentType()); 195 | DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 196 | out.write(postBody); 197 | out.close(); 198 | } 199 | break; 200 | case Method.GET: 201 | // Not necessary to set the request method because connection defaults to GET but 202 | // being explicit here. 203 | connection.setRequestMethod("GET"); 204 | break; 205 | case Method.DELETE: 206 | connection.setRequestMethod("DELETE"); 207 | break; 208 | case Method.POST: 209 | connection.setRequestMethod("POST"); 210 | addBodyIfExists(connection, request); 211 | break; 212 | case Method.PUT: 213 | connection.setRequestMethod("PUT"); 214 | addBodyIfExists(connection, request); 215 | break; 216 | case Method.HEAD: 217 | connection.setRequestMethod("HEAD"); 218 | break; 219 | case Method.OPTIONS: 220 | connection.setRequestMethod("OPTIONS"); 221 | break; 222 | case Method.TRACE: 223 | connection.setRequestMethod("TRACE"); 224 | break; 225 | case Method.PATCH: 226 | connection.setRequestMethod("PATCH"); 227 | addBodyIfExists(connection, request); 228 | break; 229 | default: 230 | throw new IllegalStateException("Unknown method type."); 231 | } 232 | } 233 | 234 | private static void addBodyIfExists(HttpURLConnection connection, Request request) 235 | throws IOException, AuthFailureError { 236 | byte[] body = request.getBody(); 237 | if (body != null) { 238 | connection.setDoOutput(true); 239 | connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); 240 | DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 241 | out.write(body); 242 | out.close(); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/ImageRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.DefaultRetryPolicy; 20 | import com.android.volley.NetworkResponse; 21 | import com.android.volley.ParseError; 22 | import com.android.volley.Request; 23 | import com.android.volley.Response; 24 | import com.android.volley.VolleyLog; 25 | 26 | import android.graphics.Bitmap; 27 | import android.graphics.Bitmap.Config; 28 | import android.graphics.BitmapFactory; 29 | 30 | /** 31 | * A canned request for getting an image at a given URL and calling 32 | * back with a decoded Bitmap. 33 | */ 34 | public class ImageRequest extends Request { 35 | /** Socket timeout in milliseconds for image requests */ 36 | private static final int IMAGE_TIMEOUT_MS = 1000; 37 | 38 | /** Default number of retries for image requests */ 39 | private static final int IMAGE_MAX_RETRIES = 2; 40 | 41 | /** Default backoff multiplier for image requests */ 42 | private static final float IMAGE_BACKOFF_MULT = 2f; 43 | 44 | private final Response.Listener mListener; 45 | private final Config mDecodeConfig; 46 | private final int mMaxWidth; 47 | private final int mMaxHeight; 48 | 49 | /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */ 50 | private static final Object sDecodeLock = new Object(); 51 | 52 | /** 53 | * Creates a new image request, decoding to a maximum specified width and 54 | * height. If both width and height are zero, the image will be decoded to 55 | * its natural size. If one of the two is nonzero, that dimension will be 56 | * clamped and the other one will be set to preserve the image's aspect 57 | * ratio. If both width and height are nonzero, the image will be decoded to 58 | * be fit in the rectangle of dimensions width x height while keeping its 59 | * aspect ratio. 60 | * 61 | * @param url URL of the image 62 | * @param listener Listener to receive the decoded bitmap 63 | * @param maxWidth Maximum width to decode this bitmap to, or zero for none 64 | * @param maxHeight Maximum height to decode this bitmap to, or zero for 65 | * none 66 | * @param decodeConfig Format to decode the bitmap to 67 | * @param errorListener Error listener, or null to ignore errors 68 | */ 69 | public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight, 70 | Config decodeConfig, Response.ErrorListener errorListener) { 71 | super(Method.GET, url, errorListener); 72 | setRetryPolicy( 73 | new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT)); 74 | mListener = listener; 75 | mDecodeConfig = decodeConfig; 76 | mMaxWidth = maxWidth; 77 | mMaxHeight = maxHeight; 78 | } 79 | 80 | @Override 81 | public Priority getPriority() { 82 | return Priority.LOW; 83 | } 84 | 85 | /** 86 | * Scales one side of a rectangle to fit aspect ratio. 87 | * 88 | * @param maxPrimary Maximum size of the primary dimension (i.e. width for 89 | * max width), or zero to maintain aspect ratio with secondary 90 | * dimension 91 | * @param maxSecondary Maximum size of the secondary dimension, or zero to 92 | * maintain aspect ratio with primary dimension 93 | * @param actualPrimary Actual size of the primary dimension 94 | * @param actualSecondary Actual size of the secondary dimension 95 | */ 96 | private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, 97 | int actualSecondary) { 98 | // If no dominant value at all, just return the actual. 99 | if (maxPrimary == 0 && maxSecondary == 0) { 100 | return actualPrimary; 101 | } 102 | 103 | // If primary is unspecified, scale primary to match secondary's scaling ratio. 104 | if (maxPrimary == 0) { 105 | double ratio = (double) maxSecondary / (double) actualSecondary; 106 | return (int) (actualPrimary * ratio); 107 | } 108 | 109 | if (maxSecondary == 0) { 110 | return maxPrimary; 111 | } 112 | 113 | double ratio = (double) actualSecondary / (double) actualPrimary; 114 | int resized = maxPrimary; 115 | if (resized * ratio > maxSecondary) { 116 | resized = (int) (maxSecondary / ratio); 117 | } 118 | return resized; 119 | } 120 | 121 | @Override 122 | protected Response parseNetworkResponse(NetworkResponse response) { 123 | // Serialize all decode on a global lock to reduce concurrent heap usage. 124 | synchronized (sDecodeLock) { 125 | try { 126 | return doParse(response); 127 | } catch (OutOfMemoryError e) { 128 | VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl()); 129 | return Response.error(new ParseError(e)); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * The real guts of parseNetworkResponse. Broken out for readability. 136 | */ 137 | private Response doParse(NetworkResponse response) { 138 | byte[] data = response.data; 139 | BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); 140 | Bitmap bitmap = null; 141 | if (mMaxWidth == 0 && mMaxHeight == 0) { 142 | decodeOptions.inPreferredConfig = mDecodeConfig; 143 | bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 144 | } else { 145 | // If we have to resize this image, first get the natural bounds. 146 | decodeOptions.inJustDecodeBounds = true; 147 | BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 148 | int actualWidth = decodeOptions.outWidth; 149 | int actualHeight = decodeOptions.outHeight; 150 | 151 | // Then compute the dimensions we would ideally like to decode to. 152 | int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, 153 | actualWidth, actualHeight); 154 | int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, 155 | actualHeight, actualWidth); 156 | 157 | // Decode to the nearest power of two scaling factor. 158 | decodeOptions.inJustDecodeBounds = false; 159 | // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it? 160 | // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; 161 | decodeOptions.inSampleSize = 162 | findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); 163 | Bitmap tempBitmap = 164 | BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 165 | 166 | // If necessary, scale down to the maximal acceptable size. 167 | if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || 168 | tempBitmap.getHeight() > desiredHeight)) { 169 | bitmap = Bitmap.createScaledBitmap(tempBitmap, 170 | desiredWidth, desiredHeight, true); 171 | tempBitmap.recycle(); 172 | } else { 173 | bitmap = tempBitmap; 174 | } 175 | } 176 | 177 | if (bitmap == null) { 178 | return Response.error(new ParseError(response)); 179 | } else { 180 | return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); 181 | } 182 | } 183 | 184 | @Override 185 | protected void deliverResponse(Bitmap response) { 186 | mListener.onResponse(response); 187 | } 188 | 189 | /** 190 | * Returns the largest power-of-two divisor for use in downscaling a bitmap 191 | * that will not result in the scaling past the desired dimensions. 192 | * 193 | * @param actualWidth Actual width of the bitmap 194 | * @param actualHeight Actual height of the bitmap 195 | * @param desiredWidth Desired width of the bitmap 196 | * @param desiredHeight Desired height of the bitmap 197 | */ 198 | // Visible for testing. 199 | static int findBestSampleSize( 200 | int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { 201 | double wr = (double) actualWidth / desiredWidth; 202 | double hr = (double) actualHeight / desiredHeight; 203 | double ratio = Math.min(wr, hr); 204 | float n = 1.0f; 205 | while ((n * 2) <= ratio) { 206 | n *= 2; 207 | } 208 | 209 | return (int) n; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/JsonArrayRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.ParseError; 21 | import com.android.volley.Response; 22 | import com.android.volley.Response.ErrorListener; 23 | import com.android.volley.Response.Listener; 24 | 25 | import org.json.JSONArray; 26 | import org.json.JSONException; 27 | 28 | import java.io.UnsupportedEncodingException; 29 | 30 | /** 31 | * A request for retrieving a {@link org.json.JSONArray} response body at a given URL. 32 | */ 33 | public class JsonArrayRequest extends JsonRequest { 34 | 35 | /** 36 | * Creates a new request. 37 | * @param url URL to fetch the JSON from 38 | * @param listener Listener to receive the JSON response 39 | * @param errorListener Error listener, or null to ignore errors. 40 | */ 41 | public JsonArrayRequest(String url, Listener listener, ErrorListener errorListener) { 42 | super(Method.GET, url, null, listener, errorListener); 43 | } 44 | 45 | @Override 46 | protected Response parseNetworkResponse(NetworkResponse response) { 47 | try { 48 | String jsonString = 49 | new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 50 | return Response.success(new JSONArray(jsonString), 51 | HttpHeaderParser.parseCacheHeaders(response)); 52 | } catch (UnsupportedEncodingException e) { 53 | return Response.error(new ParseError(e)); 54 | } catch (JSONException je) { 55 | return Response.error(new ParseError(je)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/JsonObjectRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.ParseError; 21 | import com.android.volley.Response; 22 | import com.android.volley.Response.ErrorListener; 23 | import com.android.volley.Response.Listener; 24 | 25 | import org.json.JSONException; 26 | import org.json.JSONObject; 27 | 28 | import java.io.UnsupportedEncodingException; 29 | 30 | /** 31 | * A request for retrieving a {@link org.json.JSONObject} response body at a given URL, allowing for an 32 | * optional {@link org.json.JSONObject} to be passed in as part of the request body. 33 | */ 34 | public class JsonObjectRequest extends JsonRequest { 35 | 36 | /** 37 | * Creates a new request. 38 | * @param method the HTTP method to use 39 | * @param url URL to fetch the JSON from 40 | * @param jsonRequest A {@link org.json.JSONObject} to post with the request. Null is allowed and 41 | * indicates no parameters will be posted along with request. 42 | * @param listener Listener to receive the JSON response 43 | * @param errorListener Error listener, or null to ignore errors. 44 | */ 45 | public JsonObjectRequest(int method, String url, JSONObject jsonRequest, 46 | Listener listener, ErrorListener errorListener) { 47 | super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, 48 | errorListener); 49 | } 50 | 51 | /** 52 | * Constructor which defaults to GET if jsonRequest is 53 | * null, POST otherwise. 54 | * 55 | * @see #JsonObjectRequest(int, String, org.json.JSONObject, Listener, ErrorListener) 56 | */ 57 | public JsonObjectRequest(String url, JSONObject jsonRequest, Listener listener, 58 | ErrorListener errorListener) { 59 | this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, 60 | listener, errorListener); 61 | } 62 | 63 | @Override 64 | protected Response parseNetworkResponse(NetworkResponse response) { 65 | try { 66 | String jsonString = 67 | new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 68 | return Response.success(new JSONObject(jsonString), 69 | HttpHeaderParser.parseCacheHeaders(response)); 70 | } catch (UnsupportedEncodingException e) { 71 | return Response.error(new ParseError(e)); 72 | } catch (JSONException je) { 73 | return Response.error(new ParseError(je)); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/JsonRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.Request; 21 | import com.android.volley.Response; 22 | import com.android.volley.Response.ErrorListener; 23 | import com.android.volley.Response.Listener; 24 | import com.android.volley.VolleyLog; 25 | 26 | import java.io.UnsupportedEncodingException; 27 | 28 | /** 29 | * A request for retrieving a T type response body at a given URL that also 30 | * optionally sends along a JSON body in the request specified. 31 | * 32 | * @param JSON type of response expected 33 | */ 34 | public abstract class JsonRequest extends Request { 35 | /** Charset for request. */ 36 | private static final String PROTOCOL_CHARSET = "utf-8"; 37 | 38 | /** Content type for request. */ 39 | private static final String PROTOCOL_CONTENT_TYPE = 40 | String.format("application/json; charset=%s", PROTOCOL_CHARSET); 41 | 42 | private final Listener mListener; 43 | private final String mRequestBody; 44 | 45 | /** 46 | * Deprecated constructor for a JsonRequest which defaults to GET unless {@link #getPostBody()} 47 | * or {@link #getPostParams()} is overridden (which defaults to POST). 48 | * 49 | * @deprecated Use {@link #JsonRequest(int, String, String, Listener, ErrorListener)}. 50 | */ 51 | public JsonRequest(String url, String requestBody, Listener listener, 52 | ErrorListener errorListener) { 53 | this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener); 54 | } 55 | 56 | public JsonRequest(int method, String url, String requestBody, Listener listener, 57 | ErrorListener errorListener) { 58 | super(method, url, errorListener); 59 | mListener = listener; 60 | mRequestBody = requestBody; 61 | } 62 | 63 | @Override 64 | protected void deliverResponse(T response) { 65 | mListener.onResponse(response); 66 | } 67 | 68 | @Override 69 | abstract protected Response parseNetworkResponse(NetworkResponse response); 70 | 71 | /** 72 | * @deprecated Use {@link #getBodyContentType()}. 73 | */ 74 | @Override 75 | public String getPostBodyContentType() { 76 | return getBodyContentType(); 77 | } 78 | 79 | /** 80 | * @deprecated Use {@link #getBody()}. 81 | */ 82 | @Override 83 | public byte[] getPostBody() { 84 | return getBody(); 85 | } 86 | 87 | @Override 88 | public String getBodyContentType() { 89 | return PROTOCOL_CONTENT_TYPE; 90 | } 91 | 92 | @Override 93 | public byte[] getBody() { 94 | try { 95 | return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET); 96 | } catch (UnsupportedEncodingException uee) { 97 | VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", 98 | mRequestBody, PROTOCOL_CHARSET); 99 | return null; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/NetworkImageView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.volley.toolbox; 17 | 18 | import android.content.Context; 19 | import android.text.TextUtils; 20 | import android.util.AttributeSet; 21 | import android.view.ViewGroup.LayoutParams; 22 | import android.widget.ImageView; 23 | 24 | import com.android.volley.VolleyError; 25 | import com.android.volley.toolbox.ImageLoader.ImageContainer; 26 | import com.android.volley.toolbox.ImageLoader.ImageListener; 27 | import com.vinaysshenoy.okulus.OkulusImageView; 28 | 29 | /** 30 | * Handles fetching an image from a URL as well as the life-cycle of the 31 | * associated request. 32 | */ 33 | public class NetworkImageView extends OkulusImageView { 34 | /** The URL of the network image to load */ 35 | private String mUrl; 36 | 37 | /** 38 | * Resource ID of the image to be used as a placeholder until the network image is loaded. 39 | */ 40 | private int mDefaultImageId; 41 | 42 | /** 43 | * Resource ID of the image to be used if the network response fails. 44 | */ 45 | private int mErrorImageId; 46 | 47 | /** Local copy of the ImageLoader. */ 48 | private ImageLoader mImageLoader; 49 | 50 | /** Current ImageContainer. (either in-flight or finished) */ 51 | private ImageContainer mImageContainer; 52 | 53 | public NetworkImageView(Context context) { 54 | this(context, null); 55 | } 56 | 57 | public NetworkImageView(Context context, AttributeSet attrs) { 58 | this(context, attrs, 0); 59 | } 60 | 61 | public NetworkImageView(Context context, AttributeSet attrs, int defStyle) { 62 | super(context, attrs, defStyle); 63 | } 64 | 65 | /** 66 | * Sets URL of the image that should be loaded into this view. Note that calling this will 67 | * immediately either set the cached image (if available) or the default image specified by 68 | * {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} on the view. 69 | * 70 | * NOTE: If applicable, {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} and 71 | * {@link com.android.volley.toolbox.NetworkImageView#setErrorImageResId(int)} should be called prior to calling 72 | * this function. 73 | * 74 | * @param url The URL that should be loaded into this ImageView. 75 | * @param imageLoader ImageLoader that will be used to make the request. 76 | */ 77 | public void setImageUrl(String url, ImageLoader imageLoader) { 78 | mUrl = url; 79 | mImageLoader = imageLoader; 80 | // The URL has potentially changed. See if we need to load it. 81 | loadImageIfNecessary(false); 82 | } 83 | 84 | /** 85 | * Sets the default image resource ID to be used for this view until the attempt to load it 86 | * completes. 87 | */ 88 | public void setDefaultImageResId(int defaultImage) { 89 | mDefaultImageId = defaultImage; 90 | } 91 | 92 | /** 93 | * Sets the error image resource ID to be used for this view in the event that the image 94 | * requested fails to load. 95 | */ 96 | public void setErrorImageResId(int errorImage) { 97 | mErrorImageId = errorImage; 98 | } 99 | 100 | /** 101 | * Loads the image for the view if it isn't already loaded. 102 | * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. 103 | */ 104 | void loadImageIfNecessary(final boolean isInLayoutPass) { 105 | int width = getWidth(); 106 | int height = getHeight(); 107 | 108 | boolean wrapWidth = false, wrapHeight = false; 109 | if (getLayoutParams() != null) { 110 | wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT; 111 | wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT; 112 | } 113 | 114 | // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content 115 | // view, hold off on loading the image. 116 | boolean isFullyWrapContent = wrapWidth && wrapHeight; 117 | if (width == 0 && height == 0 && !isFullyWrapContent) { 118 | return; 119 | } 120 | 121 | // if the URL to be loaded in this view is empty, cancel any old requests and clear the 122 | // currently loaded image. 123 | if (TextUtils.isEmpty(mUrl)) { 124 | if (mImageContainer != null) { 125 | mImageContainer.cancelRequest(); 126 | mImageContainer = null; 127 | } 128 | setDefaultImageOrNull(); 129 | return; 130 | } 131 | 132 | // if there was an old request in this view, check if it needs to be canceled. 133 | if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { 134 | if (mImageContainer.getRequestUrl().equals(mUrl)) { 135 | // if the request is from the same URL, return. 136 | return; 137 | } else { 138 | // if there is a pre-existing request, cancel it if it's fetching a different URL. 139 | mImageContainer.cancelRequest(); 140 | setDefaultImageOrNull(); 141 | } 142 | } 143 | 144 | // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens. 145 | int maxWidth = wrapWidth ? 0 : width; 146 | int maxHeight = wrapHeight ? 0 : height; 147 | 148 | // The pre-existing content of this view didn't match the current URL. Load the new image 149 | // from the network. 150 | ImageContainer newContainer = mImageLoader.get(mUrl, 151 | new ImageListener() { 152 | @Override 153 | public void onErrorResponse(VolleyError error) { 154 | if (mErrorImageId != 0) { 155 | setImageResource(mErrorImageId); 156 | } 157 | } 158 | 159 | @Override 160 | public void onResponse(final ImageContainer response, boolean isImmediate) { 161 | // If this was an immediate response that was delivered inside of a layout 162 | // pass do not set the image immediately as it will trigger a requestLayout 163 | // inside of a layout. Instead, defer setting the image by posting back to 164 | // the main thread. 165 | if (isImmediate && isInLayoutPass) { 166 | post(new Runnable() { 167 | @Override 168 | public void run() { 169 | onResponse(response, false); 170 | } 171 | }); 172 | return; 173 | } 174 | 175 | if (response.getBitmap() != null) { 176 | setImageBitmap(response.getBitmap()); 177 | } else if (mDefaultImageId != 0) { 178 | setImageResource(mDefaultImageId); 179 | } 180 | } 181 | }, maxWidth, maxHeight); 182 | 183 | // update the ImageContainer to be the new bitmap container. 184 | mImageContainer = newContainer; 185 | } 186 | 187 | private void setDefaultImageOrNull() { 188 | if(mDefaultImageId != 0) { 189 | setImageResource(mDefaultImageId); 190 | } 191 | else { 192 | setImageBitmap(null); 193 | } 194 | } 195 | 196 | @Override 197 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 198 | super.onLayout(changed, left, top, right, bottom); 199 | loadImageIfNecessary(true); 200 | } 201 | 202 | @Override 203 | protected void onDetachedFromWindow() { 204 | if (mImageContainer != null) { 205 | // If the view was bound to an image request, cancel it and clear 206 | // out the image from the view. 207 | mImageContainer.cancelRequest(); 208 | setImageBitmap(null); 209 | // also clear out the container so we can reload the image if necessary. 210 | mImageContainer = null; 211 | } 212 | super.onDetachedFromWindow(); 213 | } 214 | 215 | @Override 216 | protected void drawableStateChanged() { 217 | super.drawableStateChanged(); 218 | invalidate(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/NoCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.Cache; 20 | 21 | /** 22 | * A cache that doesn't. 23 | */ 24 | public class NoCache implements Cache { 25 | @Override 26 | public void clear() { 27 | } 28 | 29 | @Override 30 | public Entry get(String key) { 31 | return null; 32 | } 33 | 34 | @Override 35 | public void put(String key, Entry entry) { 36 | } 37 | 38 | @Override 39 | public void invalidate(String key, boolean fullExpire) { 40 | } 41 | 42 | @Override 43 | public void remove(String key) { 44 | } 45 | 46 | @Override 47 | public void initialize() { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/PoolingByteArrayOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.IOException; 21 | 22 | /** 23 | * A variation of {@link java.io.ByteArrayOutputStream} that uses a pool of byte[] buffers instead 24 | * of always allocating them fresh, saving on heap churn. 25 | */ 26 | public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { 27 | /** 28 | * If the {@link #PoolingByteArrayOutputStream(com.android.volley.toolbox.ByteArrayPool)} constructor is called, this is 29 | * the default size to which the underlying byte array is initialized. 30 | */ 31 | private static final int DEFAULT_SIZE = 256; 32 | 33 | private final ByteArrayPool mPool; 34 | 35 | /** 36 | * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written 37 | * to this instance, the underlying byte array will expand. 38 | */ 39 | public PoolingByteArrayOutputStream(ByteArrayPool pool) { 40 | this(pool, DEFAULT_SIZE); 41 | } 42 | 43 | /** 44 | * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If 45 | * more than {@code size} bytes are written to this instance, the underlying byte array will 46 | * expand. 47 | * 48 | * @param size initial size for the underlying byte array. The value will be pinned to a default 49 | * minimum size. 50 | */ 51 | public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { 52 | mPool = pool; 53 | buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); 54 | } 55 | 56 | @Override 57 | public void close() throws IOException { 58 | mPool.returnBuf(buf); 59 | buf = null; 60 | super.close(); 61 | } 62 | 63 | @Override 64 | public void finalize() { 65 | mPool.returnBuf(buf); 66 | } 67 | 68 | /** 69 | * Ensures there is enough space in the buffer for the given number of additional bytes. 70 | */ 71 | private void expand(int i) { 72 | /* Can the buffer handle @i more bytes, if not expand it */ 73 | if (count + i <= buf.length) { 74 | return; 75 | } 76 | byte[] newbuf = mPool.getBuf((count + i) * 2); 77 | System.arraycopy(buf, 0, newbuf, 0, count); 78 | mPool.returnBuf(buf); 79 | buf = newbuf; 80 | } 81 | 82 | @Override 83 | public synchronized void write(byte[] buffer, int offset, int len) { 84 | expand(len); 85 | super.write(buffer, offset, len); 86 | } 87 | 88 | @Override 89 | public synchronized void write(int oneByte) { 90 | expand(1); 91 | super.write(oneByte); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/RequestFuture.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.Request; 20 | import com.android.volley.Response; 21 | import com.android.volley.VolleyError; 22 | 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.Future; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.TimeoutException; 27 | 28 | /** 29 | * A Future that represents a Volley request. 30 | * 31 | * Used by providing as your response and error listeners. For example: 32 | *
 33 |  * RequestFuture<JSONObject> future = RequestFuture.newFuture();
 34 |  * MyRequest request = new MyRequest(URL, future, future);
 35 |  *
 36 |  * // If you want to be able to cancel the request:
 37 |  * future.setRequest(requestQueue.add(request));
 38 |  *
 39 |  * // Otherwise:
 40 |  * requestQueue.add(request);
 41 |  *
 42 |  * try {
 43 |  *   JSONObject response = future.get();
 44 |  *   // do something with response
 45 |  * } catch (InterruptedException e) {
 46 |  *   // handle the error
 47 |  * } catch (ExecutionException e) {
 48 |  *   // handle the error
 49 |  * }
 50 |  * 
51 | * 52 | * @param The type of parsed response this future expects. 53 | */ 54 | public class RequestFuture implements Future, Response.Listener, 55 | Response.ErrorListener { 56 | private Request mRequest; 57 | private boolean mResultReceived = false; 58 | private T mResult; 59 | private VolleyError mException; 60 | 61 | public static RequestFuture newFuture() { 62 | return new RequestFuture(); 63 | } 64 | 65 | private RequestFuture() {} 66 | 67 | public void setRequest(Request request) { 68 | mRequest = request; 69 | } 70 | 71 | @Override 72 | public synchronized boolean cancel(boolean mayInterruptIfRunning) { 73 | if (mRequest == null) { 74 | return false; 75 | } 76 | 77 | if (!isDone()) { 78 | mRequest.cancel(); 79 | return true; 80 | } else { 81 | return false; 82 | } 83 | } 84 | 85 | @Override 86 | public T get() throws InterruptedException, ExecutionException { 87 | try { 88 | return doGet(null); 89 | } catch (TimeoutException e) { 90 | throw new AssertionError(e); 91 | } 92 | } 93 | 94 | @Override 95 | public T get(long timeout, TimeUnit unit) 96 | throws InterruptedException, ExecutionException, TimeoutException { 97 | return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit)); 98 | } 99 | 100 | private synchronized T doGet(Long timeoutMs) 101 | throws InterruptedException, ExecutionException, TimeoutException { 102 | if (mException != null) { 103 | throw new ExecutionException(mException); 104 | } 105 | 106 | if (mResultReceived) { 107 | return mResult; 108 | } 109 | 110 | if (timeoutMs == null) { 111 | wait(0); 112 | } else if (timeoutMs > 0) { 113 | wait(timeoutMs); 114 | } 115 | 116 | if (mException != null) { 117 | throw new ExecutionException(mException); 118 | } 119 | 120 | if (!mResultReceived) { 121 | throw new TimeoutException(); 122 | } 123 | 124 | return mResult; 125 | } 126 | 127 | @Override 128 | public boolean isCancelled() { 129 | if (mRequest == null) { 130 | return false; 131 | } 132 | return mRequest.isCanceled(); 133 | } 134 | 135 | @Override 136 | public synchronized boolean isDone() { 137 | return mResultReceived || mException != null || isCancelled(); 138 | } 139 | 140 | @Override 141 | public synchronized void onResponse(T response) { 142 | mResultReceived = true; 143 | mResult = response; 144 | notifyAll(); 145 | } 146 | 147 | @Override 148 | public synchronized void onErrorResponse(VolleyError error) { 149 | mException = error; 150 | notifyAll(); 151 | } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/StringRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import com.android.volley.NetworkResponse; 20 | import com.android.volley.Request; 21 | import com.android.volley.Response; 22 | import com.android.volley.Response.ErrorListener; 23 | import com.android.volley.Response.Listener; 24 | 25 | import java.io.UnsupportedEncodingException; 26 | 27 | /** 28 | * A canned request for retrieving the response body at a given URL as a String. 29 | */ 30 | public class StringRequest extends Request { 31 | private final Listener mListener; 32 | 33 | /** 34 | * Creates a new request with the given method. 35 | * 36 | * @param method the request {@link Method} to use 37 | * @param url URL to fetch the string at 38 | * @param listener Listener to receive the String response 39 | * @param errorListener Error listener, or null to ignore errors 40 | */ 41 | public StringRequest(int method, String url, Listener listener, 42 | ErrorListener errorListener) { 43 | super(method, url, errorListener); 44 | mListener = listener; 45 | } 46 | 47 | /** 48 | * Creates a new GET request. 49 | * 50 | * @param url URL to fetch the string at 51 | * @param listener Listener to receive the String response 52 | * @param errorListener Error listener, or null to ignore errors 53 | */ 54 | public StringRequest(String url, Listener listener, ErrorListener errorListener) { 55 | this(Method.GET, url, listener, errorListener); 56 | } 57 | 58 | @Override 59 | protected void deliverResponse(String response) { 60 | mListener.onResponse(response); 61 | } 62 | 63 | @Override 64 | protected Response parseNetworkResponse(NetworkResponse response) { 65 | String parsed; 66 | try { 67 | parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 68 | } catch (UnsupportedEncodingException e) { 69 | parsed = new String(response.data); 70 | } 71 | return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/volley/toolbox/Volley.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.volley.toolbox; 18 | 19 | import android.content.Context; 20 | import android.content.pm.PackageInfo; 21 | import android.content.pm.PackageManager.NameNotFoundException; 22 | import android.net.http.AndroidHttpClient; 23 | import android.os.Build; 24 | 25 | import com.android.volley.Network; 26 | import com.android.volley.RequestQueue; 27 | 28 | import java.io.File; 29 | 30 | public class Volley { 31 | 32 | /** Default on-disk cache directory. */ 33 | private static final String DEFAULT_CACHE_DIR = "volley"; 34 | 35 | /** 36 | * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 37 | * 38 | * @param context A {@link android.content.Context} to use for creating the cache dir. 39 | * @param stack An {@link HttpStack} to use for the network, or null for default. 40 | * @return A started {@link RequestQueue} instance. 41 | */ 42 | public static RequestQueue newRequestQueue(Context context, HttpStack stack) { 43 | File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); 44 | 45 | String userAgent = "volley/0"; 46 | try { 47 | String packageName = context.getPackageName(); 48 | PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); 49 | userAgent = packageName + "/" + info.versionCode; 50 | } catch (NameNotFoundException e) { 51 | } 52 | 53 | if (stack == null) { 54 | if (Build.VERSION.SDK_INT >= 9) { 55 | stack = new HurlStack(); 56 | } else { 57 | // Prior to Gingerbread, HttpUrlConnection was unreliable. 58 | // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 59 | stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); 60 | } 61 | } 62 | 63 | Network network = new BasicNetwork(stack); 64 | 65 | RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); 66 | queue.start(); 67 | 68 | return queue; 69 | } 70 | 71 | /** 72 | * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 73 | * 74 | * @param context A {@link android.content.Context} to use for creating the cache dir. 75 | * @return A started {@link RequestQueue} instance. 76 | */ 77 | public static RequestQueue newRequestQueue(Context context) { 78 | return newRequestQueue(context, null); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinaysshenoy.okulusdemo; 2 | 3 | import android.app.Application; 4 | import android.graphics.Bitmap; 5 | import android.support.v4.util.LruCache; 6 | 7 | import com.android.volley.RequestQueue; 8 | import com.android.volley.toolbox.ImageLoader; 9 | import com.android.volley.toolbox.Volley; 10 | import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache; 11 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 12 | import com.nostra13.universalimageloader.utils.StorageUtils; 13 | 14 | import java.io.File; 15 | 16 | /** 17 | * Created by vinaysshenoy on 04/12/14. 18 | */ 19 | public class DemoApplication extends Application { 20 | 21 | private RequestQueue mRequestQueue; 22 | 23 | private ImageLoader mImageLoader; 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | 29 | mRequestQueue = Volley.newRequestQueue(this); 30 | mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { 31 | private final LruCache 32 | cache = new LruCache<>(20); 33 | 34 | @Override 35 | public Bitmap getBitmap(String url) { 36 | return cache.get(url); 37 | } 38 | 39 | @Override 40 | public void putBitmap(String url, Bitmap bitmap) { 41 | cache.put(url, bitmap); 42 | } 43 | }); 44 | 45 | final File cacheDirectory = StorageUtils.getCacheDirectory(this); 46 | final ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this) 47 | .diskCache(new UnlimitedDiscCache(cacheDirectory)) 48 | .build(); 49 | com.nostra13.universalimageloader.core.ImageLoader.getInstance().init(config); 50 | } 51 | 52 | public RequestQueue getRequestQueue() { 53 | return mRequestQueue; 54 | } 55 | 56 | public ImageLoader getImageLoader() { 57 | return mImageLoader; 58 | } 59 | 60 | public com.nostra13.universalimageloader.core.ImageLoader getUniversalImageLoader() { 61 | return com.nostra13.universalimageloader.core.ImageLoader.getInstance(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vinaysshenoy.okulusdemo; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v7.app.ActionBarActivity; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | 10 | import com.vinaysshenoy.okulusdemo.fragments.ComparisonFragment; 11 | import com.vinaysshenoy.okulusdemo.fragments.NetworkFragment; 12 | import com.vinaysshenoy.okulusdemo.fragments.RoundedRectanglesFragment; 13 | 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | if(savedInstanceState == null) { 23 | loadComparisonFragment(); 24 | } 25 | } 26 | 27 | private void loadComparisonFragment() { 28 | 29 | getSupportFragmentManager().beginTransaction() 30 | .replace(R.id.frame_content, Fragment.instantiate(this, ComparisonFragment.class.getName()), 31 | "fragment_comparison") 32 | .commit(); 33 | } 34 | 35 | private void loadRoundRectFragment() { 36 | 37 | getSupportFragmentManager().beginTransaction() 38 | .replace(R.id.frame_content, 39 | Fragment.instantiate(this, RoundedRectanglesFragment.class.getName()), 40 | "fragment_rounded_rectangles") 41 | .commit(); 42 | } 43 | 44 | private void loadNetworkFragment() { 45 | 46 | getSupportFragmentManager().beginTransaction() 47 | .replace(R.id.frame_content, 48 | Fragment.instantiate(this, NetworkFragment.class.getName()), 49 | "fragment_network") 50 | .commit(); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/fragments/ComparisonFragment.java: -------------------------------------------------------------------------------- 1 | package com.vinaysshenoy.okulusdemo.fragments; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ImageView; 13 | 14 | import com.vinaysshenoy.okulus.OkulusImageView; 15 | import com.vinaysshenoy.okulusdemo.R; 16 | import com.vinaysshenoy.okulusdemo.utils.RawBitmapManager; 17 | 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | 21 | /** 22 | * Created by vinay.shenoy on 24/07/14. 23 | */ 24 | public class ComparisonFragment extends Fragment { 25 | 26 | private OkulusImageView mOkulusImageView; 27 | private ImageView mNormalImageView; 28 | 29 | private ExecutorService mPool; 30 | private Handler mHandler = new Handler(Looper.getMainLooper()); 31 | 32 | private boolean mPaused; 33 | 34 | @Override 35 | public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { 36 | 37 | mPool = Executors.newSingleThreadExecutor(); 38 | final View content = inflater.inflate(R.layout.fragment_comparison, container, false); 39 | 40 | mOkulusImageView = (OkulusImageView) content.findViewById(R.id.image_okulus); 41 | mNormalImageView = (ImageView) content.findViewById(R.id.image_normal); 42 | 43 | return content; 44 | } 45 | 46 | @Override 47 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | loadBitmaps(); 50 | } 51 | 52 | private void loadBitmaps() { 53 | 54 | mPool.submit(new LoadBitmapRunnable(mOkulusImageView, R.raw.img_6, 128, 128)); 55 | mPool.submit(new LoadBitmapRunnable(mNormalImageView, R.raw.img_6, 128, 128)); 56 | 57 | } 58 | 59 | @Override 60 | public void onPause() { 61 | super.onPause(); 62 | mPool.shutdownNow(); 63 | mPaused = true; 64 | } 65 | 66 | @Override 67 | public void onResume() { 68 | super.onResume(); 69 | mPaused = false; 70 | } 71 | 72 | private class LoadBitmapRunnable implements Runnable { 73 | 74 | private int mResourceId; 75 | private int mTargetWidth, mTargetHeight; 76 | private ImageView mImageView; 77 | 78 | public LoadBitmapRunnable(ImageView imageView, int resourceId, int targetWidth, int targetheight) { 79 | 80 | mImageView = imageView; 81 | mResourceId = resourceId; 82 | mTargetWidth = targetWidth; 83 | mTargetHeight = targetheight; 84 | } 85 | 86 | @Override 87 | public void run() { 88 | 89 | final Bitmap bitmap = RawBitmapManager.INSTANCE.getBitmap( 90 | mImageView.getContext(), 91 | mResourceId, 92 | mTargetWidth, 93 | mTargetHeight); 94 | 95 | if (!mPaused && bitmap != null) { 96 | mHandler.post(new Runnable() { 97 | @Override 98 | public void run() { 99 | mImageView.setImageBitmap(bitmap); 100 | } 101 | }); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/fragments/NetworkFragment.java: -------------------------------------------------------------------------------- 1 | package com.vinaysshenoy.okulusdemo.fragments; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.Drawable; 6 | import android.os.Bundle; 7 | import android.support.v4.app.ListFragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.BaseAdapter; 12 | import android.widget.ImageView; 13 | 14 | import com.android.volley.RequestQueue; 15 | import com.android.volley.Response; 16 | import com.android.volley.VolleyError; 17 | import com.android.volley.toolbox.ImageLoader; 18 | import com.android.volley.toolbox.ImageRequest; 19 | import com.android.volley.toolbox.NetworkImageView; 20 | import com.bumptech.glide.Glide; 21 | import com.bumptech.glide.request.animation.GlideAnimation; 22 | import com.bumptech.glide.request.target.ViewTarget; 23 | import com.nostra13.universalimageloader.core.assist.ImageSize; 24 | import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; 25 | import com.squareup.picasso.Picasso; 26 | import com.squareup.picasso.Target; 27 | import com.vinaysshenoy.okulusdemo.DemoApplication; 28 | import com.vinaysshenoy.okulusdemo.R; 29 | 30 | /** 31 | * Fragment to display images in a ListView 32 | *

33 | * Created by vinaysshenoy on 04/12/14. 34 | */ 35 | public class NetworkFragment extends ListFragment { 36 | 37 | 38 | @Override 39 | public void onViewCreated(View view, Bundle savedInstanceState) { 40 | super.onViewCreated(view, savedInstanceState); 41 | 42 | final DemoApplication application = (DemoApplication) getActivity().getApplication(); 43 | setListAdapter(new ImageAdapter(getActivity(), "https://scontent-b-ams.xx.fbcdn.net/hphotos-xfp1/t31.0-8/10700409_10205008935044693_3621835106117173363_o.jpg", application.getRequestQueue(), application.getImageLoader(), application.getUniversalImageLoader())); 44 | 45 | } 46 | 47 | private static final class ImageAdapter extends BaseAdapter { 48 | 49 | private String mImageUrl; 50 | 51 | private ImageLoader mImageLoader; 52 | 53 | private RequestQueue mRequestQueue; 54 | 55 | private Context mContext; 56 | 57 | private com.nostra13.universalimageloader.core.ImageLoader mUniversalImageLoader; 58 | 59 | public ImageAdapter(final Context context, final String imageUrl, final RequestQueue requestQueue, final ImageLoader imageLoader, final com.nostra13.universalimageloader.core.ImageLoader universalImageLoader) { 60 | mContext = context; 61 | mImageUrl = imageUrl; 62 | mRequestQueue = requestQueue; 63 | mImageLoader = imageLoader; 64 | mUniversalImageLoader = universalImageLoader; 65 | } 66 | 67 | @Override 68 | public int getCount() { 69 | return 5; 70 | } 71 | 72 | @Override 73 | public Object getItem(int position) { 74 | return mImageUrl; 75 | } 76 | 77 | @Override 78 | public long getItemId(int position) { 79 | return position; 80 | } 81 | 82 | @Override 83 | public int getViewTypeCount() { 84 | return 5; 85 | } 86 | 87 | @Override 88 | public int getItemViewType(int position) { 89 | 90 | return position % 5; 91 | } 92 | 93 | @Override 94 | public View getView(int position, View convertView, ViewGroup parent) { 95 | 96 | final View view = createView(position, convertView, parent); 97 | loadImage((ImageView) view, position); 98 | return view; 99 | } 100 | 101 | private int dpToPx(int dp) { 102 | 103 | float density = mContext.getResources().getDisplayMetrics().density; 104 | return (int) (dp * density); 105 | } 106 | 107 | private void loadImage(final ImageView imageView, final int position) { 108 | 109 | final String imageurl = (String) getItem(position); 110 | switch (getItemViewType(position)) { 111 | 112 | //Volley - ImageRequest 113 | case 0: { 114 | final ImageRequest request = new ImageRequest( 115 | imageurl, 116 | new Response.Listener() { 117 | @Override 118 | public void onResponse(Bitmap response) { 119 | imageView.setImageBitmap(response); 120 | } 121 | }, 122 | dpToPx(128), 123 | dpToPx(96), 124 | null, 125 | new Response.ErrorListener() { 126 | @Override 127 | public void onErrorResponse(VolleyError error) { 128 | 129 | } 130 | } 131 | ); 132 | 133 | mRequestQueue.add(request); 134 | break; 135 | } 136 | 137 | //Volley - NetworkImageView - In this case, NetworkImageView has been modified to extend OkulusImageView 138 | case 1: { 139 | 140 | final NetworkImageView networkImageView = (NetworkImageView) imageView; 141 | networkImageView.setImageUrl(imageurl, mImageLoader); 142 | break; 143 | } 144 | 145 | //Picasso 146 | case 2: { 147 | 148 | Picasso.with(imageView.getContext()) 149 | .load(imageurl) 150 | .resize(dpToPx(128), dpToPx(96)) 151 | .centerCrop() 152 | .into(new Target() { 153 | @Override 154 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { 155 | imageView.setImageBitmap(bitmap); 156 | } 157 | 158 | @Override 159 | public void onBitmapFailed(Drawable errorDrawable) { 160 | 161 | } 162 | 163 | @Override 164 | public void onPrepareLoad(Drawable placeHolderDrawable) { 165 | 166 | } 167 | }); 168 | break; 169 | } 170 | 171 | //Glide 172 | case 3: { 173 | 174 | Glide.with(imageView.getContext()) 175 | .load(imageurl) 176 | .asBitmap() 177 | //.override(dpToPx(128), dpToPx(96)) 178 | .centerCrop() 179 | .into(new ViewTarget(imageView) { 180 | @Override 181 | public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { 182 | imageView.setImageBitmap(resource); 183 | } 184 | }); 185 | break; 186 | } 187 | 188 | // Universal Image Loader 189 | case 4: { 190 | 191 | ImageSize targetSize = new ImageSize(dpToPx(96), dpToPx(128)); 192 | mUniversalImageLoader.loadImage(imageurl, targetSize, new SimpleImageLoadingListener() { 193 | 194 | @Override 195 | public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { 196 | imageView.setImageBitmap(loadedImage); 197 | } 198 | }); 199 | break; 200 | } 201 | 202 | } 203 | } 204 | 205 | private View createView(int position, View convertView, ViewGroup parent) { 206 | 207 | if (convertView != null) { 208 | return convertView; 209 | } else { 210 | 211 | final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 212 | int resId; 213 | switch (getItemViewType(position)) { 214 | 215 | case 0: { 216 | resId = R.layout.layout_image_1; 217 | break; 218 | } 219 | 220 | case 1: { 221 | resId = R.layout.layout_image_2; 222 | break; 223 | } 224 | 225 | case 2: { 226 | resId = R.layout.layout_image_3; 227 | break; 228 | } 229 | 230 | case 3: { 231 | resId = R.layout.layout_image_4; 232 | break; 233 | } 234 | 235 | case 4: 236 | default: { 237 | resId = R.layout.layout_image_5; 238 | break; 239 | } 240 | 241 | } 242 | return inflater.inflate(resId, parent, false); 243 | } 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/fragments/RoundedRectanglesFragment.java: -------------------------------------------------------------------------------- 1 | package com.vinaysshenoy.okulusdemo.fragments; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.support.annotation.Nullable; 12 | import android.support.v4.app.Fragment; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | 17 | import com.vinaysshenoy.okulus.OkulusImageView; 18 | import com.vinaysshenoy.okulusdemo.R; 19 | import com.vinaysshenoy.okulusdemo.utils.RawBitmapManager; 20 | 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | 24 | /** 25 | * Created by vinay.shenoy on 24/07/14. 26 | */ 27 | public class RoundedRectanglesFragment extends Fragment { 28 | 29 | private static final int[] ITEMS = new int[]{ 30 | R.raw.img_1, 31 | R.raw.img_2, 32 | R.raw.img_3, 33 | R.raw.img_4, 34 | R.raw.img_5, 35 | R.raw.img_6 36 | }; 37 | 38 | private OkulusImageView mImageView1; 39 | private OkulusImageView mImageView2; 40 | private OkulusImageView mImageView3; 41 | private OkulusImageView mImageView4; 42 | private OkulusImageView mImageView5; 43 | private OkulusImageView mImageView6; 44 | 45 | 46 | private ExecutorService mPool; 47 | private Handler mHandler = new Handler(Looper.getMainLooper()); 48 | 49 | private boolean mPaused; 50 | 51 | @Override 52 | public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { 53 | 54 | mPool = Executors.newSingleThreadExecutor(); 55 | final View content = inflater.inflate(R.layout.fragment_rounded_rectangles, container, false); 56 | 57 | mImageView1 = (OkulusImageView) content.findViewById(R.id.image_1); 58 | mImageView2 = (OkulusImageView) content.findViewById(R.id.image_2); 59 | mImageView3 = (OkulusImageView) content.findViewById(R.id.image_3); 60 | mImageView4 = (OkulusImageView) content.findViewById(R.id.image_4); 61 | mImageView5 = (OkulusImageView) content.findViewById(R.id.image_5); 62 | mImageView6 = (OkulusImageView) content.findViewById(R.id.image_6); 63 | 64 | 65 | //loadBitmaps(); 66 | 67 | return content; 68 | } 69 | 70 | @Override 71 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 72 | super.onViewCreated(view, savedInstanceState); 73 | Drawable drawable = new ColorDrawable(Color.RED); 74 | mImageView1.setImageDrawable(drawable); 75 | Uri uri = Uri.parse("android.resource://com.vinaysshenoy.okulusdemo/" + R.drawable.img_for_uri); 76 | mImageView2.setImageURI(uri); 77 | 78 | mImageView3.setImageResource(R.drawable.img_res); 79 | loadBitmaps(); 80 | } 81 | 82 | private void loadBitmaps() { 83 | 84 | mPool.submit(new LoadBitmapRunnable(mImageView4, R.raw.img_4, 256, 128)); 85 | mPool.submit(new LoadBitmapRunnable(mImageView5, R.raw.img_5, 128, 256)); 86 | mPool.submit(new LoadBitmapRunnable(mImageView6, R.raw.img_6, 256, 128)); 87 | 88 | } 89 | 90 | @Override 91 | public void onPause() { 92 | super.onPause(); 93 | mPool.shutdownNow(); 94 | mPaused = true; 95 | } 96 | 97 | @Override 98 | public void onResume() { 99 | super.onResume(); 100 | mPaused = false; 101 | } 102 | 103 | private class LoadBitmapRunnable implements Runnable { 104 | 105 | private int mResourceId; 106 | private int mTargetWidth, mTargetHeight; 107 | private OkulusImageView mImageView; 108 | 109 | public LoadBitmapRunnable(OkulusImageView imageView, int resourceId, int targetWidth, int targetheight) { 110 | 111 | mImageView = imageView; 112 | mResourceId = resourceId; 113 | mTargetWidth = targetWidth; 114 | mTargetHeight = targetheight; 115 | } 116 | 117 | @Override 118 | public void run() { 119 | 120 | final Bitmap bitmap = RawBitmapManager.INSTANCE.getBitmap( 121 | mImageView.getContext(), 122 | mResourceId, 123 | mTargetWidth, 124 | mTargetHeight); 125 | 126 | if (!mPaused && bitmap != null) { 127 | mHandler.post(new Runnable() { 128 | @Override 129 | public void run() { 130 | mImageView.setImageBitmap(bitmap); 131 | } 132 | }); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/src/main/java/com/vinaysshenoy/okulusdemo/utils/RawBitmapManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Vinay S Shenoy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vinaysshenoy.okulusdemo.utils; 18 | 19 | import android.content.Context; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.os.Build; 23 | import android.support.v4.util.LruCache; 24 | 25 | /** 26 | * Class to hold the raw bitmaps in a cache 27 | *

28 | * Created by vinay.shenoy on 24/07/14. 29 | */ 30 | public class RawBitmapManager { 31 | 32 | public static final RawBitmapManager INSTANCE = new RawBitmapManager(); 33 | 34 | public static final int CACHE_SIZE = 5 * 1024 * 1024; //2 MB cache 35 | 36 | private LruCache mRawBitmapLruCache = new LruCache(CACHE_SIZE) { 37 | 38 | @Override 39 | protected int sizeOf(final Integer key, final Bitmap value) { 40 | 41 | if (Build.VERSION.SDK_INT >= 19) { 42 | return value.getAllocationByteCount(); 43 | } else if (Build.VERSION.SDK_INT >= 12) { 44 | return value.getByteCount(); 45 | } else { 46 | return value.getRowBytes() * value.getHeight(); 47 | } 48 | } 49 | }; 50 | 51 | /** 52 | * Gets a Bitmap from the cache, reading from the resources if not available in the cache 53 | *

54 | * This method should be called from a separate thread, since it can read from the resources 55 | */ 56 | public Bitmap getBitmap(Context context, int resourceId, int targetWidth, int targetHeight) { 57 | 58 | if (mRawBitmapLruCache.get(resourceId) != null) { 59 | return mRawBitmapLruCache.get(resourceId); 60 | 61 | } else { 62 | Bitmap readBitmap = readScaledBitmapFromResources(context, resourceId, targetWidth, targetHeight); 63 | 64 | if (readBitmap != null) { 65 | mRawBitmapLruCache.put(resourceId, readBitmap); 66 | } 67 | 68 | return readBitmap; 69 | 70 | } 71 | } 72 | 73 | private Bitmap readScaledBitmapFromResources(final Context context, final int resourceId, final int targetWidth, final int targetHeight) { 74 | 75 | final BitmapFactory.Options options = new BitmapFactory.Options(); 76 | options.inJustDecodeBounds = true; 77 | BitmapFactory.decodeResource(context.getResources(), resourceId, options); 78 | options.inJustDecodeBounds = false; 79 | options.inSampleSize = 1; 80 | return BitmapFactory.decodeResource(context.getResources(), resourceId, options); 81 | } 82 | 83 | private int calculateInSampleSize( 84 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 85 | // Raw height and width of image 86 | final int height = options.outHeight; 87 | final int width = options.outWidth; 88 | int inSampleSize = 1; 89 | 90 | if (height > reqHeight || width > reqWidth) { 91 | 92 | final int halfHeight = height / 2; 93 | final int halfWidth = width / 2; 94 | 95 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 96 | // height and width larger than the requested height and width. 97 | while ((halfHeight / inSampleSize) > reqHeight 98 | && (halfWidth / inSampleSize) > reqWidth) { 99 | inSampleSize *= 2; 100 | } 101 | } 102 | 103 | return inSampleSize; 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/img_for_uri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-nodpi/img_for_uri.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/img_res.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-nodpi/img_res.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_comparison.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 27 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_rounded_rectangles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 19 | 20 | 27 | 28 | 37 | 38 | 46 | 47 | 55 | 56 | 66 | 67 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_image_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 |

5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/raw/img_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_1.jpg -------------------------------------------------------------------------------- /app/src/main/res/raw/img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_2.jpg -------------------------------------------------------------------------------- /app/src/main/res/raw/img_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_3.jpg -------------------------------------------------------------------------------- /app/src/main/res/raw/img_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_4.jpg -------------------------------------------------------------------------------- /app/src/main/res/raw/img_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_5.jpg -------------------------------------------------------------------------------- /app/src/main/res/raw/img_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/app/src/main/res/raw/img_6.jpg -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 128dp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OkulusDemo 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.0.0-alpha3' 9 | // NOTE: Do not place your application dependencies here; they belong 10 | // in the individual module build.gradle files 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | } 18 | } 19 | 20 | task wrapper(type: Wrapper) { 21 | gradleVersion = '2.9' 22 | } 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinaysshenoy/okulus/533c8a86bf619ac83e0a749b521718e15b6a626a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 21 00:06:08 IST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | .DS_Store 26 | 27 | #IntelliJ 28 | .idea/ -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | okulus(BETA) 2 | ============ 3 | Custom Imageview for Android that allows for setting shapes/borders/shadows efficiently. This is currently in Beta as some minor issues are getting ironed out. 4 | 5 | Demo app is coming on the Play Store soon! 6 | 7 | The basic concept for drawing Bitmaps with a shape without creating a new Bitmap is based on [Romain Guy](https://plus.google.com/+RomainGuy)'s article on drawing [Bitmaps with Rounded Corners](http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/). 8 | 9 | ### Usage 10 | 1. Clone the repository and add the library to your project. 11 | 12 | 2. Add the custom namespace attribute to your layout 13 | ```xml 14 | xmlns:okulus="http://schemas.android.com/apk/res/com.vinaysshenoy.okulus 15 | ``` 16 | 17 | 3. Add `OkulusImageView` to your layout and set the attributes you need 18 | ```xml 19 | 30 | ``` 31 | 32 | 4. Call `setImageBitmap()` and you're done! 33 | ```java 34 | OkulusImageView imageView = findViewById(R.id.image_1); 35 | imageView.setImageBitmap(bitmap); 36 | ``` 37 | 38 | ### Custom Attributes 39 | 1. `cornerRadius(dimension)` - Sets the corner radius used for adding the rounded corners. Set it to 50% of the width(for a square image) to make it a full circle. Default `0dp`. 40 | 2. `fullCircle(boolean)` - If this is set to `true`, the entire Bitmap will be drawn as a circle. The width and height will be set to whichever is smaller among them, and `cornerRadius` attribute will be ignored. Default `false`. 41 | 3. `borderWidth(dimension)` - Sets the width of the border to be drawn. Will be capped at `5dp`. 42 | 4. `borderColor(color)` - Sets the color of the border to draw. Default color is `#FF000000`. 43 | 5. `shadowWidth(dimension)` - Sets the width of the shadow to draw. Will be capped at `3dp`. 44 | 6. `shadowColor(color)` - Sets the color of the shadow to draw. Default is `#B3444444`. 45 | 7. `shadowRadius(float)` - Defines how sharp the shadow is. Set it to a small value(~0.5) for sharp shadows, and larger values for soft shadows. Default `0.5` 46 | 8. `touchSelectorColor(color)` - Defines the colour of the color overlayed on the view when it is touched. This is ignored if `touchSelectorEnabled` is `false`. Default `#66444444` 47 | 9. `touchSelectorEnabled(boolean)` - Defines whether the touch selectors should be drawn or not. Default is `false` 48 | 49 | ### Pros 50 | 1. No extra memory used for creating the reshaped Bitmap 51 | 2. Zero overdraw 52 | 3. Any combination of shapes - Rounded Rects, Rects, Squares, Circles are possible with borders + shadow 53 | 54 | ### Limitations 55 | 1. Supports only fixed dimensions. `wrap_content` cannot be used. 56 | 2. Does not respect the `scaleType` attribute of `ImageView`. Scaled Bitmaps need to be provided. 57 | 3. Shadows cannot be used without borders 58 | 4. Supports only the `setImageBitmap` method. 59 | 5. Attributes can only be set through XML 60 | 6. Shadows are currently drawn to the right and bottom of the View and cannot be changed. 61 | 62 | ## Roadmap 63 | ### Version 1.0 64 | 1. Adding `get()` and `set()` attributes for `OkulusImageView` for setting attributes through code 65 | 2. Respecting ImageView's `scaleType` attribute 66 | 67 | ### Future(in descending order of priority) 68 | 1. Adding support for `wrap_content` 69 | 2. Adding support for any configuration of shadows 70 | 3. Adding support for `setImageUri`, `setImageResource` and `setImageDrawable` 71 | 4. Adding support for color filters to easily configure effects like Sepia, Grayscale etc. 72 | 5. Adding support for Image transitions when changing the image content 73 | 6. Adding support for custom shapes 74 | 7. ? 75 | 76 | ### License 77 | Copyright 2014 Vinay S Shenoy 78 | 79 | Licensed under the Apache License, Version 2.0 (the "License"); 80 | you may not use this file except in compliance with the License. 81 | You may obtain a copy of the License at 82 | 83 | http://www.apache.org/licenses/LICENSE-2.0 84 | 85 | Unless required by applicable law or agreed to in writing, software 86 | distributed under the License is distributed on an "AS IS" BASIS, 87 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 88 | See the License for the specific language governing permissions and 89 | limitations under the License. 90 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.2' 6 | 7 | defaultConfig { 8 | minSdkVersion 8 9 | targetSdkVersion 23 10 | } 11 | } 12 | 13 | apply from: 'maventask.gradle' 14 | 15 | dependencies { 16 | compile fileTree(dir: 'libs', include: ['*.jar']) 17 | compile 'com.android.support:support-v4:23.1.1' 18 | } 19 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=0.9.1 2 | VERSION_CODE=2 3 | GROUP=com.vinaysshenoy 4 | POM_DESCRIPTION=Awesome Image Effects for Android 5 | POM_URL=https://github.com/vinaysshenoy/okulus 6 | POM_SCM_URL=https://github.com/vinaysshenoy/okulus 7 | POM_SCM_CONNECTION=scm:git@github.com:vinaysshenoy/okulus.git 8 | POM_SCM_DEV_CONNECTION=scm:git@github.com:vinaysshenoy/okulus.git 9 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 10 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 11 | POM_LICENCE_DIST=repo 12 | POM_DEVELOPER_ID=vinaysshenoy 13 | POM_DEVELOPER_NAME=Vinay S Shenoy 14 | POM_PACKAGING=aar 15 | POM_NAME=Okulus 16 | POM_ARTIFACT_ID=okulus -------------------------------------------------------------------------------- /library/maventask.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'signing' 3 | 4 | version = VERSION_NAME 5 | group = GROUP 6 | 7 | def isReleaseBuild() { 8 | return !version.contains("SNAPSHOT") 9 | } 10 | 11 | def sonatypeRepositoryUrl 12 | if (isReleaseBuild()) { 13 | sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 14 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 15 | println("RELEASE") 16 | } else { 17 | sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 18 | : "https://oss.sonatype.org/content/repositories/snapshots/" 19 | println("SNAPSHOT") 20 | } 21 | 22 | def getRepositoryUsername() { 23 | return hasProperty('nexusUsername') ? nexusUsername : "" 24 | } 25 | 26 | def getRepositoryPassword() { 27 | return hasProperty('nexusPassword') ? nexusPassword : "" 28 | } 29 | 30 | afterEvaluate { project -> 31 | uploadArchives { 32 | repositories { 33 | mavenDeployer { 34 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 35 | 36 | pom.artifactId = POM_ARTIFACT_ID 37 | 38 | repository(url: sonatypeRepositoryUrl) { 39 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 40 | } 41 | 42 | pom.project { 43 | name POM_NAME 44 | packaging POM_PACKAGING 45 | description POM_DESCRIPTION 46 | url POM_URL 47 | 48 | scm { 49 | url POM_SCM_URL 50 | connection POM_SCM_CONNECTION 51 | developerConnection POM_SCM_DEV_CONNECTION 52 | } 53 | 54 | licenses { 55 | license { 56 | name POM_LICENCE_NAME 57 | url POM_LICENCE_URL 58 | distribution POM_LICENCE_DIST 59 | } 60 | } 61 | 62 | developers { 63 | developer { 64 | id POM_DEVELOPER_ID 65 | name POM_DEVELOPER_NAME 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | signing { 74 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 75 | sign configurations.archives 76 | } 77 | 78 | task androidJavadocs(type: Javadoc) { 79 | source = android.sourceSets.main.java.srcDirs 80 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 81 | } 82 | 83 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 84 | classifier = 'javadoc' 85 | from androidJavadocs.destinationDir 86 | } 87 | 88 | task androidSourcesJar(type: Jar) { 89 | classifier = 'sources' 90 | from android.sourceSets.main.java.sourceFiles 91 | } 92 | 93 | artifacts { 94 | archives androidSourcesJar 95 | archives androidJavadocsJar 96 | } 97 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------