1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/com/squareup/picasso/BitmapHunter.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,357 @@ 1.4 +/* 1.5 + * Copyright (C) 2013 Square, Inc. 1.6 + * 1.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.8 + * you may not use this file except in compliance with the License. 1.9 + * You may obtain a copy of the License at 1.10 + * 1.11 + * http://www.apache.org/licenses/LICENSE-2.0 1.12 + * 1.13 + * Unless required by applicable law or agreed to in writing, software 1.14 + * distributed under the License is distributed on an "AS IS" BASIS, 1.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.16 + * See the License for the specific language governing permissions and 1.17 + * limitations under the License. 1.18 + */ 1.19 +package com.squareup.picasso; 1.20 + 1.21 +import android.content.Context; 1.22 +import android.graphics.Bitmap; 1.23 +import android.graphics.BitmapFactory; 1.24 +import android.graphics.Matrix; 1.25 +import android.net.NetworkInfo; 1.26 +import android.net.Uri; 1.27 +import android.provider.MediaStore; 1.28 +import java.io.IOException; 1.29 +import java.io.PrintWriter; 1.30 +import java.io.StringWriter; 1.31 +import java.util.ArrayList; 1.32 +import java.util.List; 1.33 +import java.util.concurrent.Future; 1.34 + 1.35 +import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; 1.36 +import static android.content.ContentResolver.SCHEME_CONTENT; 1.37 +import static android.content.ContentResolver.SCHEME_FILE; 1.38 +import static android.provider.ContactsContract.Contacts; 1.39 +import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; 1.40 + 1.41 +abstract class BitmapHunter implements Runnable { 1.42 + 1.43 + /** 1.44 + * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since 1.45 + * this will only ever happen in background threads we help avoid excessive memory thrashing as 1.46 + * well as potential OOMs. Shamelessly stolen from Volley. 1.47 + */ 1.48 + private static final Object DECODE_LOCK = new Object(); 1.49 + private static final String ANDROID_ASSET = "android_asset"; 1.50 + protected static final int ASSET_PREFIX_LENGTH = 1.51 + (SCHEME_FILE + ":///" + ANDROID_ASSET + "/").length(); 1.52 + 1.53 + final Picasso picasso; 1.54 + final Dispatcher dispatcher; 1.55 + final Cache cache; 1.56 + final Stats stats; 1.57 + final String key; 1.58 + final Request data; 1.59 + final List<Action> actions; 1.60 + final boolean skipMemoryCache; 1.61 + 1.62 + Bitmap result; 1.63 + Future<?> future; 1.64 + Picasso.LoadedFrom loadedFrom; 1.65 + Exception exception; 1.66 + int exifRotation; // Determined during decoding of original resource. 1.67 + 1.68 + BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { 1.69 + this.picasso = picasso; 1.70 + this.dispatcher = dispatcher; 1.71 + this.cache = cache; 1.72 + this.stats = stats; 1.73 + this.key = action.getKey(); 1.74 + this.data = action.getData(); 1.75 + this.skipMemoryCache = action.skipCache; 1.76 + this.actions = new ArrayList<Action>(4); 1.77 + attach(action); 1.78 + } 1.79 + 1.80 + protected void setExifRotation(int exifRotation) { 1.81 + this.exifRotation = exifRotation; 1.82 + } 1.83 + 1.84 + @Override public void run() { 1.85 + try { 1.86 + Thread.currentThread().setName(Utils.THREAD_PREFIX + data.getName()); 1.87 + 1.88 + result = hunt(); 1.89 + 1.90 + if (result == null) { 1.91 + dispatcher.dispatchFailed(this); 1.92 + } else { 1.93 + dispatcher.dispatchComplete(this); 1.94 + } 1.95 + } catch (Downloader.ResponseException e) { 1.96 + exception = e; 1.97 + dispatcher.dispatchFailed(this); 1.98 + } catch (IOException e) { 1.99 + exception = e; 1.100 + dispatcher.dispatchRetry(this); 1.101 + } catch (OutOfMemoryError e) { 1.102 + StringWriter writer = new StringWriter(); 1.103 + stats.createSnapshot().dump(new PrintWriter(writer)); 1.104 + exception = new RuntimeException(writer.toString(), e); 1.105 + dispatcher.dispatchFailed(this); 1.106 + } catch (Exception e) { 1.107 + exception = e; 1.108 + dispatcher.dispatchFailed(this); 1.109 + } finally { 1.110 + Thread.currentThread().setName(Utils.THREAD_IDLE_NAME); 1.111 + } 1.112 + } 1.113 + 1.114 + abstract Bitmap decode(Request data) throws IOException; 1.115 + 1.116 + Bitmap hunt() throws IOException { 1.117 + Bitmap bitmap; 1.118 + 1.119 + if (!skipMemoryCache) { 1.120 + bitmap = cache.get(key); 1.121 + if (bitmap != null) { 1.122 + stats.dispatchCacheHit(); 1.123 + loadedFrom = MEMORY; 1.124 + return bitmap; 1.125 + } 1.126 + } 1.127 + 1.128 + bitmap = decode(data); 1.129 + 1.130 + if (bitmap != null) { 1.131 + stats.dispatchBitmapDecoded(bitmap); 1.132 + if (data.needsTransformation() || exifRotation != 0) { 1.133 + synchronized (DECODE_LOCK) { 1.134 + if (data.needsMatrixTransform() || exifRotation != 0) { 1.135 + bitmap = transformResult(data, bitmap, exifRotation); 1.136 + } 1.137 + if (data.hasCustomTransformations()) { 1.138 + bitmap = applyCustomTransformations(data.transformations, bitmap); 1.139 + } 1.140 + } 1.141 + stats.dispatchBitmapTransformed(bitmap); 1.142 + } 1.143 + } 1.144 + 1.145 + return bitmap; 1.146 + } 1.147 + 1.148 + void attach(Action action) { 1.149 + actions.add(action); 1.150 + } 1.151 + 1.152 + void detach(Action action) { 1.153 + actions.remove(action); 1.154 + } 1.155 + 1.156 + boolean cancel() { 1.157 + return actions.isEmpty() && future != null && future.cancel(false); 1.158 + } 1.159 + 1.160 + boolean isCancelled() { 1.161 + return future != null && future.isCancelled(); 1.162 + } 1.163 + 1.164 + boolean shouldSkipMemoryCache() { 1.165 + return skipMemoryCache; 1.166 + } 1.167 + 1.168 + boolean shouldRetry(boolean airplaneMode, NetworkInfo info) { 1.169 + return false; 1.170 + } 1.171 + 1.172 + Bitmap getResult() { 1.173 + return result; 1.174 + } 1.175 + 1.176 + String getKey() { 1.177 + return key; 1.178 + } 1.179 + 1.180 + Request getData() { 1.181 + return data; 1.182 + } 1.183 + 1.184 + List<Action> getActions() { 1.185 + return actions; 1.186 + } 1.187 + 1.188 + Exception getException() { 1.189 + return exception; 1.190 + } 1.191 + 1.192 + Picasso.LoadedFrom getLoadedFrom() { 1.193 + return loadedFrom; 1.194 + } 1.195 + 1.196 + static BitmapHunter forRequest(Context context, Picasso picasso, Dispatcher dispatcher, 1.197 + Cache cache, Stats stats, Action action, Downloader downloader) { 1.198 + if (action.getData().resourceId != 0) { 1.199 + return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.200 + } 1.201 + Uri uri = action.getData().uri; 1.202 + String scheme = uri.getScheme(); 1.203 + if (SCHEME_CONTENT.equals(scheme)) { 1.204 + if (Contacts.CONTENT_URI.getHost().equals(uri.getHost()) // 1.205 + && !uri.getPathSegments().contains(Contacts.Photo.CONTENT_DIRECTORY)) { 1.206 + return new ContactsPhotoBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.207 + } else if (MediaStore.AUTHORITY.equals(uri.getAuthority())) { 1.208 + return new MediaStoreBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.209 + } else { 1.210 + return new ContentStreamBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.211 + } 1.212 + } else if (SCHEME_FILE.equals(scheme)) { 1.213 + if (!uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0))) { 1.214 + return new AssetBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.215 + } 1.216 + return new FileBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.217 + } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { 1.218 + return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action); 1.219 + } else { 1.220 + return new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader); 1.221 + } 1.222 + } 1.223 + 1.224 + static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) { 1.225 + calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options); 1.226 + } 1.227 + 1.228 + static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height, 1.229 + BitmapFactory.Options options) { 1.230 + int sampleSize = 1; 1.231 + if (height > reqHeight || width > reqWidth) { 1.232 + final int heightRatio = Math.round((float) height / (float) reqHeight); 1.233 + final int widthRatio = Math.round((float) width / (float) reqWidth); 1.234 + sampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 1.235 + } 1.236 + 1.237 + options.inSampleSize = sampleSize; 1.238 + options.inJustDecodeBounds = false; 1.239 + } 1.240 + 1.241 + static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) { 1.242 + for (int i = 0, count = transformations.size(); i < count; i++) { 1.243 + final Transformation transformation = transformations.get(i); 1.244 + Bitmap newResult = transformation.transform(result); 1.245 + 1.246 + if (newResult == null) { 1.247 + final StringBuilder builder = new StringBuilder() // 1.248 + .append("Transformation ") 1.249 + .append(transformation.key()) 1.250 + .append(" returned null after ") 1.251 + .append(i) 1.252 + .append(" previous transformation(s).\n\nTransformation list:\n"); 1.253 + for (Transformation t : transformations) { 1.254 + builder.append(t.key()).append('\n'); 1.255 + } 1.256 + Picasso.HANDLER.post(new Runnable() { 1.257 + @Override public void run() { 1.258 + throw new NullPointerException(builder.toString()); 1.259 + } 1.260 + }); 1.261 + return null; 1.262 + } 1.263 + 1.264 + if (newResult == result && result.isRecycled()) { 1.265 + Picasso.HANDLER.post(new Runnable() { 1.266 + @Override public void run() { 1.267 + throw new IllegalStateException("Transformation " 1.268 + + transformation.key() 1.269 + + " returned input Bitmap but recycled it."); 1.270 + } 1.271 + }); 1.272 + return null; 1.273 + } 1.274 + 1.275 + // If the transformation returned a new bitmap ensure they recycled the original. 1.276 + if (newResult != result && !result.isRecycled()) { 1.277 + Picasso.HANDLER.post(new Runnable() { 1.278 + @Override public void run() { 1.279 + throw new IllegalStateException("Transformation " 1.280 + + transformation.key() 1.281 + + " mutated input Bitmap but failed to recycle the original."); 1.282 + } 1.283 + }); 1.284 + return null; 1.285 + } 1.286 + 1.287 + result = newResult; 1.288 + } 1.289 + return result; 1.290 + } 1.291 + 1.292 + static Bitmap transformResult(Request data, Bitmap result, int exifRotation) { 1.293 + int inWidth = result.getWidth(); 1.294 + int inHeight = result.getHeight(); 1.295 + 1.296 + int drawX = 0; 1.297 + int drawY = 0; 1.298 + int drawWidth = inWidth; 1.299 + int drawHeight = inHeight; 1.300 + 1.301 + Matrix matrix = new Matrix(); 1.302 + 1.303 + if (data.needsMatrixTransform()) { 1.304 + int targetWidth = data.targetWidth; 1.305 + int targetHeight = data.targetHeight; 1.306 + 1.307 + float targetRotation = data.rotationDegrees; 1.308 + if (targetRotation != 0) { 1.309 + if (data.hasRotationPivot) { 1.310 + matrix.setRotate(targetRotation, data.rotationPivotX, data.rotationPivotY); 1.311 + } else { 1.312 + matrix.setRotate(targetRotation); 1.313 + } 1.314 + } 1.315 + 1.316 + if (data.centerCrop) { 1.317 + float widthRatio = targetWidth / (float) inWidth; 1.318 + float heightRatio = targetHeight / (float) inHeight; 1.319 + float scale; 1.320 + if (widthRatio > heightRatio) { 1.321 + scale = widthRatio; 1.322 + int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio)); 1.323 + drawY = (inHeight - newSize) / 2; 1.324 + drawHeight = newSize; 1.325 + } else { 1.326 + scale = heightRatio; 1.327 + int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio)); 1.328 + drawX = (inWidth - newSize) / 2; 1.329 + drawWidth = newSize; 1.330 + } 1.331 + matrix.preScale(scale, scale); 1.332 + } else if (data.centerInside) { 1.333 + float widthRatio = targetWidth / (float) inWidth; 1.334 + float heightRatio = targetHeight / (float) inHeight; 1.335 + float scale = widthRatio < heightRatio ? widthRatio : heightRatio; 1.336 + matrix.preScale(scale, scale); 1.337 + } else if (targetWidth != 0 && targetHeight != 0 // 1.338 + && (targetWidth != inWidth || targetHeight != inHeight)) { 1.339 + // If an explicit target size has been specified and they do not match the results bounds, 1.340 + // pre-scale the existing matrix appropriately. 1.341 + float sx = targetWidth / (float) inWidth; 1.342 + float sy = targetHeight / (float) inHeight; 1.343 + matrix.preScale(sx, sy); 1.344 + } 1.345 + } 1.346 + 1.347 + if (exifRotation != 0) { 1.348 + matrix.preRotate(exifRotation); 1.349 + } 1.350 + 1.351 + Bitmap newResult = 1.352 + Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true); 1.353 + if (newResult != result) { 1.354 + result.recycle(); 1.355 + result = newResult; 1.356 + } 1.357 + 1.358 + return result; 1.359 + } 1.360 +}