1package com.bumptech.glide;
2
3import android.app.Activity;
4import android.app.Fragment;
5import android.content.Context;
6import android.net.Uri;
7import android.os.ParcelFileDescriptor;
8import android.provider.MediaStore;
9import android.support.v4.app.FragmentActivity;
10import com.bumptech.glide.load.ResourceDecoder;
11import com.bumptech.glide.load.ResourceEncoder;
12import com.bumptech.glide.load.model.ModelLoader;
13import com.bumptech.glide.load.model.ModelLoaderFactory;
14import com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader;
15import com.bumptech.glide.load.model.stream.MediaStoreStreamLoader;
16import com.bumptech.glide.load.model.stream.StreamByteArrayLoader;
17import com.bumptech.glide.load.model.stream.StreamFileLoader;
18import com.bumptech.glide.load.model.stream.StreamModelLoader;
19import com.bumptech.glide.load.model.stream.StreamResourceLoader;
20import com.bumptech.glide.load.model.stream.StreamStringLoader;
21import com.bumptech.glide.load.model.stream.StreamUriLoader;
22import com.bumptech.glide.manager.ConnectivityMonitor;
23import com.bumptech.glide.manager.ConnectivityMonitorFactory;
24import com.bumptech.glide.manager.RequestTracker;
25import com.bumptech.glide.volley.VolleyUrlLoader;
26
27import java.io.File;
28import java.io.InputStream;
29import java.net.URL;
30import java.util.UUID;
31
32/**
33 * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity lifecycle events to
34 * intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage
35 * built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity.
36 *
37 * @see Glide#with(Activity)
38 * @see Glide#with(FragmentActivity)
39 * @see Glide#with(Fragment)
40 * @see Glide#with(android.support.v4.app.Fragment)
41 * @see Glide#with(Context)
42 */
43public class RequestManager {
44    private final ConnectivityMonitor connectivityMonitor;
45    private final Context context;
46    private final RequestTracker requestTracker;
47    private final Glide glide;
48    private final OptionsApplier optionsApplier;
49    private DefaultOptions options;
50
51    public RequestManager(Context context) {
52        this(context, new RequestTracker(), new ConnectivityMonitorFactory());
53    }
54
55    RequestManager(Context context, RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
56        this.context = context;
57        this.requestTracker = requestTracker;
58        this.connectivityMonitor = factory.build(context, new RequestManagerConnectivityListener(requestTracker));
59        connectivityMonitor.register();
60        this.glide = Glide.get(context);
61        this.optionsApplier = new OptionsApplier();
62    }
63
64    public interface DefaultOptions {
65        public <T> void apply(T model, GenericRequestBuilder<T, ?, ?, ?> requestBuilder);
66    }
67
68    public void setDefaultOptions(DefaultOptions options) {
69        this.options = options;
70    }
71
72    /**
73     * Cancels any in progress loads, but does not clear resources of completed loads.
74     */
75    public void pauseRequests() {
76        requestTracker.pauseRequests();
77    }
78
79    /**
80     * Restarts any loads that have not yet completed.
81     */
82    public void resumeRequests() {
83        requestTracker.resumeRequests();
84    }
85
86    /**
87     * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
88     * permission is present) and restarts failed or paused requests.
89     */
90    public void onStart() {
91        // onStart might not be called because this object may be created after the fragment/activity's onStart method.
92        connectivityMonitor.register();
93
94        requestTracker.resumeRequests();
95    }
96
97    /**
98     * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
99     * permission is present) and pauses in progress loads.
100     */
101    public void onStop() {
102        connectivityMonitor.unregister();
103        requestTracker.pauseRequests();
104    }
105
106    /**
107     * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed
108     * requests.
109     */
110    public void onDestroy() {
111        requestTracker.clearRequests();
112    }
113
114    /**
115     * Use the given generic model loader to load the given generic data class.
116     * <p>
117     *     Note that in most cases you will also need to specify an {@link ResourceDecoder} and an
118     *     {@link ResourceEncoder} for the load to complete successfully.
119     * </p>
120     * @param modelLoader The {@link ModelLoader} class to use to load the model.
121     * @param dataClass The type of data the {@link ModelLoader} will load.
122     * @param <A> The type of the model to be loaded.
123     * @param <T> The type of the data to be loaded from the mode.
124     * @return A {@link GenericModelRequest} to set options for the load and ultimately the target to load the model
125     * into.
126     */
127    public <A, T> GenericModelRequest<A, T> using(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
128        return new GenericModelRequest<A, T>(modelLoader, dataClass);
129    }
130
131    /**
132     * Set the {@link ModelLoader} to use for for a new load where the model loader translates from a model to an
133     * {@link InputStream} resource for loading images.
134     *
135     * @param modelLoader The model loader to use.
136     * @param <T> The type of the model.
137     * @return A new {@link ImageModelRequest}.
138     */
139    public <T> ImageModelRequest<T> using(final StreamModelLoader<T> modelLoader) {
140        return new ImageModelRequest<T>(modelLoader);
141    }
142
143    /**
144     * A convenience method to use a {@link StreamByteArrayLoader} to decode an image from a byte array.
145     *
146     * @param modelLoader The byte array loader.
147     * @return A new {@link ImageModelRequest}.
148     */
149    public ImageModelRequest<byte[]> using(StreamByteArrayLoader modelLoader) {
150        return new ImageModelRequest<byte[]>(modelLoader);
151    }
152
153    /**
154     * Set the {@link ModelLoader} to use for a new load where the model loader translates from a model to an
155     * {@link ParcelFileDescriptor} resource for loading video thumbnails.
156     *
157     * @param modelLoader The model loader to use.
158     * @param <T> The type of the model.
159     * @return A new {@link VideoModelRequest}.
160     */
161    public <T> VideoModelRequest<T> using(final FileDescriptorModelLoader<T> modelLoader) {
162        return new VideoModelRequest<T>(modelLoader);
163    }
164
165    /**
166     * Use the {@link ModelLoaderFactory} currently registered for {@link String} to load the image represented by
167     * the given {@link String}. Defaults to {@link StreamStringLoader.Factory} and {@link StreamStringLoader} to
168     * load the given model.
169     *
170     * @see #using(StreamModelLoader)
171     *
172     * @param string The string representing the image. Must be either a path, or a uri handled by
173     *      {@link StreamUriLoader}
174     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
175     * into.
176     */
177    public DrawableTypeRequest<String> load(String string) {
178        return loadGeneric(string);
179    }
180
181    /**
182     * Use the {@link ModelLoaderFactory} currently registered for {@link Uri} to load the image at the given uri.
183     * Defaults to {@link StreamUriLoader.Factory} and {@link StreamUriLoader}.
184     *
185     * @see #using(StreamModelLoader)
186     *
187     * @param uri The uri representing the image. Must be a uri handled by {@link StreamUriLoader}
188     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
189     * into.
190     */
191    public DrawableTypeRequest<Uri> load(Uri uri) {
192        return loadGeneric(uri);
193    }
194
195    /**
196     * Use {@link MediaStore.Images.Thumbnails} and {@link MediaStore.Video.Thumbnails} to retrieve pre-generated
197     * thumbnails for the given uri. Falls back to the registered {@link ModelLoaderFactory} registered for {@link Uri}s
198     * if the given uri is not a media store uri or if no pre-generated thumbnail exists for the given uri. In addition,
199     * mixes the given mimeType, dateModified, and orientation into the cache key to detect and invalidate thumbnails
200     * if content is changed locally.
201     *
202     * @param uri The uri representing the media.
203     * @param mimeType The mime type of the media store media. Ok to default to empty string "". See
204     *      {@link MediaStore.Images.ImageColumns#MIME_TYPE} or {@link MediaStore.Video.VideoColumns#MIME_TYPE}.
205     * @param dateModified The date modified time of the media store media. Ok to default to 0. See
206     *      {@link MediaStore.Images.ImageColumns#DATE_MODIFIED} or {@link MediaStore.Video.VideoColumns#DATE_MODIFIED}.
207     * @param orientation The orientation of the media store media. Ok to default to 0. See
208     *      {@link MediaStore.Images.ImageColumns#ORIENTATION}.
209     * @return A new {@link DrawableRequestBuilder} to set options for the load and ultimately the target to load the
210     *      uri into.
211     */
212    public DrawableTypeRequest<Uri> loadFromMediaStore(Uri uri, String mimeType, long dateModified, int orientation) {
213        ModelLoader<Uri, InputStream> genericStreamLoader = Glide.buildStreamModelLoader(uri, context);
214        ModelLoader<Uri, InputStream> mediaStoreLoader = new MediaStoreStreamLoader(context, genericStreamLoader,
215                mimeType, dateModified, orientation);
216        ModelLoader<Uri, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(uri,
217                context);
218        return optionsApplier.apply(uri, new DrawableTypeRequest<Uri>(uri, mediaStoreLoader, fileDescriptorModelLoader,
219                context, glide, requestTracker, optionsApplier));
220    }
221
222    /**
223     * Use the {@link ModelLoaderFactory} currently registered for {@link File} to load the image represented by the
224     * given {@link File}. Defaults to {@link StreamFileLoader.Factory} and {@link StreamFileLoader} to load the
225     * given model.
226     *
227     * @see #using(StreamModelLoader)
228     *
229     * @param file The File containing the image
230     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
231     * into.
232     */
233    public DrawableTypeRequest<File> load(File file) {
234        return loadGeneric(file);
235    }
236
237    /**
238     * Use the {@link ModelLoaderFactory} currently registered for {@link Integer} to load the image represented by
239     * the given {@link Integer} resource id. Defaults to {@link StreamResourceLoader.Factory} and
240     * {@link StreamResourceLoader} to load the given model.
241     *
242     * @see #using(StreamModelLoader)
243     *
244     * @param resourceId the id of the resource containing the image
245     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
246     * into.
247     */
248    public DrawableTypeRequest<Integer> load(Integer resourceId) {
249        return loadGeneric(resourceId);
250    }
251
252    /**
253     * Use the {@link ModelLoaderFactory} currently registered for the given model type to load the image
254     * represented by the given model.
255     *
256     * @param model The model to load.
257     * @param <T> The type of the model to load.
258     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
259     * into.
260     */
261    @SuppressWarnings("unused")
262    public <T> DrawableTypeRequest<T> loadFromImage(T model) {
263        return loadGeneric(model);
264    }
265
266    /**
267     * Use the {@link ModelLoaderFactory} currently registered for {@link URL} to load the image represented by the
268     * given {@link URL}. Defaults to {@link VolleyUrlLoader.Factory} and {@link VolleyUrlLoader} to load the given
269     * model.
270     *
271     * @see #using(StreamModelLoader)
272     *
273     * @param url The URL representing the image.
274     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
275     * into.
276     */
277    public DrawableTypeRequest<URL> loadFromImage(URL url) {
278        return loadGeneric(url);
279    }
280
281    /**
282     * Use a new {@link StreamByteArrayLoader} to load an image from the given model.
283     *
284     * @see #loadFromImage(byte[])
285     *
286     * @param model The data to load.
287     * @param id A unique id that identifies the image represented by the model suitable for use as a cache key
288     *           (url, filepath etc). If there is no suitable id, use {@link #loadFromImage(byte[])} instaed.
289     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
290     * into.
291     */
292    public DrawableTypeRequest<byte[]> loadFromImage(byte[] model, final String id) {
293        final StreamByteArrayLoader loader = new StreamByteArrayLoader(id);
294        return optionsApplier.apply(model,
295                new DrawableTypeRequest<byte[]>(model, loader, null, context, glide, requestTracker, optionsApplier));
296    }
297
298    /**
299     * Use a new {@link StreamByteArrayLoader} to load an image from the given model. Suitable when there is no
300     * simple id that represents the given data.
301     *
302     * @param model the data to load.
303     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
304     * into.
305     */
306    public DrawableTypeRequest<byte[]> loadFromImage(byte[] model) {
307        return loadFromImage(model, UUID.randomUUID()
308                .toString());
309    }
310
311    /**
312     * Use the {@link ModelLoaderFactory} currently registered for the given model type for
313     * {@link ParcelFileDescriptor}s to load a thumbnail for the video represented by the given model.
314     *
315     * @param model The model to load.
316     * @param <T> The type of the model to load.
317     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
318     * into.
319     */
320    @SuppressWarnings("unused")
321    public <T> DrawableTypeRequest<T> loadFromVideo(T model) {
322        return loadGeneric(model);
323    }
324
325    /**
326     * Use the {@link ModelLoaderFactory}s currently registered for the given model type for
327     * {@link InputStream}s and {@link ParcelFileDescriptor}s to load a thumbnail from either the image or the video
328     * represented by the given model.
329     *
330     * @param model The model the load.
331     * @param <T> The type of the model to load.
332     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
333     * into.
334     */
335    public <T> DrawableTypeRequest<T> load(T model) {
336        return loadGeneric(model);
337    }
338
339    private <T> DrawableTypeRequest<T> loadGeneric(T model) {
340        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(model, context);
341        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
342                Glide.buildFileDescriptorModelLoader(model, context);
343        if (model != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
344            throw new IllegalArgumentException("Unknown type " + model + ". You must provide a Model of a type for"
345                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
346                    + " Glide#register with a ModelLoaderFactory for your custom model class");
347        }
348        return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, streamModelLoader,
349                fileDescriptorModelLoader, context, glide, requestTracker, optionsApplier));
350    }
351
352    /**
353     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
354     * {@link ParcelFileDescriptor} resources for loading video thumbnails.
355     *
356     * @param <T> The type of the model.
357     */
358    public class VideoModelRequest<T> {
359        private final ModelLoader<T, ParcelFileDescriptor> loader;
360
361        private VideoModelRequest(ModelLoader<T, ParcelFileDescriptor> loader) {
362            this.loader = loader;
363        }
364
365        public BitmapTypeRequest<T> loadFromVideo(T model) {
366            return optionsApplier.apply(model, new BitmapTypeRequest<T>(context, model, null, loader, glide,
367                    requestTracker, optionsApplier));
368        }
369    }
370
371    /**
372     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
373     * {@link InputStream} resources for loading images.
374     *
375     * @param <T> The type of the model.
376     */
377    public class ImageModelRequest<T> {
378        private final ModelLoader<T, InputStream> loader;
379
380        private ImageModelRequest(ModelLoader<T, InputStream> loader) {
381            this.loader = loader;
382        }
383
384        public DrawableTypeRequest<T> load(T model) {
385            return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, loader, null, context, glide,
386                    requestTracker, optionsApplier));
387        }
388    }
389
390    class OptionsApplier {
391
392        public <A, X extends GenericRequestBuilder<A, ?, ?, ?>> X apply(A model, X builder) {
393            if (options != null) {
394                options.apply(model, builder);
395            }
396            return builder;
397        }
398
399    }
400
401    /**
402     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
403     * {@link InputStream} resources for loading images.
404     *
405     * @param <T> The type of the model.
406     */
407    public class GenericModelRequest<A, T> {
408        private final ModelLoader<A, T> modelLoader;
409        private final Class<T> dataClass;
410
411        private GenericModelRequest(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
412            this.modelLoader = modelLoader;
413            this.dataClass = dataClass;
414        }
415
416        public GenericTypeRequest load(A model) {
417            return new GenericTypeRequest(model, modelLoader, dataClass);
418        }
419
420        public class GenericTypeRequest {
421            private final A model;
422            private final ModelLoader<A, T> modelLoader;
423            private final Class<T> dataClass;
424
425            private GenericTypeRequest(A model, ModelLoader<A, T> modelLoader, Class<T> dataClass) {
426                this.model = model;
427                this.modelLoader = modelLoader;
428                this.dataClass = dataClass;
429            }
430
431            public <Z> GenericTranscodeRequest<A, T, Z> as(Class<Z> resourceClass) {
432                return optionsApplier.apply(model, new GenericTranscodeRequest<A, T, Z>(context, glide, model,
433                        modelLoader, dataClass, resourceClass, requestTracker, optionsApplier));
434            }
435        }
436    }
437
438    private static class RequestManagerConnectivityListener implements ConnectivityMonitor.ConnectivityListener {
439        private RequestTracker requestTracker;
440
441        public RequestManagerConnectivityListener(RequestTracker requestTracker) {
442            this.requestTracker = requestTracker;
443        }
444
445        @Override
446        public void onConnectivityChanged(boolean isConnected) {
447            if (isConnected) {
448                requestTracker.restartRequests();
449            }
450        }
451    }
452}
453