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