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

changeset 0
6474c204b198
     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 +}

mercurial