1 /* 2 * Copyright (C) 2021 The Android Open Source Project 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 17 package com.android.providers.media.photopicker.ui; 18 19 import static com.bumptech.glide.load.resource.bitmap.Downsampler.PREFERRED_COLOR_SPACE; 20 21 import android.content.Context; 22 import android.graphics.Bitmap; 23 import android.graphics.drawable.Drawable; 24 import android.provider.CloudMediaProviderContract; 25 import android.provider.MediaStore; 26 import android.view.View; 27 import android.widget.ImageView; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.providers.media.photopicker.data.glide.GlideLoadable; 33 import com.android.providers.media.photopicker.data.model.Category; 34 import com.android.providers.media.photopicker.data.model.Item; 35 36 import com.bumptech.glide.Glide; 37 import com.bumptech.glide.RequestBuilder; 38 import com.bumptech.glide.load.Option; 39 import com.bumptech.glide.load.PreferredColorSpace; 40 import com.bumptech.glide.load.resource.gif.GifDrawable; 41 import com.bumptech.glide.request.RequestOptions; 42 import com.bumptech.glide.signature.ObjectKey; 43 44 import java.util.Optional; 45 46 /** 47 * A class to assist with loading and managing the Images (i.e. thumbnails and preview) associated 48 * with item. 49 */ 50 public class ImageLoader { 51 52 public static final Option<Boolean> THUMBNAIL_REQUEST = 53 Option.memory(CloudMediaProviderContract.EXTRA_MEDIASTORE_THUMB, false); 54 private static final String TAG = "ImageLoader"; 55 private static final RequestOptions THUMBNAIL_OPTION = 56 RequestOptions.option(THUMBNAIL_REQUEST, /* enableThumbnail */ true); 57 private final Context mContext; 58 private final String mMediaStoreVersion; 59 private final PreferredColorSpace mPreferredColorSpace; 60 private static final String PREVIEW_PREFIX = "preview_"; 61 ImageLoader(Context context)62 public ImageLoader(Context context) { 63 mContext = context; 64 mMediaStoreVersion = MediaStore.getVersion(mContext); 65 66 final boolean isScreenWideColorGamut = 67 mContext.getResources().getConfiguration().isScreenWideColorGamut(); 68 mPreferredColorSpace = 69 isScreenWideColorGamut ? PreferredColorSpace.DISPLAY_P3 : PreferredColorSpace.SRGB; 70 } 71 72 /** 73 * Load the thumbnail of the {@code category} and set it on the {@code imageView} 74 * 75 * @param category the album 76 * @param imageView the imageView shows the thumbnail 77 */ loadAlbumThumbnail(@onNull Category category, @NonNull ImageView imageView, int defaultThumbnailRes, @NonNull ImageView defaultIcon)78 public void loadAlbumThumbnail(@NonNull Category category, @NonNull ImageView imageView, 79 int defaultThumbnailRes, @NonNull ImageView defaultIcon) { 80 // Always show all thumbnails as bitmap images instead of drawables 81 // This is to ensure that we do not animate any thumbnail (for eg GIF) 82 // TODO(b/194285082): Use drawable instead of bitmap, as it saves memory. 83 if (category.getCoverUri() != null || defaultThumbnailRes == -1) { 84 defaultIcon.setVisibility(View.GONE); 85 imageView.setVisibility(View.VISIBLE); 86 87 loadWithGlide(getBitmapRequestBuilder(category.toGlideLoadable()), THUMBNAIL_OPTION, 88 /* signature */ null, imageView); 89 } else { 90 imageView.setVisibility(View.INVISIBLE); 91 defaultIcon.setVisibility(View.VISIBLE); 92 93 loadWithGlide(getDrawableRequestBuilder(mContext.getDrawable(defaultThumbnailRes)), 94 THUMBNAIL_OPTION, /* signature */ null, defaultIcon); 95 } 96 } 97 98 /** 99 * Load the thumbnail of the photo item {@code item} and set it on the {@code imageView} 100 * 101 * @param item the photo item 102 * @param imageView the imageView shows the thumbnail 103 */ loadPhotoThumbnail(@onNull Item item, @NonNull ImageView imageView)104 public void loadPhotoThumbnail(@NonNull Item item, @NonNull ImageView imageView) { 105 final GlideLoadable loadable = item.toGlideLoadable(); 106 // Always show all thumbnails as bitmap images instead of drawables 107 // This is to ensure that we do not animate any thumbnail (for eg GIF) 108 // TODO(b/194285082): Use drawable instead of bitmap, as it saves memory. 109 loadWithGlide(getBitmapRequestBuilder(loadable), THUMBNAIL_OPTION, 110 getGlideSignature(loadable, /* prefix */ null), imageView); 111 } 112 113 /** 114 * Load the image of the photo item {@code item} and set it on the {@code imageView} 115 * 116 * @param item the photo item 117 * @param imageView the imageView shows the image 118 */ loadImagePreview(@onNull Item item, @NonNull ImageView imageView)119 public void loadImagePreview(@NonNull Item item, @NonNull ImageView imageView) { 120 final GlideLoadable loadable = item.toGlideLoadable(); 121 if (item.isGif()) { 122 loadWithGlide( 123 getGifRequestBuilder(loadable), 124 /* requestOptions */ null, 125 getGlideSignature(loadable, /* prefix= */ PREVIEW_PREFIX), 126 imageView); 127 return; 128 } 129 130 if (item.isAnimatedWebp()) { 131 loadWithGlide( 132 getDrawableRequestBuilder(loadable), 133 /* requestOptions */ null, 134 getGlideSignature(loadable, PREVIEW_PREFIX), 135 imageView); 136 return; 137 } 138 139 // Preview as bitmap image for all other image types 140 loadWithGlide( 141 getBitmapRequestBuilder(loadable), 142 /* requestOptions */ null, 143 getGlideSignature(loadable, /* prefix= */ PREVIEW_PREFIX), 144 imageView); 145 } 146 147 /** 148 * Loads the image from first frame of the given video item 149 */ loadImageFromVideoForPreview(@onNull Item item, @NonNull ImageView imageView)150 public void loadImageFromVideoForPreview(@NonNull Item item, @NonNull ImageView imageView) { 151 final GlideLoadable loadable = item.toGlideLoadable(); 152 loadWithGlide( 153 getBitmapRequestBuilder(loadable), 154 new RequestOptions().frame(1000), 155 getGlideSignature(loadable, /* prefix= */ PREVIEW_PREFIX), 156 imageView); 157 } 158 getGlideSignature(GlideLoadable loadable, @Nullable String prefix)159 private ObjectKey getGlideSignature(GlideLoadable loadable, @Nullable String prefix) { 160 return loadable.getLoadableSignature( 161 /* prefix= */ mMediaStoreVersion + Optional.ofNullable(prefix).orElse("")); 162 } 163 getBitmapRequestBuilder(GlideLoadable loadable)164 private RequestBuilder<Bitmap> getBitmapRequestBuilder(GlideLoadable loadable) { 165 return Glide.with(mContext) 166 .asBitmap() 167 .load(loadable); 168 } 169 getGifRequestBuilder(GlideLoadable loadable)170 private RequestBuilder<GifDrawable> getGifRequestBuilder(GlideLoadable loadable) { 171 return Glide.with(mContext) 172 .asGif() 173 .load(loadable); 174 } 175 getDrawableRequestBuilder(Object model)176 private RequestBuilder<Drawable> getDrawableRequestBuilder(Object model) { 177 return Glide.with(mContext) 178 .load(model); 179 } 180 loadWithGlide(RequestBuilder<T> requestBuilder, @Nullable RequestOptions requestOptions, @Nullable ObjectKey signature, ImageView imageView)181 private <T> void loadWithGlide(RequestBuilder<T> requestBuilder, 182 @Nullable RequestOptions requestOptions, @Nullable ObjectKey signature, 183 ImageView imageView) { 184 RequestBuilder<T> newRequestBuilder = requestBuilder.clone(); 185 186 final RequestOptions requestOptionsWithPreferredColorSpace; 187 if (requestOptions != null) { 188 requestOptionsWithPreferredColorSpace = requestOptions.clone(); 189 } else { 190 requestOptionsWithPreferredColorSpace = new RequestOptions(); 191 } 192 requestOptionsWithPreferredColorSpace.set(PREFERRED_COLOR_SPACE, mPreferredColorSpace); 193 194 newRequestBuilder = newRequestBuilder.apply(requestOptionsWithPreferredColorSpace); 195 196 if (signature != null) { 197 newRequestBuilder = newRequestBuilder.signature(signature); 198 } 199 200 newRequestBuilder.into(imageView); 201 } 202 } 203