1package com.bumptech.glide.load.data;
2
3import android.content.ContentResolver;
4import android.content.Context;
5import android.database.Cursor;
6import android.net.Uri;
7import android.provider.MediaStore;
8import android.text.TextUtils;
9import com.bumptech.glide.Priority;
10
11import java.io.File;
12import java.io.FileNotFoundException;
13import java.io.IOException;
14import java.io.InputStream;
15
16public class MediaStoreThumbFetcher implements DataFetcher<InputStream> {
17    private static final int MINI_WIDTH = 512;
18    private static final int MINI_HEIGHT = 384;
19    private static final ThumbnailStreamOpenerFactory DEFAULT_FACTORY = new ThumbnailStreamOpenerFactory();
20
21    private final Context context;
22    private final Uri mediaStoreUri;
23    private final DataFetcher<InputStream> defaultFetcher;
24    private final int width;
25    private final int height;
26    private final long dateModified;
27    private final int orientation;
28    private final ThumbnailStreamOpenerFactory factory;
29    private InputStream inputStream;
30    private String mimeType;
31
32    public MediaStoreThumbFetcher(Context context, Uri mediaStoreUri, DataFetcher<InputStream> defaultFetcher,
33            int width, int height, String mimeType, long dateModified, int orientation) {
34        this(context, mediaStoreUri, defaultFetcher, width, height, mimeType, dateModified, orientation,
35                DEFAULT_FACTORY);
36    }
37
38    MediaStoreThumbFetcher(Context context, Uri mediaStoreUri, DataFetcher<InputStream> defaultFetcher, int width,
39            int height, String mimeType, long dateModified, int orientation, ThumbnailStreamOpenerFactory factory) {
40        this.context = context;
41        this.mediaStoreUri = mediaStoreUri;
42        this.defaultFetcher = defaultFetcher;
43        this.width = width;
44        this.height = height;
45        this.mimeType = mimeType;
46        this.dateModified = dateModified;
47        this.orientation = orientation;
48        this.factory = factory;
49    }
50
51    @Override
52    public InputStream loadData(Priority priority) throws Exception {
53        ThumbnailStreamOpener fetcher = factory.build(mediaStoreUri, width, height);
54
55        if (fetcher != null) {
56            inputStream = fetcher.open(context, mediaStoreUri);
57        }
58
59        if (inputStream != null) {
60            return inputStream;
61        } else {
62            return defaultFetcher.loadData(priority);
63        }
64    }
65
66    @Override
67    public void cleanup() {
68        if (inputStream != null) {
69            try {
70                inputStream.close();
71            } catch (IOException e) {
72                // Do nothing.
73            }
74        }
75        defaultFetcher.cleanup();
76    }
77
78    @Override
79    public String getId() {
80        return mediaStoreUri + mimeType + String.valueOf(dateModified) + String.valueOf(orientation);
81    }
82
83    @Override
84    public void cancel() {
85        // Do nothing.
86    }
87
88    private static boolean isMediaStoreUri(Uri uri) {
89        return uri != null
90                && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
91                && MediaStore.AUTHORITY.equals(uri.getAuthority());
92    }
93
94    private static boolean isMediaStoreVideo(Uri uri) {
95        return isMediaStoreUri(uri) && uri.getPathSegments().contains("video");
96    }
97
98    static class FileService {
99        public boolean exists(File file) {
100            return file.exists();
101        }
102
103        public long length(File file) {
104            return file.length();
105        }
106
107        public File get(String path) {
108            return new File(path);
109        }
110    }
111
112    interface ThumbnailQuery {
113        public Cursor query(Context context, Uri uri);
114    }
115
116    static class ThumbnailStreamOpener {
117        private static final FileService DEFAULT_SERVICE = new FileService();
118        private final FileService service;
119        private ThumbnailQuery query;
120
121        public ThumbnailStreamOpener(ThumbnailQuery query) {
122            this(DEFAULT_SERVICE, query);
123        }
124
125        public ThumbnailStreamOpener(FileService service, ThumbnailQuery query) {
126            this.service = service;
127            this.query = query;
128        }
129
130        public InputStream open(Context context, Uri uri) throws FileNotFoundException {
131            Uri thumbnailUri = null;
132            InputStream inputStream = null;
133
134            final Cursor cursor = query.query(context, uri);
135            try {
136                if (cursor != null && cursor.moveToFirst()) {
137                    String path = cursor.getString(0);
138                    if (!TextUtils.isEmpty(path)) {
139                        File file = service.get(path);
140                        if (service.exists(file) && service.length(file) > 0) {
141                            thumbnailUri = Uri.fromFile(file);
142                        }
143                    }
144                }
145            } finally {
146                if (cursor != null) {
147                    cursor.close();
148                }
149            }
150            if (thumbnailUri != null) {
151                inputStream = context.getContentResolver().openInputStream(thumbnailUri);
152            }
153            return inputStream;
154        }
155    }
156
157    static class ImageThumbnailQuery implements ThumbnailQuery {
158
159        @Override
160        public Cursor query(Context context, Uri uri) {
161            String id = uri.getLastPathSegment();
162            return context.getContentResolver().query(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, new String[] {
163                            MediaStore.Images.Thumbnails.DATA
164                    }, MediaStore.Images.Thumbnails.IMAGE_ID + " = ? AND " + MediaStore.Images.Thumbnails.KIND + " = ?",
165                    new String[] { id, String.valueOf(MediaStore.Images.Thumbnails.MINI_KIND) }, null);
166        }
167    }
168
169    static class VideoThumbnailQuery implements ThumbnailQuery {
170
171        @Override
172        public Cursor query(Context context, Uri uri) {
173            String id = uri.getLastPathSegment();
174            return context.getContentResolver().query(MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI, new String[] {
175                    MediaStore.Video.Thumbnails.DATA
176            }, MediaStore.Video.Thumbnails.VIDEO_ID + " = ? AND " + MediaStore.Video.Thumbnails.KIND + " = ?",
177                    new String[] { id, String.valueOf(MediaStore.Video.Thumbnails.MINI_KIND) }, null);
178        }
179    }
180
181    static class ThumbnailStreamOpenerFactory {
182
183        public ThumbnailStreamOpener build(Uri uri, int width, int height) {
184            if (!isMediaStoreUri(uri) || width > MINI_WIDTH || height > MINI_HEIGHT) {
185                return null;
186            } else if (isMediaStoreVideo(uri)) {
187                return new ThumbnailStreamOpener(new VideoThumbnailQuery());
188            } else {
189                return new ThumbnailStreamOpener(new ImageThumbnailQuery());
190            }
191        }
192    }
193}
194