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.

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

mercurial