Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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.Color;
21 import android.net.Uri;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.Process;
26 import android.widget.ImageView;
27 import java.io.File;
28 import java.lang.ref.ReferenceQueue;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.WeakHashMap;
32 import java.util.concurrent.ExecutorService;
34 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
35 import static com.squareup.picasso.Action.RequestWeakReference;
36 import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE;
37 import static com.squareup.picasso.Dispatcher.REQUEST_GCED;
38 import static com.squareup.picasso.Utils.THREAD_PREFIX;
40 /**
41 * Image downloading, transformation, and caching manager.
42 * <p/>
43 * Use {@link #with(android.content.Context)} for the global singleton instance or construct your
44 * own instance with {@link Builder}.
45 */
46 public class Picasso {
48 /** Callbacks for Picasso events. */
49 public interface Listener {
50 /**
51 * Invoked when an image has failed to load. This is useful for reporting image failures to a
52 * remote analytics service, for example.
53 */
54 void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
55 }
57 /**
58 * A transformer that is called immediately before every request is submitted. This can be used to
59 * modify any information about a request.
60 * <p>
61 * For example, if you use a CDN you can change the hostname for the image based on the current
62 * location of the user in order to get faster download speeds.
63 * <p>
64 * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
65 * way at any time.
66 */
67 public interface RequestTransformer {
68 /**
69 * Transform a request before it is submitted to be processed.
70 *
71 * @return The original request or a new request to replace it. Must not be null.
72 */
73 Request transformRequest(Request request);
75 /** A {@link RequestTransformer} which returns the original request. */
76 RequestTransformer IDENTITY = new RequestTransformer() {
77 @Override public Request transformRequest(Request request) {
78 return request;
79 }
80 };
81 }
83 static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
84 @Override public void handleMessage(Message msg) {
85 switch (msg.what) {
86 case HUNTER_BATCH_COMPLETE: {
87 @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
88 for (BitmapHunter hunter : batch) {
89 hunter.picasso.complete(hunter);
90 }
91 break;
92 }
93 case REQUEST_GCED: {
94 Action action = (Action) msg.obj;
95 action.picasso.cancelExistingRequest(action.getTarget());
96 break;
97 }
98 default:
99 throw new AssertionError("Unknown handler message received: " + msg.what);
100 }
101 }
102 };
104 static Picasso singleton = null;
106 private final Listener listener;
107 private final RequestTransformer requestTransformer;
108 private final CleanupThread cleanupThread;
110 final Context context;
111 final Dispatcher dispatcher;
112 final Cache cache;
113 final Stats stats;
114 final Map<Object, Action> targetToAction;
115 final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
116 final ReferenceQueue<Object> referenceQueue;
118 boolean debugging;
119 boolean shutdown;
121 Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
122 RequestTransformer requestTransformer, Stats stats, boolean debugging) {
123 this.context = context;
124 this.dispatcher = dispatcher;
125 this.cache = cache;
126 this.listener = listener;
127 this.requestTransformer = requestTransformer;
128 this.stats = stats;
129 this.targetToAction = new WeakHashMap<Object, Action>();
130 this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
131 this.debugging = debugging;
132 this.referenceQueue = new ReferenceQueue<Object>();
133 this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
134 this.cleanupThread.start();
135 }
137 /** Cancel any existing requests for the specified target {@link ImageView}. */
138 public void cancelRequest(ImageView view) {
139 cancelExistingRequest(view);
140 }
142 /** Cancel any existing requests for the specified {@link Target} instance. */
143 public void cancelRequest(Target target) {
144 cancelExistingRequest(target);
145 }
147 /**
148 * Start an image request using the specified URI.
149 * <p>
150 * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
151 * if one is specified.
152 *
153 * @see #load(File)
154 * @see #load(String)
155 * @see #load(int)
156 */
157 public RequestCreator load(Uri uri) {
158 return new RequestCreator(this, uri, 0);
159 }
161 /**
162 * Start an image request using the specified path. This is a convenience method for calling
163 * {@link #load(Uri)}.
164 * <p>
165 * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
166 * (prefixed with {@code content:}), or android resource (prefixed with {@code
167 * android.resource:}.
168 * <p>
169 * Passing {@code null} as a {@code path} will not trigger any request but will set a
170 * placeholder, if one is specified.
171 *
172 * @see #load(Uri)
173 * @see #load(File)
174 * @see #load(int)
175 */
176 public RequestCreator load(String path) {
177 if (path == null) {
178 return new RequestCreator(this, null, 0);
179 }
180 if (path.trim().length() == 0) {
181 throw new IllegalArgumentException("Path must not be empty.");
182 }
183 return load(Uri.parse(path));
184 }
186 /**
187 * Start an image request using the specified image file. This is a convenience method for
188 * calling {@link #load(Uri)}.
189 * <p>
190 * Passing {@code null} as a {@code file} will not trigger any request but will set a
191 * placeholder, if one is specified.
192 *
193 * @see #load(Uri)
194 * @see #load(String)
195 * @see #load(int)
196 */
197 public RequestCreator load(File file) {
198 if (file == null) {
199 return new RequestCreator(this, null, 0);
200 }
201 return load(Uri.fromFile(file));
202 }
204 /**
205 * Start an image request using the specified drawable resource ID.
206 *
207 * @see #load(Uri)
208 * @see #load(String)
209 * @see #load(File)
210 */
211 public RequestCreator load(int resourceId) {
212 if (resourceId == 0) {
213 throw new IllegalArgumentException("Resource ID must not be zero.");
214 }
215 return new RequestCreator(this, null, resourceId);
216 }
218 /** {@code true} if debug display, logging, and statistics are enabled. */
219 @SuppressWarnings("UnusedDeclaration") public boolean isDebugging() {
220 return debugging;
221 }
223 /** Toggle whether debug display, logging, and statistics are enabled. */
224 @SuppressWarnings("UnusedDeclaration") public void setDebugging(boolean debugging) {
225 this.debugging = debugging;
226 }
228 /** Creates a {@link StatsSnapshot} of the current stats for this instance. */
229 @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() {
230 return stats.createSnapshot();
231 }
233 /** Stops this instance from accepting further requests. */
234 public void shutdown() {
235 if (this == singleton) {
236 throw new UnsupportedOperationException("Default singleton instance cannot be shutdown.");
237 }
238 if (shutdown) {
239 return;
240 }
241 cache.clear();
242 cleanupThread.shutdown();
243 stats.shutdown();
244 dispatcher.shutdown();
245 for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) {
246 deferredRequestCreator.cancel();
247 }
248 targetToDeferredRequestCreator.clear();
249 shutdown = true;
250 }
252 Request transformRequest(Request request) {
253 Request transformed = requestTransformer.transformRequest(request);
254 if (transformed == null) {
255 throw new IllegalStateException("Request transformer "
256 + requestTransformer.getClass().getCanonicalName()
257 + " returned null for "
258 + request);
259 }
260 return transformed;
261 }
263 void defer(ImageView view, DeferredRequestCreator request) {
264 targetToDeferredRequestCreator.put(view, request);
265 }
267 void enqueueAndSubmit(Action action) {
268 Object target = action.getTarget();
269 if (target != null) {
270 cancelExistingRequest(target);
271 targetToAction.put(target, action);
272 }
273 submit(action);
274 }
276 void submit(Action action) {
277 dispatcher.dispatchSubmit(action);
278 }
280 Bitmap quickMemoryCacheCheck(String key) {
281 Bitmap cached = cache.get(key);
282 if (cached != null) {
283 stats.dispatchCacheHit();
284 } else {
285 stats.dispatchCacheMiss();
286 }
287 return cached;
288 }
290 void complete(BitmapHunter hunter) {
291 List<Action> joined = hunter.getActions();
292 if (joined.isEmpty()) {
293 return;
294 }
296 Uri uri = hunter.getData().uri;
297 Exception exception = hunter.getException();
298 Bitmap result = hunter.getResult();
299 LoadedFrom from = hunter.getLoadedFrom();
301 for (Action join : joined) {
302 if (join.isCancelled()) {
303 continue;
304 }
305 targetToAction.remove(join.getTarget());
306 if (result != null) {
307 if (from == null) {
308 throw new AssertionError("LoadedFrom cannot be null.");
309 }
310 join.complete(result, from);
311 } else {
312 join.error();
313 }
314 }
316 if (listener != null && exception != null) {
317 listener.onImageLoadFailed(this, uri, exception);
318 }
319 }
321 private void cancelExistingRequest(Object target) {
322 Action action = targetToAction.remove(target);
323 if (action != null) {
324 action.cancel();
325 dispatcher.dispatchCancel(action);
326 }
327 if (target instanceof ImageView) {
328 ImageView targetImageView = (ImageView) target;
329 DeferredRequestCreator deferredRequestCreator =
330 targetToDeferredRequestCreator.remove(targetImageView);
331 if (deferredRequestCreator != null) {
332 deferredRequestCreator.cancel();
333 }
334 }
335 }
337 private static class CleanupThread extends Thread {
338 private final ReferenceQueue<?> referenceQueue;
339 private final Handler handler;
341 CleanupThread(ReferenceQueue<?> referenceQueue, Handler handler) {
342 this.referenceQueue = referenceQueue;
343 this.handler = handler;
344 setDaemon(true);
345 setName(THREAD_PREFIX + "refQueue");
346 }
348 @Override public void run() {
349 Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
350 while (true) {
351 try {
352 RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove();
353 handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action));
354 } catch (InterruptedException e) {
355 break;
356 } catch (final Exception e) {
357 handler.post(new Runnable() {
358 @Override public void run() {
359 throw new RuntimeException(e);
360 }
361 });
362 break;
363 }
364 }
365 }
367 void shutdown() {
368 interrupt();
369 }
370 }
372 /**
373 * The global default {@link Picasso} instance.
374 * <p>
375 * This instance is automatically initialized with defaults that are suitable to most
376 * implementations.
377 * <ul>
378 * <li>LRU memory cache of 15% the available application RAM</li>
379 * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
380 * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk
381 * cache on all API levels like OkHttp)</li>
382 * <li>Three download threads for disk and network access.</li>
383 * </ul>
384 * <p>
385 * If these settings do not meet the requirements of your application you can construct your own
386 * instance with full control over the configuration by using {@link Picasso.Builder}.
387 */
388 public static Picasso with(Context context) {
389 if (singleton == null) {
390 singleton = new Builder(context).build();
391 }
392 return singleton;
393 }
395 /** Fluent API for creating {@link Picasso} instances. */
396 @SuppressWarnings("UnusedDeclaration") // Public API.
397 public static class Builder {
398 private final Context context;
399 private Downloader downloader;
400 private ExecutorService service;
401 private Cache cache;
402 private Listener listener;
403 private RequestTransformer transformer;
404 private boolean debugging;
406 /** Start building a new {@link Picasso} instance. */
407 public Builder(Context context) {
408 if (context == null) {
409 throw new IllegalArgumentException("Context must not be null.");
410 }
411 this.context = context.getApplicationContext();
412 }
414 /** Specify the {@link Downloader} that will be used for downloading images. */
415 public Builder downloader(Downloader downloader) {
416 if (downloader == null) {
417 throw new IllegalArgumentException("Downloader must not be null.");
418 }
419 if (this.downloader != null) {
420 throw new IllegalStateException("Downloader already set.");
421 }
422 this.downloader = downloader;
423 return this;
424 }
426 /** Specify the executor service for loading images in the background. */
427 public Builder executor(ExecutorService executorService) {
428 if (executorService == null) {
429 throw new IllegalArgumentException("Executor service must not be null.");
430 }
431 if (this.service != null) {
432 throw new IllegalStateException("Executor service already set.");
433 }
434 this.service = executorService;
435 return this;
436 }
438 /** Specify the memory cache used for the most recent images. */
439 public Builder memoryCache(Cache memoryCache) {
440 if (memoryCache == null) {
441 throw new IllegalArgumentException("Memory cache must not be null.");
442 }
443 if (this.cache != null) {
444 throw new IllegalStateException("Memory cache already set.");
445 }
446 this.cache = memoryCache;
447 return this;
448 }
450 /** Specify a listener for interesting events. */
451 public Builder listener(Listener listener) {
452 if (listener == null) {
453 throw new IllegalArgumentException("Listener must not be null.");
454 }
455 if (this.listener != null) {
456 throw new IllegalStateException("Listener already set.");
457 }
458 this.listener = listener;
459 return this;
460 }
462 /**
463 * Specify a transformer for all incoming requests.
464 * <p>
465 * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
466 * way at any time.
467 */
468 public Builder requestTransformer(RequestTransformer transformer) {
469 if (transformer == null) {
470 throw new IllegalArgumentException("Transformer must not be null.");
471 }
472 if (this.transformer != null) {
473 throw new IllegalStateException("Transformer already set.");
474 }
475 this.transformer = transformer;
476 return this;
477 }
479 /** Whether debugging is enabled or not. */
480 public Builder debugging(boolean debugging) {
481 this.debugging = debugging;
482 return this;
483 }
485 /** Create the {@link Picasso} instance. */
486 public Picasso build() {
487 Context context = this.context;
489 if (downloader == null) {
490 downloader = Utils.createDefaultDownloader(context);
491 }
492 if (cache == null) {
493 cache = new LruCache(context);
494 }
495 if (service == null) {
496 service = new PicassoExecutorService();
497 }
498 if (transformer == null) {
499 transformer = RequestTransformer.IDENTITY;
500 }
502 Stats stats = new Stats(cache);
504 Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
506 return new Picasso(context, dispatcher, cache, listener, transformer, stats, debugging);
507 }
508 }
510 /** Describes where the image was loaded from. */
511 public enum LoadedFrom {
512 MEMORY(Color.GREEN),
513 DISK(Color.YELLOW),
514 NETWORK(Color.RED);
516 final int debugColor;
518 private LoadedFrom(int debugColor) {
519 this.debugColor = debugColor;
520 }
521 }
522 }