• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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