mobile/android/thirdparty/com/squareup/picasso/BitmapHunter.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /*
michael@0 2 * Copyright (C) 2013 Square, Inc.
michael@0 3 *
michael@0 4 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 5 * you may not use this file except in compliance with the License.
michael@0 6 * You may obtain a copy of the License at
michael@0 7 *
michael@0 8 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 9 *
michael@0 10 * Unless required by applicable law or agreed to in writing, software
michael@0 11 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 13 * See the License for the specific language governing permissions and
michael@0 14 * limitations under the License.
michael@0 15 */
michael@0 16 package com.squareup.picasso;
michael@0 17
michael@0 18 import android.content.Context;
michael@0 19 import android.graphics.Bitmap;
michael@0 20 import android.graphics.BitmapFactory;
michael@0 21 import android.graphics.Matrix;
michael@0 22 import android.net.NetworkInfo;
michael@0 23 import android.net.Uri;
michael@0 24 import android.provider.MediaStore;
michael@0 25 import java.io.IOException;
michael@0 26 import java.io.PrintWriter;
michael@0 27 import java.io.StringWriter;
michael@0 28 import java.util.ArrayList;
michael@0 29 import java.util.List;
michael@0 30 import java.util.concurrent.Future;
michael@0 31
michael@0 32 import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
michael@0 33 import static android.content.ContentResolver.SCHEME_CONTENT;
michael@0 34 import static android.content.ContentResolver.SCHEME_FILE;
michael@0 35 import static android.provider.ContactsContract.Contacts;
michael@0 36 import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
michael@0 37
michael@0 38 abstract class BitmapHunter implements Runnable {
michael@0 39
michael@0 40 /**
michael@0 41 * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
michael@0 42 * this will only ever happen in background threads we help avoid excessive memory thrashing as
michael@0 43 * well as potential OOMs. Shamelessly stolen from Volley.
michael@0 44 */
michael@0 45 private static final Object DECODE_LOCK = new Object();
michael@0 46 private static final String ANDROID_ASSET = "android_asset";
michael@0 47 protected static final int ASSET_PREFIX_LENGTH =
michael@0 48 (SCHEME_FILE + ":///" + ANDROID_ASSET + "/").length();
michael@0 49
michael@0 50 final Picasso picasso;
michael@0 51 final Dispatcher dispatcher;
michael@0 52 final Cache cache;
michael@0 53 final Stats stats;
michael@0 54 final String key;
michael@0 55 final Request data;
michael@0 56 final List<Action> actions;
michael@0 57 final boolean skipMemoryCache;
michael@0 58
michael@0 59 Bitmap result;
michael@0 60 Future<?> future;
michael@0 61 Picasso.LoadedFrom loadedFrom;
michael@0 62 Exception exception;
michael@0 63 int exifRotation; // Determined during decoding of original resource.
michael@0 64
michael@0 65 BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
michael@0 66 this.picasso = picasso;
michael@0 67 this.dispatcher = dispatcher;
michael@0 68 this.cache = cache;
michael@0 69 this.stats = stats;
michael@0 70 this.key = action.getKey();
michael@0 71 this.data = action.getData();
michael@0 72 this.skipMemoryCache = action.skipCache;
michael@0 73 this.actions = new ArrayList<Action>(4);
michael@0 74 attach(action);
michael@0 75 }
michael@0 76
michael@0 77 protected void setExifRotation(int exifRotation) {
michael@0 78 this.exifRotation = exifRotation;
michael@0 79 }
michael@0 80
michael@0 81 @Override public void run() {
michael@0 82 try {
michael@0 83 Thread.currentThread().setName(Utils.THREAD_PREFIX + data.getName());
michael@0 84
michael@0 85 result = hunt();
michael@0 86
michael@0 87 if (result == null) {
michael@0 88 dispatcher.dispatchFailed(this);
michael@0 89 } else {
michael@0 90 dispatcher.dispatchComplete(this);
michael@0 91 }
michael@0 92 } catch (Downloader.ResponseException e) {
michael@0 93 exception = e;
michael@0 94 dispatcher.dispatchFailed(this);
michael@0 95 } catch (IOException e) {
michael@0 96 exception = e;
michael@0 97 dispatcher.dispatchRetry(this);
michael@0 98 } catch (OutOfMemoryError e) {
michael@0 99 StringWriter writer = new StringWriter();
michael@0 100 stats.createSnapshot().dump(new PrintWriter(writer));
michael@0 101 exception = new RuntimeException(writer.toString(), e);
michael@0 102 dispatcher.dispatchFailed(this);
michael@0 103 } catch (Exception e) {
michael@0 104 exception = e;
michael@0 105 dispatcher.dispatchFailed(this);
michael@0 106 } finally {
michael@0 107 Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
michael@0 108 }
michael@0 109 }
michael@0 110
michael@0 111 abstract Bitmap decode(Request data) throws IOException;
michael@0 112
michael@0 113 Bitmap hunt() throws IOException {
michael@0 114 Bitmap bitmap;
michael@0 115
michael@0 116 if (!skipMemoryCache) {
michael@0 117 bitmap = cache.get(key);
michael@0 118 if (bitmap != null) {
michael@0 119 stats.dispatchCacheHit();
michael@0 120 loadedFrom = MEMORY;
michael@0 121 return bitmap;
michael@0 122 }
michael@0 123 }
michael@0 124
michael@0 125 bitmap = decode(data);
michael@0 126
michael@0 127 if (bitmap != null) {
michael@0 128 stats.dispatchBitmapDecoded(bitmap);
michael@0 129 if (data.needsTransformation() || exifRotation != 0) {
michael@0 130 synchronized (DECODE_LOCK) {
michael@0 131 if (data.needsMatrixTransform() || exifRotation != 0) {
michael@0 132 bitmap = transformResult(data, bitmap, exifRotation);
michael@0 133 }
michael@0 134 if (data.hasCustomTransformations()) {
michael@0 135 bitmap = applyCustomTransformations(data.transformations, bitmap);
michael@0 136 }
michael@0 137 }
michael@0 138 stats.dispatchBitmapTransformed(bitmap);
michael@0 139 }
michael@0 140 }
michael@0 141
michael@0 142 return bitmap;
michael@0 143 }
michael@0 144
michael@0 145 void attach(Action action) {
michael@0 146 actions.add(action);
michael@0 147 }
michael@0 148
michael@0 149 void detach(Action action) {
michael@0 150 actions.remove(action);
michael@0 151 }
michael@0 152
michael@0 153 boolean cancel() {
michael@0 154 return actions.isEmpty() && future != null && future.cancel(false);
michael@0 155 }
michael@0 156
michael@0 157 boolean isCancelled() {
michael@0 158 return future != null && future.isCancelled();
michael@0 159 }
michael@0 160
michael@0 161 boolean shouldSkipMemoryCache() {
michael@0 162 return skipMemoryCache;
michael@0 163 }
michael@0 164
michael@0 165 boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
michael@0 166 return false;
michael@0 167 }
michael@0 168
michael@0 169 Bitmap getResult() {
michael@0 170 return result;
michael@0 171 }
michael@0 172
michael@0 173 String getKey() {
michael@0 174 return key;
michael@0 175 }
michael@0 176
michael@0 177 Request getData() {
michael@0 178 return data;
michael@0 179 }
michael@0 180
michael@0 181 List<Action> getActions() {
michael@0 182 return actions;
michael@0 183 }
michael@0 184
michael@0 185 Exception getException() {
michael@0 186 return exception;
michael@0 187 }
michael@0 188
michael@0 189 Picasso.LoadedFrom getLoadedFrom() {
michael@0 190 return loadedFrom;
michael@0 191 }
michael@0 192
michael@0 193 static BitmapHunter forRequest(Context context, Picasso picasso, Dispatcher dispatcher,
michael@0 194 Cache cache, Stats stats, Action action, Downloader downloader) {
michael@0 195 if (action.getData().resourceId != 0) {
michael@0 196 return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 197 }
michael@0 198 Uri uri = action.getData().uri;
michael@0 199 String scheme = uri.getScheme();
michael@0 200 if (SCHEME_CONTENT.equals(scheme)) {
michael@0 201 if (Contacts.CONTENT_URI.getHost().equals(uri.getHost()) //
michael@0 202 && !uri.getPathSegments().contains(Contacts.Photo.CONTENT_DIRECTORY)) {
michael@0 203 return new ContactsPhotoBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 204 } else if (MediaStore.AUTHORITY.equals(uri.getAuthority())) {
michael@0 205 return new MediaStoreBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 206 } else {
michael@0 207 return new ContentStreamBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 208 }
michael@0 209 } else if (SCHEME_FILE.equals(scheme)) {
michael@0 210 if (!uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0))) {
michael@0 211 return new AssetBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 212 }
michael@0 213 return new FileBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 214 } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
michael@0 215 return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
michael@0 216 } else {
michael@0 217 return new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
michael@0 218 }
michael@0 219 }
michael@0 220
michael@0 221 static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
michael@0 222 calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options);
michael@0 223 }
michael@0 224
michael@0 225 static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height,
michael@0 226 BitmapFactory.Options options) {
michael@0 227 int sampleSize = 1;
michael@0 228 if (height > reqHeight || width > reqWidth) {
michael@0 229 final int heightRatio = Math.round((float) height / (float) reqHeight);
michael@0 230 final int widthRatio = Math.round((float) width / (float) reqWidth);
michael@0 231 sampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
michael@0 232 }
michael@0 233
michael@0 234 options.inSampleSize = sampleSize;
michael@0 235 options.inJustDecodeBounds = false;
michael@0 236 }
michael@0 237
michael@0 238 static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
michael@0 239 for (int i = 0, count = transformations.size(); i < count; i++) {
michael@0 240 final Transformation transformation = transformations.get(i);
michael@0 241 Bitmap newResult = transformation.transform(result);
michael@0 242
michael@0 243 if (newResult == null) {
michael@0 244 final StringBuilder builder = new StringBuilder() //
michael@0 245 .append("Transformation ")
michael@0 246 .append(transformation.key())
michael@0 247 .append(" returned null after ")
michael@0 248 .append(i)
michael@0 249 .append(" previous transformation(s).\n\nTransformation list:\n");
michael@0 250 for (Transformation t : transformations) {
michael@0 251 builder.append(t.key()).append('\n');
michael@0 252 }
michael@0 253 Picasso.HANDLER.post(new Runnable() {
michael@0 254 @Override public void run() {
michael@0 255 throw new NullPointerException(builder.toString());
michael@0 256 }
michael@0 257 });
michael@0 258 return null;
michael@0 259 }
michael@0 260
michael@0 261 if (newResult == result && result.isRecycled()) {
michael@0 262 Picasso.HANDLER.post(new Runnable() {
michael@0 263 @Override public void run() {
michael@0 264 throw new IllegalStateException("Transformation "
michael@0 265 + transformation.key()
michael@0 266 + " returned input Bitmap but recycled it.");
michael@0 267 }
michael@0 268 });
michael@0 269 return null;
michael@0 270 }
michael@0 271
michael@0 272 // If the transformation returned a new bitmap ensure they recycled the original.
michael@0 273 if (newResult != result && !result.isRecycled()) {
michael@0 274 Picasso.HANDLER.post(new Runnable() {
michael@0 275 @Override public void run() {
michael@0 276 throw new IllegalStateException("Transformation "
michael@0 277 + transformation.key()
michael@0 278 + " mutated input Bitmap but failed to recycle the original.");
michael@0 279 }
michael@0 280 });
michael@0 281 return null;
michael@0 282 }
michael@0 283
michael@0 284 result = newResult;
michael@0 285 }
michael@0 286 return result;
michael@0 287 }
michael@0 288
michael@0 289 static Bitmap transformResult(Request data, Bitmap result, int exifRotation) {
michael@0 290 int inWidth = result.getWidth();
michael@0 291 int inHeight = result.getHeight();
michael@0 292
michael@0 293 int drawX = 0;
michael@0 294 int drawY = 0;
michael@0 295 int drawWidth = inWidth;
michael@0 296 int drawHeight = inHeight;
michael@0 297
michael@0 298 Matrix matrix = new Matrix();
michael@0 299
michael@0 300 if (data.needsMatrixTransform()) {
michael@0 301 int targetWidth = data.targetWidth;
michael@0 302 int targetHeight = data.targetHeight;
michael@0 303
michael@0 304 float targetRotation = data.rotationDegrees;
michael@0 305 if (targetRotation != 0) {
michael@0 306 if (data.hasRotationPivot) {
michael@0 307 matrix.setRotate(targetRotation, data.rotationPivotX, data.rotationPivotY);
michael@0 308 } else {
michael@0 309 matrix.setRotate(targetRotation);
michael@0 310 }
michael@0 311 }
michael@0 312
michael@0 313 if (data.centerCrop) {
michael@0 314 float widthRatio = targetWidth / (float) inWidth;
michael@0 315 float heightRatio = targetHeight / (float) inHeight;
michael@0 316 float scale;
michael@0 317 if (widthRatio > heightRatio) {
michael@0 318 scale = widthRatio;
michael@0 319 int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio));
michael@0 320 drawY = (inHeight - newSize) / 2;
michael@0 321 drawHeight = newSize;
michael@0 322 } else {
michael@0 323 scale = heightRatio;
michael@0 324 int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio));
michael@0 325 drawX = (inWidth - newSize) / 2;
michael@0 326 drawWidth = newSize;
michael@0 327 }
michael@0 328 matrix.preScale(scale, scale);
michael@0 329 } else if (data.centerInside) {
michael@0 330 float widthRatio = targetWidth / (float) inWidth;
michael@0 331 float heightRatio = targetHeight / (float) inHeight;
michael@0 332 float scale = widthRatio < heightRatio ? widthRatio : heightRatio;
michael@0 333 matrix.preScale(scale, scale);
michael@0 334 } else if (targetWidth != 0 && targetHeight != 0 //
michael@0 335 && (targetWidth != inWidth || targetHeight != inHeight)) {
michael@0 336 // If an explicit target size has been specified and they do not match the results bounds,
michael@0 337 // pre-scale the existing matrix appropriately.
michael@0 338 float sx = targetWidth / (float) inWidth;
michael@0 339 float sy = targetHeight / (float) inHeight;
michael@0 340 matrix.preScale(sx, sy);
michael@0 341 }
michael@0 342 }
michael@0 343
michael@0 344 if (exifRotation != 0) {
michael@0 345 matrix.preRotate(exifRotation);
michael@0 346 }
michael@0 347
michael@0 348 Bitmap newResult =
michael@0 349 Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true);
michael@0 350 if (newResult != result) {
michael@0 351 result.recycle();
michael@0 352 result = newResult;
michael@0 353 }
michael@0 354
michael@0 355 return result;
michael@0 356 }
michael@0 357 }

mercurial