Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;

import static android.content.ContentResolver.SCHEME_FILE;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;

class AssetBitmapHunter extends BitmapHunter {
class AssetRequestHandler extends RequestHandler {
protected static final String ANDROID_ASSET = "android_asset";
private static final int ASSET_PREFIX_LENGTH =
(SCHEME_FILE + ":///" + ANDROID_ASSET + "/").length();

private final AssetManager assetManager;

public AssetBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
Stats stats, Action action) {
super(picasso, dispatcher, cache, stats, action);
public AssetRequestHandler(Context context) {
assetManager = context.getAssets();
}

@Override Bitmap decode(Request data) throws IOException {
String filePath = data.uri.toString().substring(ASSET_PREFIX_LENGTH);
return decodeAsset(filePath);
@Override public boolean canHandleRequest(Request data) {
Uri uri = data.uri;
return (SCHEME_FILE.equals(uri.getScheme())
&& !uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0)));
}

@Override Picasso.LoadedFrom getLoadedFrom() {
return DISK;
@Override public Result load(Request data) throws IOException {
String filePath = data.uri.toString().substring(ASSET_PREFIX_LENGTH);
return new Result(decodeAsset(data, filePath), DISK);
}

Bitmap decodeAsset(String filePath) throws IOException {
Bitmap decodeAsset(Request data, String filePath) throws IOException {
final BitmapFactory.Options options = createBitmapOptions(data);
if (requiresInSampleSize(options)) {
InputStream is = null;
Expand Down
114 changes: 32 additions & 82 deletions picasso/src/main/java/com/squareup/picasso/BitmapHunter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,16 @@
*/
package com.squareup.picasso;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.NetworkInfo;
import android.net.Uri;
import android.provider.MediaStore;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
import static android.provider.ContactsContract.Contacts;
import static com.squareup.picasso.AssetBitmapHunter.ANDROID_ASSET;
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
import static com.squareup.picasso.Utils.OWNER_HUNTER;
import static com.squareup.picasso.Utils.VERB_DECODED;
Expand All @@ -44,8 +35,7 @@
import static com.squareup.picasso.Utils.getLogIdsForHunter;
import static com.squareup.picasso.Utils.log;

abstract class BitmapHunter implements Runnable {

class BitmapHunter implements Runnable {
/**
* Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
* this will only ever happen in background threads we help avoid excessive memory thrashing as
Expand All @@ -66,6 +56,7 @@ abstract class BitmapHunter implements Runnable {
final String key;
final Request data;
final boolean skipMemoryCache;
final RequestHandler requestHandler;

Action action;
List<Action> actions;
Expand All @@ -74,22 +65,22 @@ abstract class BitmapHunter implements Runnable {
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifRotation; // Determined during decoding of original resource.
int retryCount;

BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,
RequestHandler requestHandler) {
this.picasso = picasso;
this.dispatcher = dispatcher;
this.cache = cache;
this.stats = stats;
this.key = action.getKey();
this.data = action.getRequest();
this.skipMemoryCache = action.skipCache;
this.requestHandler = requestHandler;
this.retryCount = requestHandler.getRetryCount();
this.action = action;
}

protected void setExifRotation(int exifRotation) {
this.exifRotation = exifRotation;
}

@Override public void run() {
try {
updateThreadName(data);
Expand Down Expand Up @@ -124,10 +115,8 @@ protected void setExifRotation(int exifRotation) {
}
}

abstract Bitmap decode(Request data) throws IOException;

Bitmap hunt() throws IOException {
Bitmap bitmap;
Bitmap bitmap = null;

if (!skipMemoryCache) {
bitmap = cache.get(key);
Expand All @@ -141,7 +130,13 @@ Bitmap hunt() throws IOException {
}
}

bitmap = decode(data);
data.loadFromLocalCacheOnly = (retryCount == 0);
RequestHandler.Result result = requestHandler.load(data);
if (result != null) {
bitmap = result.getBitmap();
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
}

if (bitmap != null) {
if (picasso.loggingEnabled) {
Expand Down Expand Up @@ -227,11 +222,16 @@ boolean shouldSkipMemoryCache() {
}

boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
return false;
boolean hasRetries = retryCount > 0;
if (!hasRetries) {
return false;
}
retryCount--;
return requestHandler.shouldRetry(airplaneMode, info);
}

boolean supportsReplay() {
return false;
return requestHandler.supportsReplay();
}

Bitmap getResult() {
Expand Down Expand Up @@ -276,70 +276,20 @@ static void updateThreadName(Request data) {
Thread.currentThread().setName(builder.toString());
}

static BitmapHunter forRequest(Context context, Picasso picasso, Dispatcher dispatcher,
Cache cache, Stats stats, Action action, Downloader downloader) {
if (action.getRequest().resourceId != 0) {
return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
}
Uri uri = action.getRequest().uri;
String scheme = uri.getScheme();
if (SCHEME_CONTENT.equals(scheme)) {
if (Contacts.CONTENT_URI.getHost().equals(uri.getHost()) //
&& !uri.getPathSegments().contains(Contacts.Photo.CONTENT_DIRECTORY)) {
return new ContactsPhotoBitmapHunter(context, picasso, dispatcher, cache, stats, action);
} else if (MediaStore.AUTHORITY.equals(uri.getAuthority())) {
return new MediaStoreBitmapHunter(context, picasso, dispatcher, cache, stats, action);
} else {
return new ContentStreamBitmapHunter(context, picasso, dispatcher, cache, stats, action);
}
} else if (SCHEME_FILE.equals(scheme)) {
if (!uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0))) {
return new AssetBitmapHunter(context, picasso, dispatcher, cache, stats, action);
}
return new FileBitmapHunter(context, picasso, dispatcher, cache, stats, action);
} else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
} else {
return new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
}
}
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am so glad this is almost dead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\o/

Cache cache, Stats stats, Action action) {
Request request = action.getRequest();

/**
* Lazily create {@link android.graphics.BitmapFactory.Options} based in given
* {@link com.squareup.picasso.Request}, only instantiating them if needed.
*/
static BitmapFactory.Options createBitmapOptions(Request data) {
final boolean justBounds = data.hasSize();
final boolean hasConfig = data.config != null;
BitmapFactory.Options options = null;
if (justBounds || hasConfig) {
options = new BitmapFactory.Options();
options.inJustDecodeBounds = justBounds;
if (hasConfig) {
options.inPreferredConfig = data.config;
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
final int count = requestHandlers.size();
for (int i = 0; i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return options;
}

static boolean requiresInSampleSize(BitmapFactory.Options options) {
return options != null && options.inJustDecodeBounds;
}

static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options);
}

static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height,
BitmapFactory.Options options) {
int sampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = (int) Math.floor((float) height / (float) reqHeight);
final int widthRatio = (int) Math.floor((float) width / (float) reqWidth);
sampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
throw new IllegalStateException("Unrecognized type of request: " + request);
}

static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
import java.io.IOException;
import java.io.InputStream;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;

class ContactsPhotoBitmapHunter extends BitmapHunter {
class ContactsPhotoRequestHandler extends RequestHandler {
/** A lookup uri (e.g. content://com.android.contacts/contacts/lookup/3570i61d948d30808e537) */
private static final int ID_LOOKUP = 1;
/** A contact thumbnail uri (e.g. content://com.android.contacts/contacts/38/photo) */
Expand All @@ -58,29 +59,30 @@ class ContactsPhotoBitmapHunter extends BitmapHunter {

final Context context;

ContactsPhotoBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
Stats stats, Action action) {
super(picasso, dispatcher, cache, stats, action);
ContactsPhotoRequestHandler(Context context) {
this.context = context;
}

@Override Bitmap decode(Request data) throws IOException {
@Override public boolean canHandleRequest(Request data) {
final Uri uri = data.uri;
return (SCHEME_CONTENT.equals(uri.getScheme())
&& ContactsContract.Contacts.CONTENT_URI.getHost().equals(uri.getHost())
&& !uri.getPathSegments().contains(ContactsContract.Contacts.Photo.CONTENT_DIRECTORY));
}

@Override public Result load(Request data) throws IOException {
InputStream is = null;
try {
is = getInputStream();
return decodeStream(is, data);
is = getInputStream(data);
return new Result(decodeStream(is, data), DISK);
} finally {
Utils.closeQuietly(is);
}
}

@Override Picasso.LoadedFrom getLoadedFrom() {
return DISK;
}

private InputStream getInputStream() throws IOException {
private InputStream getInputStream(Request data) throws IOException {
ContentResolver contentResolver = context.getContentResolver();
Uri uri = getData().uri;
Uri uri = data.uri;
switch (matcher.match(uri)) {
case ID_LOOKUP:
uri = ContactsContract.Contacts.lookupContact(contentResolver, uri);
Expand Down Expand Up @@ -108,7 +110,7 @@ private Bitmap decodeStream(InputStream stream, Request data) throws IOException
}
final BitmapFactory.Options options = createBitmapOptions(data);
if (requiresInSampleSize(options)) {
InputStream is = getInputStream();
InputStream is = getInputStream(data);
try {
BitmapFactory.decodeStream(is, null, options);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,22 @@
import java.io.IOException;
import java.io.InputStream;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;

class ContentStreamBitmapHunter extends BitmapHunter {
class ContentStreamRequestHandler extends RequestHandler {
final Context context;

ContentStreamBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
Stats stats, Action action) {
super(picasso, dispatcher, cache, stats, action);
ContentStreamRequestHandler(Context context) {
this.context = context;
}

@Override Bitmap decode(Request data)
throws IOException {
return decodeContentStream(data);
@Override public boolean canHandleRequest(Request data) {
return SCHEME_CONTENT.equals(data.uri.getScheme());
}

@Override Picasso.LoadedFrom getLoadedFrom() {
return DISK;
@Override public Result load(Request data) throws IOException {
return new Result(decodeContentStream(data), DISK);
}

protected Bitmap decodeContentStream(Request data) throws IOException {
Expand Down
2 changes: 1 addition & 1 deletion picasso/src/main/java/com/squareup/picasso/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ void performSubmit(Action action) {
return;
}

hunter = forRequest(context, action.getPicasso(), this, cache, stats, action, downloader);
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
failedActions.remove(action.getTarget());
Expand Down
Loading