diff --git a/picasso/api/picasso.api b/picasso/api/picasso.api index 40684a794d..c2e278ff93 100644 --- a/picasso/api/picasso.api +++ b/picasso/api/picasso.api @@ -268,43 +268,51 @@ public final class com/squareup/picasso3/Request$Companion { } public class com/squareup/picasso3/RequestCreator { - public fun centerCrop ()Lcom/squareup/picasso3/RequestCreator; - public fun centerCrop (I)Lcom/squareup/picasso3/RequestCreator; - public fun centerInside ()Lcom/squareup/picasso3/RequestCreator; - public fun config (Landroid/graphics/Bitmap$Config;)Lcom/squareup/picasso3/RequestCreator; - public fun error (I)Lcom/squareup/picasso3/RequestCreator; - public fun error (Landroid/graphics/drawable/Drawable;)Lcom/squareup/picasso3/RequestCreator; - public fun fetch ()V - public fun fetch (Lcom/squareup/picasso3/Callback;)V - public fun fit ()Lcom/squareup/picasso3/RequestCreator; - public fun get ()Landroid/graphics/Bitmap; - public fun into (Landroid/widget/ImageView;)V - public fun into (Landroid/widget/ImageView;Lcom/squareup/picasso3/Callback;)V - public fun into (Landroid/widget/RemoteViews;II)V - public fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;)V - public fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;Ljava/lang/String;)V - public fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;Ljava/lang/String;Lcom/squareup/picasso3/Callback;)V - public fun into (Landroid/widget/RemoteViews;IILcom/squareup/picasso3/Callback;)V - public fun into (Landroid/widget/RemoteViews;I[I)V - public fun into (Landroid/widget/RemoteViews;I[ILcom/squareup/picasso3/Callback;)V - public fun into (Lcom/squareup/picasso3/BitmapTarget;)V - public fun memoryPolicy (Lcom/squareup/picasso3/MemoryPolicy;[Lcom/squareup/picasso3/MemoryPolicy;)Lcom/squareup/picasso3/RequestCreator; - public fun networkPolicy (Lcom/squareup/picasso3/NetworkPolicy;[Lcom/squareup/picasso3/NetworkPolicy;)Lcom/squareup/picasso3/RequestCreator; - public fun noFade ()Lcom/squareup/picasso3/RequestCreator; - public fun noPlaceholder ()Lcom/squareup/picasso3/RequestCreator; - public fun onlyScaleDown ()Lcom/squareup/picasso3/RequestCreator; - public fun placeholder (I)Lcom/squareup/picasso3/RequestCreator; - public fun placeholder (Landroid/graphics/drawable/Drawable;)Lcom/squareup/picasso3/RequestCreator; - public fun priority (Lcom/squareup/picasso3/Picasso$Priority;)Lcom/squareup/picasso3/RequestCreator; - public fun purgeable ()Lcom/squareup/picasso3/RequestCreator; - public fun resize (II)Lcom/squareup/picasso3/RequestCreator; - public fun resizeDimen (II)Lcom/squareup/picasso3/RequestCreator; - public fun rotate (F)Lcom/squareup/picasso3/RequestCreator; - public fun rotate (FFF)Lcom/squareup/picasso3/RequestCreator; - public fun stableKey (Ljava/lang/String;)Lcom/squareup/picasso3/RequestCreator; - public fun tag (Ljava/lang/Object;)Lcom/squareup/picasso3/RequestCreator; - public fun transform (Lcom/squareup/picasso3/Transformation;)Lcom/squareup/picasso3/RequestCreator; - public fun transform (Ljava/util/List;)Lcom/squareup/picasso3/RequestCreator; + public static final field Companion Lcom/squareup/picasso3/RequestCreator$Companion; + public final fun centerCrop ()Lcom/squareup/picasso3/RequestCreator; + public final fun centerCrop (I)Lcom/squareup/picasso3/RequestCreator; + public final fun centerInside ()Lcom/squareup/picasso3/RequestCreator; + public final fun config (Landroid/graphics/Bitmap$Config;)Lcom/squareup/picasso3/RequestCreator; + public final fun error (I)Lcom/squareup/picasso3/RequestCreator; + public final fun error (Landroid/graphics/drawable/Drawable;)Lcom/squareup/picasso3/RequestCreator; + public final fun fetch ()V + public final fun fetch (Lcom/squareup/picasso3/Callback;)V + public static synthetic fun fetch$default (Lcom/squareup/picasso3/RequestCreator;Lcom/squareup/picasso3/Callback;ILjava/lang/Object;)V + public final fun fit ()Lcom/squareup/picasso3/RequestCreator; + public final fun get ()Landroid/graphics/Bitmap; + public final fun into (Landroid/widget/ImageView;)V + public final fun into (Landroid/widget/ImageView;Lcom/squareup/picasso3/Callback;)V + public final fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;)V + public final fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;Ljava/lang/String;)V + public final fun into (Landroid/widget/RemoteViews;IILandroid/app/Notification;Ljava/lang/String;Lcom/squareup/picasso3/Callback;)V + public final fun into (Landroid/widget/RemoteViews;IILcom/squareup/picasso3/Callback;)V + public final fun into (Landroid/widget/RemoteViews;I[I)V + public final fun into (Landroid/widget/RemoteViews;I[ILcom/squareup/picasso3/Callback;)V + public final fun into (Lcom/squareup/picasso3/BitmapTarget;)V + public static synthetic fun into$default (Lcom/squareup/picasso3/RequestCreator;Landroid/widget/ImageView;Lcom/squareup/picasso3/Callback;ILjava/lang/Object;)V + public static synthetic fun into$default (Lcom/squareup/picasso3/RequestCreator;Landroid/widget/RemoteViews;IILandroid/app/Notification;Ljava/lang/String;Lcom/squareup/picasso3/Callback;ILjava/lang/Object;)V + public static synthetic fun into$default (Lcom/squareup/picasso3/RequestCreator;Landroid/widget/RemoteViews;IILcom/squareup/picasso3/Callback;ILjava/lang/Object;)V + public static synthetic fun into$default (Lcom/squareup/picasso3/RequestCreator;Landroid/widget/RemoteViews;I[ILcom/squareup/picasso3/Callback;ILjava/lang/Object;)V + public final fun memoryPolicy (Lcom/squareup/picasso3/MemoryPolicy;[Lcom/squareup/picasso3/MemoryPolicy;)Lcom/squareup/picasso3/RequestCreator; + public final fun networkPolicy (Lcom/squareup/picasso3/NetworkPolicy;[Lcom/squareup/picasso3/NetworkPolicy;)Lcom/squareup/picasso3/RequestCreator; + public final fun noFade ()Lcom/squareup/picasso3/RequestCreator; + public final fun noPlaceholder ()Lcom/squareup/picasso3/RequestCreator; + public final fun onlyScaleDown ()Lcom/squareup/picasso3/RequestCreator; + public final fun placeholder (I)Lcom/squareup/picasso3/RequestCreator; + public final fun placeholder (Landroid/graphics/drawable/Drawable;)Lcom/squareup/picasso3/RequestCreator; + public final fun priority (Lcom/squareup/picasso3/Picasso$Priority;)Lcom/squareup/picasso3/RequestCreator; + public final fun purgeable ()Lcom/squareup/picasso3/RequestCreator; + public final fun resize (II)Lcom/squareup/picasso3/RequestCreator; + public final fun resizeDimen (II)Lcom/squareup/picasso3/RequestCreator; + public final fun rotate (F)Lcom/squareup/picasso3/RequestCreator; + public final fun rotate (FFF)Lcom/squareup/picasso3/RequestCreator; + public final fun stableKey (Ljava/lang/String;)Lcom/squareup/picasso3/RequestCreator; + public final fun tag (Ljava/lang/Object;)Lcom/squareup/picasso3/RequestCreator; + public final fun transform (Lcom/squareup/picasso3/Transformation;)Lcom/squareup/picasso3/RequestCreator; + public final fun transform (Ljava/util/List;)Lcom/squareup/picasso3/RequestCreator; +} + +public final class com/squareup/picasso3/RequestCreator$Companion { } public abstract class com/squareup/picasso3/RequestHandler { diff --git a/picasso/src/main/java/com/squareup/picasso3/Request.kt b/picasso/src/main/java/com/squareup/picasso3/Request.kt index 52622b442c..9f85f677ac 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Request.kt +++ b/picasso/src/main/java/com/squareup/picasso3/Request.kt @@ -23,17 +23,16 @@ import androidx.annotation.DrawableRes import androidx.annotation.Px import com.squareup.picasso3.Picasso.Priority import com.squareup.picasso3.Picasso.Priority.NORMAL -import java.util.ArrayList import java.util.concurrent.TimeUnit.NANOSECONDS import java.util.concurrent.TimeUnit.SECONDS /** Immutable data about an image and the transformations that will be applied to it. */ class Request internal constructor(builder: Builder) { /** A unique ID for the request. */ - @JvmField var id = 0 + internal var id = 0 /** The time that the request was first submitted (in nanos). */ - @JvmField var started: Long = 0 + internal var started: Long = 0 /** The [MemoryPolicy] to use for this request. */ @JvmField val memoryPolicy: Int = builder.memoryPolicy @@ -62,12 +61,13 @@ class Request internal constructor(builder: Builder) { val stableKey: String? = builder.stableKey /** List of custom transformations to be applied after the built-in transformations. */ - @JvmField var transformations: List = + var transformations: List = if (builder.transformations == null) { emptyList() } else { builder.transformations!!.toList() } + internal set /** Target image width for resizing. */ @JvmField val targetWidth: Int = builder.targetWidth @@ -577,4 +577,4 @@ class Request internal constructor(builder: Builder) { private const val KEY_PADDING = 50 // Determined by exact science. const val KEY_SEPARATOR = '\n' } -} \ No newline at end of file +} diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java deleted file mode 100644 index 16db04f2f8..0000000000 --- a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java +++ /dev/null @@ -1,794 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.squareup.picasso3; - -import android.app.Notification; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.view.Gravity; -import android.widget.ImageView; -import android.widget.RemoteViews; -import androidx.annotation.DrawableRes; -import androidx.annotation.IdRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; -import com.squareup.picasso3.RemoteViewsAction.RemoteViewsTarget; -import com.squareup.picasso3.RequestHandler.Result; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.squareup.picasso3.BitmapHunter.forRequest; -import static com.squareup.picasso3.MemoryPolicy.shouldReadFromMemoryCache; -import static com.squareup.picasso3.MemoryPolicy.shouldWriteToMemoryCache; -import static com.squareup.picasso3.Picasso.LoadedFrom.MEMORY; -import static com.squareup.picasso3.Picasso.Priority; -import static com.squareup.picasso3.PicassoDrawable.setPlaceholder; -import static com.squareup.picasso3.PicassoDrawable.setResult; -import static com.squareup.picasso3.RemoteViewsAction.AppWidgetAction; -import static com.squareup.picasso3.RemoteViewsAction.NotificationAction; -import static com.squareup.picasso3.Utils.OWNER_MAIN; -import static com.squareup.picasso3.Utils.VERB_CHANGED; -import static com.squareup.picasso3.Utils.VERB_COMPLETED; -import static com.squareup.picasso3.Utils.VERB_CREATED; -import static com.squareup.picasso3.Utils.checkMain; -import static com.squareup.picasso3.Utils.checkNotMain; -import static com.squareup.picasso3.Utils.log; - -/** Fluent API for building an image download request. */ -@SuppressWarnings("UnusedDeclaration") // Public API. -public class RequestCreator { - private static final AtomicInteger nextId = new AtomicInteger(); - - private final Picasso picasso; - private final Request.Builder data; - - private boolean noFade; - private boolean deferred; - private boolean setPlaceholder = true; - private int placeholderResId; - @DrawableRes private int errorResId; - private @Nullable Drawable placeholderDrawable; - private @Nullable Drawable errorDrawable; - - RequestCreator(Picasso picasso, @Nullable Uri uri, int resourceId) { - if (picasso.shutdown) { - throw new IllegalStateException( - "Picasso instance already shut down. Cannot submit new requests."); - } - this.picasso = picasso; - this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); - } - - @SuppressWarnings("NullAway") - @VisibleForTesting RequestCreator() { - this.picasso = null; - this.data = new Request.Builder(null, 0, null); - } - - /** - * Explicitly opt-out to having a placeholder set when calling {@code into}. - *

- * By default, Picasso will either set a supplied placeholder or clear the target - * {@link ImageView} in order to ensure behavior in situations where views are recycled. This - * method will prevent that behavior and retain any already set image. - */ - @NonNull - public RequestCreator noPlaceholder() { - if (placeholderResId != 0) { - throw new IllegalStateException("Placeholder resource already set."); - } - if (placeholderDrawable != null) { - throw new IllegalStateException("Placeholder image already set."); - } - setPlaceholder = false; - return this; - } - - /** - * A placeholder drawable to be used while the image is being loaded. If the requested image is - * not immediately available in the memory cache then this resource will be set on the target - * {@link ImageView}. - */ - @NonNull - public RequestCreator placeholder(@DrawableRes int placeholderResId) { - if (!setPlaceholder) { - throw new IllegalStateException("Already explicitly declared as no placeholder."); - } - if (placeholderResId == 0) { - throw new IllegalArgumentException("Placeholder image resource invalid."); - } - if (placeholderDrawable != null) { - throw new IllegalStateException("Placeholder image already set."); - } - this.placeholderResId = placeholderResId; - return this; - } - - /** - * A placeholder drawable to be used while the image is being loaded. If the requested image is - * not immediately available in the memory cache then this resource will be set on the target - * {@link ImageView}. - *

- * If you are not using a placeholder image but want to clear an existing image (such as when - * used in an {@link android.widget.Adapter adapter}), pass in {@code null}. - */ - @NonNull - public RequestCreator placeholder(@NonNull Drawable placeholderDrawable) { - if (!setPlaceholder) { - throw new IllegalStateException("Already explicitly declared as no placeholder."); - } - if (placeholderResId != 0) { - throw new IllegalStateException("Placeholder image already set."); - } - this.placeholderDrawable = placeholderDrawable; - return this; - } - - /** An error drawable to be used if the request image could not be loaded. */ - @NonNull - public RequestCreator error(@DrawableRes int errorResId) { - if (errorResId == 0) { - throw new IllegalArgumentException("Error image resource invalid."); - } - if (errorDrawable != null) { - throw new IllegalStateException("Error image already set."); - } - this.errorResId = errorResId; - return this; - } - - /** An error drawable to be used if the request image could not be loaded. */ - @NonNull - public RequestCreator error(@NonNull Drawable errorDrawable) { - if (errorDrawable == null) { - throw new IllegalArgumentException("Error image may not be null."); - } - if (errorResId != 0) { - throw new IllegalStateException("Error image already set."); - } - this.errorDrawable = errorDrawable; - return this; - } - - /** - * Assign a tag to this request. Tags are an easy way to logically associate - * related requests that can be managed together e.g. paused, resumed, - * or canceled. - *

- * You can either use simple {@link String} tags or objects that naturally - * define the scope of your requests within your app such as a - * {@link android.content.Context}, an {@link android.app.Activity}, or a - * {@link android.app.Fragment}. - * - * WARNING:: Picasso will keep a reference to the tag for - * as long as this tag is paused and/or has active requests. Look out for - * potential leaks. - * - * @see Picasso#cancelTag(Object) - * @see Picasso#pauseTag(Object) - * @see Picasso#resumeTag(Object) - */ - @NonNull - public RequestCreator tag(@NonNull Object tag) { - data.tag(tag); - return this; - } - - /** - * Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This - * will result in delayed execution of the request until the {@link ImageView} has been laid out. - *

- * Note: This method works only when your target is an {@link ImageView}. - */ - @NonNull - public RequestCreator fit() { - deferred = true; - return this; - } - - /** Internal use only. Used by {@link DeferredRequestCreator}. */ - RequestCreator unfit() { - deferred = false; - return this; - } - - /** Internal use only. Used by {@link DeferredRequestCreator}. */ - RequestCreator clearTag() { - data.clearTag(); - return this; - } - - /** Internal use only. Used by {@link DeferredRequestCreator}. */ - @Nullable Object getTag() { - return data.getTag(); - } - - /** - * Resize the image to the specified dimension size. - * Use 0 as desired dimension to resize keeping aspect ratio. - */ - @NonNull - public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) { - Resources resources = picasso.context.getResources(); - int targetWidth = resources.getDimensionPixelSize(targetWidthResId); - int targetHeight = resources.getDimensionPixelSize(targetHeightResId); - return resize(targetWidth, targetHeight); - } - - /** - * Resize the image to the specified size in pixels. - * Use 0 as desired dimension to resize keeping aspect ratio. - */ - @NonNull - public RequestCreator resize(int targetWidth, int targetHeight) { - data.resize(targetWidth, targetHeight); - return this; - } - - /** - * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than - * distorting the aspect ratio. This cropping technique scales the image so that it fills the - * requested bounds and then crops the extra. - */ - @NonNull - public RequestCreator centerCrop() { - data.centerCrop(Gravity.CENTER); - return this; - } - - /** - * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than - * distorting the aspect ratio. This cropping technique scales the image so that it fills the - * requested bounds and then crops the extra, preferring the contents at {@code alignGravity}. - */ - @NonNull - public RequestCreator centerCrop(int alignGravity) { - data.centerCrop(alignGravity); - return this; - } - - /** - * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales - * the image so that both dimensions are equal to or less than the requested bounds. - */ - @NonNull - public RequestCreator centerInside() { - data.centerInside(); - return this; - } - - /** - * Only resize an image if the original image size is bigger than the target size - * specified by {@link #resize(int, int)}. - */ - @NonNull - public RequestCreator onlyScaleDown() { - data.onlyScaleDown(); - return this; - } - - /** Rotate the image by the specified degrees. */ - @NonNull - public RequestCreator rotate(float degrees) { - data.rotate(degrees); - return this; - } - - /** Rotate the image by the specified degrees around a pivot point. */ - @NonNull - public RequestCreator rotate(float degrees, float pivotX, float pivotY) { - data.rotate(degrees, pivotX, pivotY); - return this; - } - - /** - * Attempt to decode the image using the specified config. - *

- * Note: This value may be ignored by {@link BitmapFactory}. See - * {@link BitmapFactory.Options#inPreferredConfig its documentation} for more details. - */ - @NonNull - public RequestCreator config(@NonNull Bitmap.Config config) { - data.config(config); - return this; - } - - /** - * Sets the stable key for this request to be used instead of the URI or resource ID when - * caching. Two requests with the same value are considered to be for the same resource. - */ - @NonNull - public RequestCreator stableKey(@NonNull String stableKey) { - data.stableKey(stableKey); - return this; - } - - /** - * Set the priority of this request. - *

- * This will affect the order in which the requests execute but does not guarantee it. - * By default, all requests have {@link Priority#NORMAL} priority, except for - * {@link #fetch()} requests, which have {@link Priority#LOW} priority by default. - */ - @NonNull - public RequestCreator priority(@NonNull Priority priority) { - data.priority(priority); - return this; - } - - /** - * Add a custom transformation to be applied to the image. - *

- * Custom transformations will always be run after the built-in transformations. - */ - // TODO show example of calling resize after a transform in the javadoc - @NonNull - public RequestCreator transform(@NonNull Transformation transformation) { - data.transform(transformation); - return this; - } - - /** - * Add a list of custom transformations to be applied to the image. - *

- * Custom transformations will always be run after the built-in transformations. - */ - @NonNull - public RequestCreator transform(@NonNull List transformations) { - data.transform(transformations); - return this; - } - - /** - * Specifies the {@link MemoryPolicy} to use for this request. You may specify additional policy - * options using the varargs parameter. - */ - @NonNull - public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, - @NonNull MemoryPolicy... additional) { - data.memoryPolicy(policy, additional); - return this; - } - - /** - * Specifies the {@link NetworkPolicy} to use for this request. You may specify additional policy - * options using the varargs parameter. - */ - @NonNull - public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, - @NonNull NetworkPolicy... additional) { - data.networkPolicy(policy, additional); - return this; - } - - /** - * Set inPurgeable and inInputShareable when decoding. This will force the bitmap to be decoded - * from a byte array instead of a stream, since inPurgeable only affects the former. - *

- * Note: as of API level 21 (Lollipop), the inPurgeable field is deprecated and will be - * ignored. - */ - @NonNull - public RequestCreator purgeable() { - data.purgeable(); - return this; - } - - /** Disable brief fade in of images loaded from the disk cache or network. */ - @NonNull - public RequestCreator noFade() { - noFade = true; - return this; - } - - /** - * Synchronously fulfill this request. Must not be called from the main thread. - */ - @Nullable // TODO make non-null and always throw? - public Bitmap get() throws IOException { - long started = System.nanoTime(); - checkNotMain(); - - if (deferred) { - throw new IllegalStateException("Fit cannot be used with get."); - } - if (!data.hasImage()) { - return null; - } - - Request request = createRequest(started); - - Action action = new GetAction(picasso, request); - Result.Bitmap result = - forRequest(picasso, picasso.dispatcher, picasso.cache, action).hunt(); - if (result == null) { - return null; - } - - Bitmap bitmap = result.getBitmap(); - if (shouldWriteToMemoryCache(request.memoryPolicy)) { - picasso.cache.set(request.key, bitmap); - } - return bitmap; - } - - /** - * Asynchronously fulfills the request without a {@link ImageView} or {@link BitmapTarget}. This - * is useful when you want to warm up the cache with an image. - *

- * Note: It is safe to invoke this method from any thread. - */ - public void fetch() { - fetch(null); - } - - /** - * Asynchronously fulfills the request without a {@link ImageView} or {@link BitmapTarget}, - * and invokes the target {@link Callback} with the result. This is useful when you want to warm - * up the cache with an image. - *

- * Note: The {@link Callback} param is a strong reference and will prevent your - * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected - * until the request is completed. - */ - public void fetch(@Nullable Callback callback) { - long started = System.nanoTime(); - - if (deferred) { - throw new IllegalStateException("Fit cannot be used with fetch."); - } - if (data.hasImage()) { - // Fetch requests have lower priority by default. - if (!data.hasPriority()) { - data.priority(Priority.LOW); - } - - Request request = createRequest(started); - - if (shouldReadFromMemoryCache(request.memoryPolicy)) { - Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key); - if (bitmap != null) { - if (picasso.loggingEnabled) { - log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); - } - if (callback != null) { - callback.onSuccess(); - } - return; - } - } - - Action action = new FetchAction(picasso, request, callback); - picasso.submit(action); - } - } - - /** - * Asynchronously fulfills the request into the specified {@link BitmapTarget}. In most cases, you - * should use this when you are dealing with a custom {@link android.view.View View} or view - * holder which should implement the {@link BitmapTarget} interface. - *

- * Implementing on a {@link android.view.View View}: - *

-   * public class ProfileView extends FrameLayout implements Target {
-   *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
-   *     setBackgroundDrawable(new BitmapDrawable(bitmap));
-   *   }
-   *
-   *   {@literal @}Override public void onBitmapFailed(Exception e, Drawable errorDrawable) {
-   *     setBackgroundDrawable(errorDrawable);
-   *   }
-   *
-   *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
-   *     setBackgroundDrawable(placeHolderDrawable);
-   *   }
-   * }
-   * 
- * Implementing on a view holder object for use inside of an adapter: - *
-   * public class ViewHolder implements Target {
-   *   public FrameLayout frame;
-   *   public TextView name;
-   *
-   *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
-   *     frame.setBackgroundDrawable(new BitmapDrawable(bitmap));
-   *   }
-   *
-   *   {@literal @}Override public void onBitmapFailed(Exception e, Drawable errorDrawable) {
-   *     frame.setBackgroundDrawable(errorDrawable);
-   *   }
-   *
-   *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
-   *     frame.setBackgroundDrawable(placeHolderDrawable);
-   *   }
-   * }
-   * 
- *

- * To receive callbacks when an image is loaded use - * {@link #into(android.widget.ImageView, Callback)}. - */ - public void into(@NonNull BitmapTarget target) { - long started = System.nanoTime(); - checkMain(); - - if (target == null) { - throw new IllegalArgumentException("Target must not be null."); - } - if (deferred) { - throw new IllegalStateException("Fit cannot be used with a Target."); - } - - if (!data.hasImage()) { - picasso.cancelRequest(target); - target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null); - return; - } - - Request request = createRequest(started); - - if (shouldReadFromMemoryCache(request.memoryPolicy)) { - Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key); - if (bitmap != null) { - picasso.cancelRequest(target); - target.onBitmapLoaded(bitmap, MEMORY); - return; - } - } - - target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null); - - Action action = new BitmapTargetAction(picasso, target, request, errorDrawable, errorResId); - picasso.enqueueAndSubmit(action); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into a {@link Notification}. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, int notificationId, - @NonNull Notification notification) { - into(remoteViews, viewId, notificationId, notification, null); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into a {@link Notification}. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, int notificationId, - @NonNull Notification notification, @Nullable String notificationTag) { - into(remoteViews, viewId, notificationId, notification, notificationTag, null); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into a {@link Notification}. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, int notificationId, - @NonNull Notification notification, @Nullable String notificationTag, - @Nullable Callback callback) { - long started = System.nanoTime(); - - if (remoteViews == null) { - throw new IllegalArgumentException("RemoteViews must not be null."); - } - if (notification == null) { - throw new IllegalArgumentException("Notification must not be null."); - } - if (deferred) { - throw new IllegalStateException("Fit cannot be used with RemoteViews."); - } - if (placeholderDrawable != null || errorDrawable != null) { - throw new IllegalArgumentException( - "Cannot use placeholder or error drawables with remote views."); - } - - Request request = createRequest(started); - RemoteViewsAction action = - new NotificationAction(picasso, request, errorResId, - new RemoteViewsTarget(remoteViews, viewId), notificationId, notification, - notificationTag, callback); - - performRemoteViewInto(request, action); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into all instances of a widget. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, int appWidgetId) { - into(remoteViews, viewId, new int[] { appWidgetId }, null); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into all instances of a widget. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, - @NonNull int[] appWidgetIds) { - into(remoteViews, viewId, appWidgetIds, null); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into all instances of a widget. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, int appWidgetId, - @Nullable Callback callback) { - into(remoteViews, viewId, new int[] { appWidgetId }, callback); - } - - /** - * Asynchronously fulfills the request into the specified {@link RemoteViews} object with the - * given {@code viewId}. This is used for loading bitmaps into all instances of a widget. - */ - public void into(@NonNull RemoteViews remoteViews, @IdRes int viewId, @NonNull int[] appWidgetIds, - @Nullable Callback callback) { - long started = System.nanoTime(); - - if (remoteViews == null) { - throw new IllegalArgumentException("remoteViews must not be null."); - } - if (appWidgetIds == null) { - throw new IllegalArgumentException("appWidgetIds must not be null."); - } - if (deferred) { - throw new IllegalStateException("Fit cannot be used with remote views."); - } - if (placeholderDrawable != null || errorDrawable != null) { - throw new IllegalArgumentException( - "Cannot use placeholder or error drawables with remote views."); - } - - Request request = createRequest(started); - RemoteViewsAction action = - new AppWidgetAction(picasso, request, errorResId, - new RemoteViewsTarget(remoteViews, viewId), appWidgetIds, callback); - - performRemoteViewInto(request, action); - } - - /** - * Asynchronously fulfills the request into the specified {@link ImageView}. - *

- * Note: This method will automatically support object recycling. - */ - public void into(@NonNull ImageView target) { - into(target, null); - } - - /** - * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the - * target {@link Callback} if it's not {@code null}. - *

- * Note: The {@link Callback} param is a strong reference and will prevent your - * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If - * you use this method, it is strongly recommended you invoke an adjacent - * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking. - */ - public void into(@NonNull ImageView target, @Nullable Callback callback) { - long started = System.nanoTime(); - checkMain(); - - if (target == null) { - throw new IllegalArgumentException("Target must not be null."); - } - - if (!data.hasImage()) { - picasso.cancelRequest(target); - if (setPlaceholder) { - setPlaceholder(target, getPlaceholderDrawable()); - } - return; - } - - if (deferred) { - if (data.hasSize()) { - throw new IllegalStateException("Fit cannot be used with resize."); - } - int width = target.getWidth(); - int height = target.getHeight(); - if (width == 0 || height == 0) { - if (setPlaceholder) { - setPlaceholder(target, getPlaceholderDrawable()); - } - picasso.defer(target, new DeferredRequestCreator(this, target, callback)); - return; - } - data.resize(width, height); - } - - Request request = createRequest(started); - - if (shouldReadFromMemoryCache(request.memoryPolicy)) { - Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key); - if (bitmap != null) { - picasso.cancelRequest(target); - Result result = new Result.Bitmap(bitmap, MEMORY); - setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled); - if (picasso.loggingEnabled) { - log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); - } - if (callback != null) { - callback.onSuccess(); - } - return; - } - } - - if (setPlaceholder) { - setPlaceholder(target, getPlaceholderDrawable()); - } - - Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade, - callback); - picasso.enqueueAndSubmit(action); - } - - private @Nullable Drawable getPlaceholderDrawable() { - return placeholderResId == 0 - ? placeholderDrawable - : ContextCompat.getDrawable(picasso.context, placeholderResId); - } - - /** Create the request optionally passing it through the request transformer. */ - private Request createRequest(long started) { - int id = nextId.getAndIncrement(); - - Request request = data.build(); - request.id = id; - request.started = started; - - boolean loggingEnabled = picasso.loggingEnabled; - if (loggingEnabled) { - log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString()); - } - - Request transformed = picasso.transformRequest(request); - if (transformed != request) { - // If the request was changed, copy over the id and timestamp from the original. - transformed.id = id; - transformed.started = started; - - if (loggingEnabled) { - log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed); - } - } - - return transformed; - } - - private void performRemoteViewInto(Request request, RemoteViewsAction action) { - if (shouldReadFromMemoryCache(request.memoryPolicy)) { - Bitmap bitmap = picasso.quickMemoryCacheCheck(action.request.key); - if (bitmap != null) { - action.complete(new Result.Bitmap(bitmap, MEMORY)); - return; - } - } - - if (placeholderResId != 0) { - action.setImageResource(placeholderResId); - } - - picasso.enqueueAndSubmit(action); - } -} diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt new file mode 100644 index 0000000000..37811e0f48 --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2022 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.picasso3 + +import android.app.Notification +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.view.Gravity +import android.widget.ImageView +import android.widget.RemoteViews +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes +import androidx.annotation.VisibleForTesting +import androidx.core.content.ContextCompat +import com.squareup.picasso3.BitmapHunter.Companion.forRequest +import com.squareup.picasso3.MemoryPolicy.Companion.shouldReadFromMemoryCache +import com.squareup.picasso3.MemoryPolicy.Companion.shouldWriteToMemoryCache +import com.squareup.picasso3.Picasso.LoadedFrom +import com.squareup.picasso3.PicassoDrawable.setPlaceholder +import com.squareup.picasso3.PicassoDrawable.setResult +import com.squareup.picasso3.RemoteViewsAction.AppWidgetAction +import com.squareup.picasso3.RemoteViewsAction.NotificationAction +import com.squareup.picasso3.RemoteViewsAction.RemoteViewsTarget +import com.squareup.picasso3.Utils.OWNER_MAIN +import com.squareup.picasso3.Utils.VERB_COMPLETED +import com.squareup.picasso3.Utils.checkMain +import com.squareup.picasso3.Utils.checkNotMain +import com.squareup.picasso3.Utils.log +import java.io.IOException +import java.util.concurrent.atomic.AtomicInteger + +/** Fluent API for building an image download request. */ +open class RequestCreator { + val picasso: Picasso? + private val data: Request.Builder + private var noFade = false + private var deferred = false + private var setPlaceholder = true + @DrawableRes private var placeholderResId = 0 + @DrawableRes private var errorResId = 0 + private var placeholderDrawable: Drawable? = null + private var errorDrawable: Drawable? = null + + /** Internal use only. Used by [DeferredRequestCreator]. */ + internal open val tag: Any? + get() = data.tag + + internal constructor(picasso: Picasso, uri: Uri?, resourceId: Int) { + check(!picasso.shutdown) { "Picasso instance already shut down. Cannot submit new requests." } + this.picasso = picasso + data = Request.Builder(uri, resourceId, picasso.defaultBitmapConfig) + } + + @VisibleForTesting + internal constructor() { + picasso = null + data = Request.Builder(null, 0, null) + } + + /** + * Explicitly opt-out to having a placeholder set when calling `into`. + * + * By default, Picasso will either set a supplied placeholder or clear the target + * [ImageView] in order to ensure behavior in situations where views are recycled. This + * method will prevent that behavior and retain any already set image. + */ + fun noPlaceholder(): RequestCreator { + check(placeholderResId == 0) { "Placeholder resource already set." } + check(placeholderDrawable == null) { "Placeholder image already set." } + setPlaceholder = false + return this + } + + /** + * A placeholder drawable to be used while the image is being loaded. If the requested image is + * not immediately available in the memory cache then this resource will be set on the target + * [ImageView]. + */ + fun placeholder(@DrawableRes placeholderResId: Int): RequestCreator { + check(setPlaceholder) { "Already explicitly declared as no placeholder." } + require(placeholderResId != 0) { "Placeholder image resource invalid." } + check(placeholderDrawable == null) { "Placeholder image already set." } + this.placeholderResId = placeholderResId + return this + } + + /** + * A placeholder drawable to be used while the image is being loaded. If the requested image is + * not immediately available in the memory cache then this resource will be set on the target + * [ImageView]. + * + * If you are not using a placeholder image but want to clear an existing image (such as when + * used in an [adapter][android.widget.Adapter]), pass in `null`. + */ + fun placeholder(placeholderDrawable: Drawable): RequestCreator { + check(setPlaceholder) { "Already explicitly declared as no placeholder." } + check(placeholderResId == 0) { "Placeholder image already set." } + this.placeholderDrawable = placeholderDrawable + return this + } + + /** An error drawable to be used if the request image could not be loaded. */ + fun error(@DrawableRes errorResId: Int): RequestCreator { + require(errorResId != 0) { "Error image resource invalid." } + check(errorDrawable == null) { "Error image already set." } + this.errorResId = errorResId + return this + } + + /** An error drawable to be used if the request image could not be loaded. */ + fun error(errorDrawable: Drawable): RequestCreator { + check(errorResId == 0) { "Error image already set." } + this.errorDrawable = errorDrawable + return this + } + + /** + * Assign a tag to this request. Tags are an easy way to logically associate + * related requests that can be managed together e.g. paused, resumed, + * or canceled. + * + * You can either use simple [String] tags or objects that naturally + * define the scope of your requests within your app such as a + * [android.content.Context], an [android.app.Activity], or a + * [android.app.Fragment]. + * + * **WARNING:**: Picasso will keep a reference to the tag for + * as long as this tag is paused and/or has active requests. Look out for + * potential leaks. + * + * @see Picasso.cancelTag + * @see Picasso.pauseTag + * @see Picasso.resumeTag + */ + fun tag(tag: Any): RequestCreator { + data.tag(tag) + return this + } + + /** + * Attempt to resize the image to fit exactly into the target [ImageView]'s bounds. This + * will result in delayed execution of the request until the [ImageView] has been laid out. + * + * *Note:* This method works only when your target is an [ImageView]. + */ + fun fit(): RequestCreator { + deferred = true + return this + } + + /** Internal use only. Used by [DeferredRequestCreator]. */ + internal fun unfit(): RequestCreator { + deferred = false + return this + } + + /** Internal use only. Used by [DeferredRequestCreator]. */ + internal open fun clearTag(): RequestCreator { + data.clearTag() + return this + } + + /** + * Resize the image to the specified dimension size. + * Use 0 as desired dimension to resize keeping aspect ratio. + */ + fun resizeDimen( + @DimenRes targetWidthResId: Int, + @DimenRes targetHeightResId: Int, + ): RequestCreator { + val resources = picasso!!.context.resources + val targetWidth = resources.getDimensionPixelSize(targetWidthResId) + val targetHeight = resources.getDimensionPixelSize(targetHeightResId) + return resize(targetWidth, targetHeight) + } + + /** + * Resize the image to the specified size in pixels. + * Use 0 as desired dimension to resize keeping aspect ratio. + */ + fun resize(targetWidth: Int, targetHeight: Int): RequestCreator { + data.resize(targetWidth, targetHeight) + return this + } + + /** + * Crops an image inside of the bounds specified by [.resize] rather than + * distorting the aspect ratio. This cropping technique scales the image so that it fills the + * requested bounds and then crops the extra. + */ + fun centerCrop(): RequestCreator { + data.centerCrop(Gravity.CENTER) + return this + } + + /** + * Crops an image inside of the bounds specified by [.resize] rather than + * distorting the aspect ratio. This cropping technique scales the image so that it fills the + * requested bounds and then crops the extra, preferring the contents at `alignGravity`. + */ + fun centerCrop(alignGravity: Int): RequestCreator { + data.centerCrop(alignGravity) + return this + } + + /** + * Centers an image inside of the bounds specified by [.resize]. This scales + * the image so that both dimensions are equal to or less than the requested bounds. + */ + fun centerInside(): RequestCreator { + data.centerInside() + return this + } + + /** + * Only resize an image if the original image size is bigger than the target size + * specified by [.resize]. + */ + fun onlyScaleDown(): RequestCreator { + data.onlyScaleDown() + return this + } + + /** Rotate the image by the specified degrees. */ + fun rotate(degrees: Float): RequestCreator { + data.rotate(degrees) + return this + } + + /** Rotate the image by the specified degrees around a pivot point. */ + fun rotate(degrees: Float, pivotX: Float, pivotY: Float): RequestCreator { + data.rotate(degrees, pivotX, pivotY) + return this + } + + /** + * Attempt to decode the image using the specified config. + * + * + * Note: This value may be ignored by [BitmapFactory]. See + * [its documentation][BitmapFactory.Options.inPreferredConfig] for more details. + */ + fun config(config: Bitmap.Config): RequestCreator { + data.config(config) + return this + } + + /** + * Sets the stable key for this request to be used instead of the URI or resource ID when + * caching. Two requests with the same value are considered to be for the same resource. + */ + fun stableKey(stableKey: String?): RequestCreator { + data.stableKey(stableKey) + return this + } + + /** + * Set the priority of this request. + * + * + * This will affect the order in which the requests execute but does not guarantee it. + * By default, all requests have [Priority.NORMAL] priority, except for + * [.fetch] requests, which have [Priority.LOW] priority by default. + */ + fun priority(priority: Picasso.Priority): RequestCreator { + data.priority(priority) + return this + } + + /** + * Add a custom transformation to be applied to the image. + * + * + * Custom transformations will always be run after the built-in transformations. + */ + // TODO show example of calling resize after a transform in the javadoc + fun transform(transformation: Transformation): RequestCreator { + data.transform(transformation) + return this + } + + /** + * Add a list of custom transformations to be applied to the image. + * + * + * Custom transformations will always be run after the built-in transformations. + */ + fun transform(transformations: List): RequestCreator { + data.transform(transformations) + return this + } + + /** + * Specifies the [MemoryPolicy] to use for this request. You may specify additional policy + * options using the varargs parameter. + */ + fun memoryPolicy( + policy: MemoryPolicy, + vararg additional: MemoryPolicy, + ): RequestCreator { + data.memoryPolicy(policy, *additional) + return this + } + + /** + * Specifies the [NetworkPolicy] to use for this request. You may specify additional policy + * options using the varargs parameter. + */ + fun networkPolicy( + policy: NetworkPolicy, + vararg additional: NetworkPolicy, + ): RequestCreator { + data.networkPolicy(policy, *additional) + return this + } + + /** + * Set inPurgeable and inInputShareable when decoding. This will force the bitmap to be decoded + * from a byte array instead of a stream, since inPurgeable only affects the former. + * + * + * *Note*: as of API level 21 (Lollipop), the inPurgeable field is deprecated and will be + * ignored. + */ + fun purgeable(): RequestCreator { + data.purgeable() + return this + } + + /** Disable brief fade in of images loaded from the disk cache or network. */ + fun noFade(): RequestCreator { + noFade = true + return this + } + + /** + * Synchronously fulfill this request. Must not be called from the main thread. + */ + @Throws(IOException::class) // TODO make non-null and always throw? + fun get(): Bitmap? { + val started = System.nanoTime() + checkNotMain() + check(!deferred) { "Fit cannot be used with get." } + if (!data.hasImage()) { + return null + } + + val request = createRequest(started) + val action = GetAction(picasso!!, request) + val result = + forRequest(picasso, picasso.dispatcher, picasso.cache, action).hunt() ?: return null + + val bitmap = result.bitmap + if (shouldWriteToMemoryCache(request.memoryPolicy)) { + picasso.cache[request.key] = bitmap + } + + return bitmap + } + /** + * Asynchronously fulfills the request without a [ImageView] or [BitmapTarget], + * and invokes the target [Callback] with the result. This is useful when you want to warm + * up the cache with an image. + * + * *Note:* The [Callback] param is a strong reference and will prevent your + * [android.app.Activity] or [android.app.Fragment] from being garbage collected + * until the request is completed. + * + * *Note:* It is safe to invoke this method from any thread. + */ + @JvmOverloads fun fetch(callback: Callback? = null) { + val started = System.nanoTime() + check(!deferred) { "Fit cannot be used with fetch." } + + if (data.hasImage()) { + // Fetch requests have lower priority by default. + if (!data.hasPriority()) { + data.priority(Picasso.Priority.LOW) + } + + val request = createRequest(started) + if (shouldReadFromMemoryCache(request.memoryPolicy)) { + val bitmap = picasso!!.quickMemoryCacheCheck(request.key) + if (bitmap != null) { + if (picasso.loggingEnabled) { + log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + LoadedFrom.MEMORY) + } + callback?.onSuccess() + return + } + } + + val action = FetchAction(picasso!!, request, callback) + picasso.submit(action) + } + } + + /** + * Asynchronously fulfills the request into the specified [BitmapTarget]. In most cases, you + * should use this when you are dealing with a custom [View][android.view.View] or view + * holder which should implement the [BitmapTarget] interface. + * + * Implementing on a [View][android.view.View]: + * ``` + * class ProfileView(context: Context) : FrameLayout(context), Target { + * override fun onBitmapLoaded(bitmap: Bitmap, from: LoadedFrom) { + * setBackgroundDrawable(BitmapDrawable(bitmap)) + * } + * + * override run onBitmapFailed(e: Exception, errorDrawable: Drawable) { + * setBackgroundDrawable(errorDrawable) + * } + * + * override fun onPrepareLoad(placeholderDrawable: Drawable) { + * setBackgroundDrawable(placeholderDrawable + * } + * } + * ``` + */ + fun into(target: BitmapTarget) { + val started = System.nanoTime() + checkMain() + check(!deferred) { "Fit cannot be used with a Target." } + + if (!data.hasImage()) { + picasso!!.cancelRequest(target) + target.onPrepareLoad(if (setPlaceholder) getPlaceholderDrawable() else null) + return + } + + val request = createRequest(started) + if (shouldReadFromMemoryCache(request.memoryPolicy)) { + val bitmap = picasso!!.quickMemoryCacheCheck(request.key) + if (bitmap != null) { + picasso.cancelRequest(target) + target.onBitmapLoaded(bitmap, LoadedFrom.MEMORY) + return + } + } + + target.onPrepareLoad(if (setPlaceholder) getPlaceholderDrawable() else null) + val action: Action = BitmapTargetAction(picasso!!, target, request, errorDrawable, errorResId) + picasso.enqueueAndSubmit(action) + } + + /** + * Asynchronously fulfills the request into the specified [RemoteViews] object with the + * given `viewId`. This is used for loading bitmaps into a [Notification]. + */ + /** + * Asynchronously fulfills the request into the specified [RemoteViews] object with the + * given `viewId`. This is used for loading bitmaps into a [Notification]. + */ + @JvmOverloads + fun into( + remoteViews: RemoteViews, + @IdRes viewId: Int, + notificationId: Int, + notification: Notification, + notificationTag: String? = null, + callback: Callback? = null, + ) { + val started = System.nanoTime() + check(!deferred) { "Fit cannot be used with RemoteViews." } + require(!(placeholderDrawable != null || errorDrawable != null)) { + "Cannot use placeholder or error drawables with remote views." + } + + val request = createRequest(started) + val action = NotificationAction( + picasso!!, + request, + errorResId, + RemoteViewsTarget(remoteViews, viewId), + notificationId, + notification, + notificationTag, + callback + ) + performRemoteViewInto(request, action) + } + + /** + * Asynchronously fulfills the request into the specified [RemoteViews] object with the + * given `viewId`. This is used for loading bitmaps into all instances of a widget. + */ + fun into( + remoteViews: RemoteViews, + @IdRes viewId: Int, + appWidgetId: Int, + callback: Callback? = null, + ) { + into(remoteViews, viewId, intArrayOf(appWidgetId), callback) + } + + /** + * Asynchronously fulfills the request into the specified [RemoteViews] object with the + * given `viewId`. This is used for loading bitmaps into all instances of a widget. + */ + @JvmOverloads fun + into( + remoteViews: RemoteViews, + @IdRes viewId: Int, + appWidgetIds: IntArray, + callback: Callback? = null, + ) { + val started = System.nanoTime() + check(!deferred) { "Fit cannot be used with remote views." } + require(!(placeholderDrawable != null || errorDrawable != null)) { + "Cannot use placeholder or error drawables with remote views." + } + + val request = createRequest(started) + val action = AppWidgetAction( + picasso!!, + request, + errorResId, + RemoteViewsTarget(remoteViews, viewId), + appWidgetIds, + callback + ) + + performRemoteViewInto(request, action) + } + + /** + * Asynchronously fulfills the request into the specified [ImageView] and invokes the + * target [Callback] if it's not `null`. + * + * *Note:* The [Callback] param is a strong reference and will prevent your + * [android.app.Activity] or [android.app.Fragment] from being garbage collected. If + * you use this method, it is **strongly** recommended you invoke an adjacent + * [Picasso.cancelRequest] call to prevent temporary leaking. + * + * *Note:* This method will automatically support object recycling. + */ + @JvmOverloads fun into(target: ImageView, callback: Callback? = null) { + val started = System.nanoTime() + checkMain() + + if (!data.hasImage()) { + picasso!!.cancelRequest(target) + if (setPlaceholder) { + setPlaceholder(target, getPlaceholderDrawable()) + } + return + } + + if (deferred) { + check(!data.hasSize()) { "Fit cannot be used with resize." } + val width = target.width + val height = target.height + if (width == 0 || height == 0) { + if (setPlaceholder) { + setPlaceholder(target, getPlaceholderDrawable()) + } + picasso!!.defer(target, DeferredRequestCreator(this, target, callback)) + return + } + data.resize(width, height) + } + + val request = createRequest(started) + + if (shouldReadFromMemoryCache(request.memoryPolicy)) { + val bitmap = picasso!!.quickMemoryCacheCheck(request.key) + if (bitmap != null) { + picasso.cancelRequest(target) + val result: RequestHandler.Result = RequestHandler.Result.Bitmap(bitmap, LoadedFrom.MEMORY) + setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled) + if (picasso.loggingEnabled) { + log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + LoadedFrom.MEMORY) + } + callback?.onSuccess() + return + } + } + + if (setPlaceholder) { + setPlaceholder(target, getPlaceholderDrawable()) + } + + val action = ImageViewAction( + picasso!!, + target, + request, + errorDrawable, + errorResId, + noFade, + callback + ) + + picasso.enqueueAndSubmit(action) + } + + /** + * Build an immutable representation of this request. Does not execute the request. + */ + fun buildRequest(): Request { + return data.build() + } + + private fun getPlaceholderDrawable(): Drawable? { + return if (placeholderResId == 0) { + placeholderDrawable + } else { + ContextCompat.getDrawable(picasso!!.context, placeholderResId) + } + } + + /** Create the request optionally passing it through the request transformer. */ + private fun createRequest(started: Long): Request { + val id = nextId.getAndIncrement() + val request = data.build() + request.id = id + request.started = started + + val loggingEnabled = picasso!!.loggingEnabled + if (loggingEnabled) { + log(OWNER_MAIN, Utils.VERB_CREATED, request.plainId(), request.toString()) + } + + val transformed = picasso.transformRequest(request) + if (transformed != request) { + // If the request was changed, copy over the id and timestamp from the original. + transformed.id = id + transformed.started = started + if (loggingEnabled) { + log(OWNER_MAIN, Utils.VERB_CHANGED, transformed.logId(), "into $transformed") + } + } + + return transformed + } + + private fun performRemoteViewInto(request: Request, action: RemoteViewsAction) { + if (shouldReadFromMemoryCache(request.memoryPolicy)) { + val bitmap = picasso!!.quickMemoryCacheCheck(action.request.key) + if (bitmap != null) { + action.complete(RequestHandler.Result.Bitmap(bitmap, LoadedFrom.MEMORY)) + return + } + } + + if (placeholderResId != 0) { + action.setImageResource(placeholderResId) + } + + picasso!!.enqueueAndSubmit(action) + } + + companion object { + private val nextId = AtomicInteger() + } +} diff --git a/picasso/src/test/java/com/squareup/picasso3/DeferredRequestCreatorTest.kt b/picasso/src/test/java/com/squareup/picasso3/DeferredRequestCreatorTest.kt index 88dad62b73..f7d5909c0b 100644 --- a/picasso/src/test/java/com/squareup/picasso3/DeferredRequestCreatorTest.kt +++ b/picasso/src/test/java/com/squareup/picasso3/DeferredRequestCreatorTest.kt @@ -15,24 +15,25 @@ */ package com.squareup.picasso3 -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.mockito.Captor -import org.mockito.ArgumentCaptor -import org.junit.Before import com.google.common.truth.Truth.assertThat import com.squareup.picasso3.TestUtils.TRANSFORM_REQUEST_ANSWER import com.squareup.picasso3.TestUtils.mockCallback import com.squareup.picasso3.TestUtils.mockFitImageViewTarget +import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any +import org.mockito.Captor import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.withSettings import org.mockito.MockitoAnnotations.initMocks +import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DeferredRequestCreatorTest { @@ -69,7 +70,11 @@ class DeferredRequestCreatorTest { @Test fun cancelWhileAttachedRemovesAttachListener() { val target = mockFitImageViewTarget(true) - val request = DeferredRequestCreator(mock(RequestCreator::class.java), target, null) + val request = DeferredRequestCreator( + mock(RequestCreator::class.java, withSettings().useConstructor()), + target, + null + ) verify(target).addOnAttachStateChangeListener(request) request.cancel() verify(target).removeOnAttachStateChangeListener(request) @@ -78,7 +83,11 @@ class DeferredRequestCreatorTest { @Test fun cancelClearsCallback() { val target = mockFitImageViewTarget(true) val callback = mockCallback() - val request = DeferredRequestCreator(mock(RequestCreator::class.java), target, callback) + val request = DeferredRequestCreator( + mock(RequestCreator::class.java, withSettings().useConstructor()), + target, + callback + ) assertThat(request.callback).isNotNull() request.cancel() assertThat(request.callback).isNull() @@ -86,7 +95,7 @@ class DeferredRequestCreatorTest { @Test fun cancelClearsTag() { val target = mockFitImageViewTarget(true) - val creator = mock(RequestCreator::class.java) + val creator = mock(RequestCreator::class.java, withSettings().useConstructor()) `when`(creator.tag).thenReturn("TAG") val request = DeferredRequestCreator(creator, target, null) request.cancel() @@ -118,7 +127,7 @@ class DeferredRequestCreatorTest { @Test fun cancelSkipsIfViewTreeObserverIsDead() { val target = mockFitImageViewTarget(false) - val creator = mock(RequestCreator::class.java) + val creator = mock(RequestCreator::class.java, withSettings().useConstructor()) val request = DeferredRequestCreator(creator, target, null) request.cancel() verify(target.viewTreeObserver, never()).removeOnPreDrawListener(request) @@ -147,4 +156,4 @@ class DeferredRequestCreatorTest { assertThat(value.request.targetWidth).isEqualTo(100) assertThat(value.request.targetHeight).isEqualTo(100) } -} \ No newline at end of file +} diff --git a/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java b/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java index 358568ed0b..67fbb17f39 100644 --- a/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java @@ -162,15 +162,6 @@ public void getOnMainCrashes() throws IOException { verify(picasso).submit(actionCaptor.capture()); } - @Test - public void intoTargetWithNullThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into((BitmapTarget) null); - fail("Calling into() with null Target should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void intoTargetWithFitThrows() { try { @@ -249,15 +240,6 @@ public void intoTargetAndNotInCacheSubmitsTargetRequest() { assertThat(actionCaptor.getValue().getTag()).isEqualTo("tag"); } - @Test - public void intoImageViewWithNullThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into((ImageView) null); - fail("Calling into() with null ImageView should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void intoImageViewWithNullUriAndResourceIdSkipsAndCancels() { ImageView target = mockImageViewTarget(); @@ -444,15 +426,6 @@ public void intoImageViewWithFitAndResizeThrows() { assertThat(actionCaptor.getValue()).isInstanceOf(NotificationAction.class); } - @Test - public void intoRemoteViewsNotificationWithNullRemoteViewsThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into(null, 0, 0, mockNotification()); - fail("Calling into() with null RemoteViews should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void intoRemoteViewsWidgetWithPlaceholderDrawableThrows() { try { @@ -493,33 +466,6 @@ public void intoRemoteViewsNotificationWithErrorDrawableThrows() { } } - @Test - public void intoRemoteViewsWidgetWithNullRemoteViewsThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into(null, 0, new int[] { 1, 2, 3 }); - fail("Calling into() with null RemoteViews should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void intoRemoteViewsWidgetWithNullAppWidgetIdsThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into(mockRemoteViews(), 0, null); - fail("Calling into() with null appWidgetIds should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void intoRemoteViewsNotificationWithNullNotificationThrows() { - try { - new RequestCreator(picasso, URI_1, 0).into(mockRemoteViews(), 0, 0, (Notification) null); - fail("Calling into() with null Notification should throw exception"); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void intoRemoteViewsWidgetWithFitThrows() { try { @@ -703,11 +649,6 @@ public void intoTargetNoResizeWithCenterInsideOrCenterCropThrows() { fail("Resource ID of zero should throw exception."); } catch (IllegalArgumentException ignored) { } - try { - new RequestCreator().error(null); - fail("Null drawable should throw exception."); - } catch (IllegalArgumentException ignored) { - } try { new RequestCreator().error(1).error(new ColorDrawable(0)); fail("Two placeholders should throw exception."); @@ -772,19 +713,6 @@ public void nullKeyInTransformationListInvalid() { // TODO verify something! } - @Test public void nullTargetsInvalid() { - try { - new RequestCreator().into((ImageView) null); - fail("Null ImageView should throw exception."); - } catch (IllegalArgumentException ignored) { - } - try { - new RequestCreator().into((BitmapTarget) null); - fail("Null Target should throw exception."); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void imageViewActionWithStableKey() { new RequestCreator(picasso, URI_1, 0).stableKey(STABLE_1).into(mockImageViewTarget()); verify(picasso).enqueueAndSubmit(actionCaptor.capture()); diff --git a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java index d22091abb7..c040b1cc31 100644 --- a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java +++ b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; class TestUtils { static final Answer TRANSFORM_REQUEST_ANSWER = invocation -> invocation.getArguments()[0]; @@ -214,7 +215,7 @@ static Callback mockCallback() { static DeferredRequestCreator mockDeferredRequestCreator(ImageView target) { ViewTreeObserver observer = mock(ViewTreeObserver.class); when(target.getViewTreeObserver()).thenReturn(observer); - return new DeferredRequestCreator(mock(RequestCreator.class), target, null); + return new DeferredRequestCreator(mock(RequestCreator.class, withSettings().useConstructor()), target, null); } static NetworkInfo mockNetworkInfo() {