1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/com/squareup/picasso/Picasso.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,522 @@ 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.Color; 1.24 +import android.net.Uri; 1.25 +import android.os.Handler; 1.26 +import android.os.Looper; 1.27 +import android.os.Message; 1.28 +import android.os.Process; 1.29 +import android.widget.ImageView; 1.30 +import java.io.File; 1.31 +import java.lang.ref.ReferenceQueue; 1.32 +import java.util.List; 1.33 +import java.util.Map; 1.34 +import java.util.WeakHashMap; 1.35 +import java.util.concurrent.ExecutorService; 1.36 + 1.37 +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 1.38 +import static com.squareup.picasso.Action.RequestWeakReference; 1.39 +import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE; 1.40 +import static com.squareup.picasso.Dispatcher.REQUEST_GCED; 1.41 +import static com.squareup.picasso.Utils.THREAD_PREFIX; 1.42 + 1.43 +/** 1.44 + * Image downloading, transformation, and caching manager. 1.45 + * <p/> 1.46 + * Use {@link #with(android.content.Context)} for the global singleton instance or construct your 1.47 + * own instance with {@link Builder}. 1.48 + */ 1.49 +public class Picasso { 1.50 + 1.51 + /** Callbacks for Picasso events. */ 1.52 + public interface Listener { 1.53 + /** 1.54 + * Invoked when an image has failed to load. This is useful for reporting image failures to a 1.55 + * remote analytics service, for example. 1.56 + */ 1.57 + void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception); 1.58 + } 1.59 + 1.60 + /** 1.61 + * A transformer that is called immediately before every request is submitted. This can be used to 1.62 + * modify any information about a request. 1.63 + * <p> 1.64 + * For example, if you use a CDN you can change the hostname for the image based on the current 1.65 + * location of the user in order to get faster download speeds. 1.66 + * <p> 1.67 + * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible 1.68 + * way at any time. 1.69 + */ 1.70 + public interface RequestTransformer { 1.71 + /** 1.72 + * Transform a request before it is submitted to be processed. 1.73 + * 1.74 + * @return The original request or a new request to replace it. Must not be null. 1.75 + */ 1.76 + Request transformRequest(Request request); 1.77 + 1.78 + /** A {@link RequestTransformer} which returns the original request. */ 1.79 + RequestTransformer IDENTITY = new RequestTransformer() { 1.80 + @Override public Request transformRequest(Request request) { 1.81 + return request; 1.82 + } 1.83 + }; 1.84 + } 1.85 + 1.86 + static final Handler HANDLER = new Handler(Looper.getMainLooper()) { 1.87 + @Override public void handleMessage(Message msg) { 1.88 + switch (msg.what) { 1.89 + case HUNTER_BATCH_COMPLETE: { 1.90 + @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj; 1.91 + for (BitmapHunter hunter : batch) { 1.92 + hunter.picasso.complete(hunter); 1.93 + } 1.94 + break; 1.95 + } 1.96 + case REQUEST_GCED: { 1.97 + Action action = (Action) msg.obj; 1.98 + action.picasso.cancelExistingRequest(action.getTarget()); 1.99 + break; 1.100 + } 1.101 + default: 1.102 + throw new AssertionError("Unknown handler message received: " + msg.what); 1.103 + } 1.104 + } 1.105 + }; 1.106 + 1.107 + static Picasso singleton = null; 1.108 + 1.109 + private final Listener listener; 1.110 + private final RequestTransformer requestTransformer; 1.111 + private final CleanupThread cleanupThread; 1.112 + 1.113 + final Context context; 1.114 + final Dispatcher dispatcher; 1.115 + final Cache cache; 1.116 + final Stats stats; 1.117 + final Map<Object, Action> targetToAction; 1.118 + final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator; 1.119 + final ReferenceQueue<Object> referenceQueue; 1.120 + 1.121 + boolean debugging; 1.122 + boolean shutdown; 1.123 + 1.124 + Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, 1.125 + RequestTransformer requestTransformer, Stats stats, boolean debugging) { 1.126 + this.context = context; 1.127 + this.dispatcher = dispatcher; 1.128 + this.cache = cache; 1.129 + this.listener = listener; 1.130 + this.requestTransformer = requestTransformer; 1.131 + this.stats = stats; 1.132 + this.targetToAction = new WeakHashMap<Object, Action>(); 1.133 + this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>(); 1.134 + this.debugging = debugging; 1.135 + this.referenceQueue = new ReferenceQueue<Object>(); 1.136 + this.cleanupThread = new CleanupThread(referenceQueue, HANDLER); 1.137 + this.cleanupThread.start(); 1.138 + } 1.139 + 1.140 + /** Cancel any existing requests for the specified target {@link ImageView}. */ 1.141 + public void cancelRequest(ImageView view) { 1.142 + cancelExistingRequest(view); 1.143 + } 1.144 + 1.145 + /** Cancel any existing requests for the specified {@link Target} instance. */ 1.146 + public void cancelRequest(Target target) { 1.147 + cancelExistingRequest(target); 1.148 + } 1.149 + 1.150 + /** 1.151 + * Start an image request using the specified URI. 1.152 + * <p> 1.153 + * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder, 1.154 + * if one is specified. 1.155 + * 1.156 + * @see #load(File) 1.157 + * @see #load(String) 1.158 + * @see #load(int) 1.159 + */ 1.160 + public RequestCreator load(Uri uri) { 1.161 + return new RequestCreator(this, uri, 0); 1.162 + } 1.163 + 1.164 + /** 1.165 + * Start an image request using the specified path. This is a convenience method for calling 1.166 + * {@link #load(Uri)}. 1.167 + * <p> 1.168 + * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource 1.169 + * (prefixed with {@code content:}), or android resource (prefixed with {@code 1.170 + * android.resource:}. 1.171 + * <p> 1.172 + * Passing {@code null} as a {@code path} will not trigger any request but will set a 1.173 + * placeholder, if one is specified. 1.174 + * 1.175 + * @see #load(Uri) 1.176 + * @see #load(File) 1.177 + * @see #load(int) 1.178 + */ 1.179 + public RequestCreator load(String path) { 1.180 + if (path == null) { 1.181 + return new RequestCreator(this, null, 0); 1.182 + } 1.183 + if (path.trim().length() == 0) { 1.184 + throw new IllegalArgumentException("Path must not be empty."); 1.185 + } 1.186 + return load(Uri.parse(path)); 1.187 + } 1.188 + 1.189 + /** 1.190 + * Start an image request using the specified image file. This is a convenience method for 1.191 + * calling {@link #load(Uri)}. 1.192 + * <p> 1.193 + * Passing {@code null} as a {@code file} will not trigger any request but will set a 1.194 + * placeholder, if one is specified. 1.195 + * 1.196 + * @see #load(Uri) 1.197 + * @see #load(String) 1.198 + * @see #load(int) 1.199 + */ 1.200 + public RequestCreator load(File file) { 1.201 + if (file == null) { 1.202 + return new RequestCreator(this, null, 0); 1.203 + } 1.204 + return load(Uri.fromFile(file)); 1.205 + } 1.206 + 1.207 + /** 1.208 + * Start an image request using the specified drawable resource ID. 1.209 + * 1.210 + * @see #load(Uri) 1.211 + * @see #load(String) 1.212 + * @see #load(File) 1.213 + */ 1.214 + public RequestCreator load(int resourceId) { 1.215 + if (resourceId == 0) { 1.216 + throw new IllegalArgumentException("Resource ID must not be zero."); 1.217 + } 1.218 + return new RequestCreator(this, null, resourceId); 1.219 + } 1.220 + 1.221 + /** {@code true} if debug display, logging, and statistics are enabled. */ 1.222 + @SuppressWarnings("UnusedDeclaration") public boolean isDebugging() { 1.223 + return debugging; 1.224 + } 1.225 + 1.226 + /** Toggle whether debug display, logging, and statistics are enabled. */ 1.227 + @SuppressWarnings("UnusedDeclaration") public void setDebugging(boolean debugging) { 1.228 + this.debugging = debugging; 1.229 + } 1.230 + 1.231 + /** Creates a {@link StatsSnapshot} of the current stats for this instance. */ 1.232 + @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() { 1.233 + return stats.createSnapshot(); 1.234 + } 1.235 + 1.236 + /** Stops this instance from accepting further requests. */ 1.237 + public void shutdown() { 1.238 + if (this == singleton) { 1.239 + throw new UnsupportedOperationException("Default singleton instance cannot be shutdown."); 1.240 + } 1.241 + if (shutdown) { 1.242 + return; 1.243 + } 1.244 + cache.clear(); 1.245 + cleanupThread.shutdown(); 1.246 + stats.shutdown(); 1.247 + dispatcher.shutdown(); 1.248 + for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) { 1.249 + deferredRequestCreator.cancel(); 1.250 + } 1.251 + targetToDeferredRequestCreator.clear(); 1.252 + shutdown = true; 1.253 + } 1.254 + 1.255 + Request transformRequest(Request request) { 1.256 + Request transformed = requestTransformer.transformRequest(request); 1.257 + if (transformed == null) { 1.258 + throw new IllegalStateException("Request transformer " 1.259 + + requestTransformer.getClass().getCanonicalName() 1.260 + + " returned null for " 1.261 + + request); 1.262 + } 1.263 + return transformed; 1.264 + } 1.265 + 1.266 + void defer(ImageView view, DeferredRequestCreator request) { 1.267 + targetToDeferredRequestCreator.put(view, request); 1.268 + } 1.269 + 1.270 + void enqueueAndSubmit(Action action) { 1.271 + Object target = action.getTarget(); 1.272 + if (target != null) { 1.273 + cancelExistingRequest(target); 1.274 + targetToAction.put(target, action); 1.275 + } 1.276 + submit(action); 1.277 + } 1.278 + 1.279 + void submit(Action action) { 1.280 + dispatcher.dispatchSubmit(action); 1.281 + } 1.282 + 1.283 + Bitmap quickMemoryCacheCheck(String key) { 1.284 + Bitmap cached = cache.get(key); 1.285 + if (cached != null) { 1.286 + stats.dispatchCacheHit(); 1.287 + } else { 1.288 + stats.dispatchCacheMiss(); 1.289 + } 1.290 + return cached; 1.291 + } 1.292 + 1.293 + void complete(BitmapHunter hunter) { 1.294 + List<Action> joined = hunter.getActions(); 1.295 + if (joined.isEmpty()) { 1.296 + return; 1.297 + } 1.298 + 1.299 + Uri uri = hunter.getData().uri; 1.300 + Exception exception = hunter.getException(); 1.301 + Bitmap result = hunter.getResult(); 1.302 + LoadedFrom from = hunter.getLoadedFrom(); 1.303 + 1.304 + for (Action join : joined) { 1.305 + if (join.isCancelled()) { 1.306 + continue; 1.307 + } 1.308 + targetToAction.remove(join.getTarget()); 1.309 + if (result != null) { 1.310 + if (from == null) { 1.311 + throw new AssertionError("LoadedFrom cannot be null."); 1.312 + } 1.313 + join.complete(result, from); 1.314 + } else { 1.315 + join.error(); 1.316 + } 1.317 + } 1.318 + 1.319 + if (listener != null && exception != null) { 1.320 + listener.onImageLoadFailed(this, uri, exception); 1.321 + } 1.322 + } 1.323 + 1.324 + private void cancelExistingRequest(Object target) { 1.325 + Action action = targetToAction.remove(target); 1.326 + if (action != null) { 1.327 + action.cancel(); 1.328 + dispatcher.dispatchCancel(action); 1.329 + } 1.330 + if (target instanceof ImageView) { 1.331 + ImageView targetImageView = (ImageView) target; 1.332 + DeferredRequestCreator deferredRequestCreator = 1.333 + targetToDeferredRequestCreator.remove(targetImageView); 1.334 + if (deferredRequestCreator != null) { 1.335 + deferredRequestCreator.cancel(); 1.336 + } 1.337 + } 1.338 + } 1.339 + 1.340 + private static class CleanupThread extends Thread { 1.341 + private final ReferenceQueue<?> referenceQueue; 1.342 + private final Handler handler; 1.343 + 1.344 + CleanupThread(ReferenceQueue<?> referenceQueue, Handler handler) { 1.345 + this.referenceQueue = referenceQueue; 1.346 + this.handler = handler; 1.347 + setDaemon(true); 1.348 + setName(THREAD_PREFIX + "refQueue"); 1.349 + } 1.350 + 1.351 + @Override public void run() { 1.352 + Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); 1.353 + while (true) { 1.354 + try { 1.355 + RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove(); 1.356 + handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action)); 1.357 + } catch (InterruptedException e) { 1.358 + break; 1.359 + } catch (final Exception e) { 1.360 + handler.post(new Runnable() { 1.361 + @Override public void run() { 1.362 + throw new RuntimeException(e); 1.363 + } 1.364 + }); 1.365 + break; 1.366 + } 1.367 + } 1.368 + } 1.369 + 1.370 + void shutdown() { 1.371 + interrupt(); 1.372 + } 1.373 + } 1.374 + 1.375 + /** 1.376 + * The global default {@link Picasso} instance. 1.377 + * <p> 1.378 + * This instance is automatically initialized with defaults that are suitable to most 1.379 + * implementations. 1.380 + * <ul> 1.381 + * <li>LRU memory cache of 15% the available application RAM</li> 1.382 + * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only 1.383 + * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk 1.384 + * cache on all API levels like OkHttp)</li> 1.385 + * <li>Three download threads for disk and network access.</li> 1.386 + * </ul> 1.387 + * <p> 1.388 + * If these settings do not meet the requirements of your application you can construct your own 1.389 + * instance with full control over the configuration by using {@link Picasso.Builder}. 1.390 + */ 1.391 + public static Picasso with(Context context) { 1.392 + if (singleton == null) { 1.393 + singleton = new Builder(context).build(); 1.394 + } 1.395 + return singleton; 1.396 + } 1.397 + 1.398 + /** Fluent API for creating {@link Picasso} instances. */ 1.399 + @SuppressWarnings("UnusedDeclaration") // Public API. 1.400 + public static class Builder { 1.401 + private final Context context; 1.402 + private Downloader downloader; 1.403 + private ExecutorService service; 1.404 + private Cache cache; 1.405 + private Listener listener; 1.406 + private RequestTransformer transformer; 1.407 + private boolean debugging; 1.408 + 1.409 + /** Start building a new {@link Picasso} instance. */ 1.410 + public Builder(Context context) { 1.411 + if (context == null) { 1.412 + throw new IllegalArgumentException("Context must not be null."); 1.413 + } 1.414 + this.context = context.getApplicationContext(); 1.415 + } 1.416 + 1.417 + /** Specify the {@link Downloader} that will be used for downloading images. */ 1.418 + public Builder downloader(Downloader downloader) { 1.419 + if (downloader == null) { 1.420 + throw new IllegalArgumentException("Downloader must not be null."); 1.421 + } 1.422 + if (this.downloader != null) { 1.423 + throw new IllegalStateException("Downloader already set."); 1.424 + } 1.425 + this.downloader = downloader; 1.426 + return this; 1.427 + } 1.428 + 1.429 + /** Specify the executor service for loading images in the background. */ 1.430 + public Builder executor(ExecutorService executorService) { 1.431 + if (executorService == null) { 1.432 + throw new IllegalArgumentException("Executor service must not be null."); 1.433 + } 1.434 + if (this.service != null) { 1.435 + throw new IllegalStateException("Executor service already set."); 1.436 + } 1.437 + this.service = executorService; 1.438 + return this; 1.439 + } 1.440 + 1.441 + /** Specify the memory cache used for the most recent images. */ 1.442 + public Builder memoryCache(Cache memoryCache) { 1.443 + if (memoryCache == null) { 1.444 + throw new IllegalArgumentException("Memory cache must not be null."); 1.445 + } 1.446 + if (this.cache != null) { 1.447 + throw new IllegalStateException("Memory cache already set."); 1.448 + } 1.449 + this.cache = memoryCache; 1.450 + return this; 1.451 + } 1.452 + 1.453 + /** Specify a listener for interesting events. */ 1.454 + public Builder listener(Listener listener) { 1.455 + if (listener == null) { 1.456 + throw new IllegalArgumentException("Listener must not be null."); 1.457 + } 1.458 + if (this.listener != null) { 1.459 + throw new IllegalStateException("Listener already set."); 1.460 + } 1.461 + this.listener = listener; 1.462 + return this; 1.463 + } 1.464 + 1.465 + /** 1.466 + * Specify a transformer for all incoming requests. 1.467 + * <p> 1.468 + * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible 1.469 + * way at any time. 1.470 + */ 1.471 + public Builder requestTransformer(RequestTransformer transformer) { 1.472 + if (transformer == null) { 1.473 + throw new IllegalArgumentException("Transformer must not be null."); 1.474 + } 1.475 + if (this.transformer != null) { 1.476 + throw new IllegalStateException("Transformer already set."); 1.477 + } 1.478 + this.transformer = transformer; 1.479 + return this; 1.480 + } 1.481 + 1.482 + /** Whether debugging is enabled or not. */ 1.483 + public Builder debugging(boolean debugging) { 1.484 + this.debugging = debugging; 1.485 + return this; 1.486 + } 1.487 + 1.488 + /** Create the {@link Picasso} instance. */ 1.489 + public Picasso build() { 1.490 + Context context = this.context; 1.491 + 1.492 + if (downloader == null) { 1.493 + downloader = Utils.createDefaultDownloader(context); 1.494 + } 1.495 + if (cache == null) { 1.496 + cache = new LruCache(context); 1.497 + } 1.498 + if (service == null) { 1.499 + service = new PicassoExecutorService(); 1.500 + } 1.501 + if (transformer == null) { 1.502 + transformer = RequestTransformer.IDENTITY; 1.503 + } 1.504 + 1.505 + Stats stats = new Stats(cache); 1.506 + 1.507 + Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); 1.508 + 1.509 + return new Picasso(context, dispatcher, cache, listener, transformer, stats, debugging); 1.510 + } 1.511 + } 1.512 + 1.513 + /** Describes where the image was loaded from. */ 1.514 + public enum LoadedFrom { 1.515 + MEMORY(Color.GREEN), 1.516 + DISK(Color.YELLOW), 1.517 + NETWORK(Color.RED); 1.518 + 1.519 + final int debugColor; 1.520 + 1.521 + private LoadedFrom(int debugColor) { 1.522 + this.debugColor = debugColor; 1.523 + } 1.524 + } 1.525 +}