• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.camera;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.database.Cursor;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Matrix;
25 import android.media.MediaMetadataRetriever;
26 import android.net.Uri;
27 import android.provider.MediaStore.Images;
28 import android.provider.MediaStore.Images.ImageColumns;
29 import android.provider.MediaStore.MediaColumns;
30 import android.provider.MediaStore.Video;
31 import android.provider.MediaStore.Video.VideoColumns;
32 import android.util.Log;
33 
34 import java.io.BufferedInputStream;
35 import java.io.BufferedOutputStream;
36 import java.io.DataInputStream;
37 import java.io.DataOutputStream;
38 import java.io.File;
39 import java.io.FileDescriptor;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 
44 public class Thumbnail {
45     private static final String TAG = "Thumbnail";
46 
47     private static final String LAST_THUMB_FILENAME = "last_thumb";
48     private static final int BUFSIZE = 4096;
49 
50     private Uri mUri;
51     private Bitmap mBitmap;
52     // whether this thumbnail is read from file
53     private boolean mFromFile = false;
54 
55     // Camera, VideoCamera, and Panorama share the same thumbnail. Use sLock
56     // to serialize the storage access.
57     private static Object sLock = new Object();
58 
Thumbnail(Uri uri, Bitmap bitmap, int orientation)59     private Thumbnail(Uri uri, Bitmap bitmap, int orientation) {
60         mUri = uri;
61         mBitmap = rotateImage(bitmap, orientation);
62     }
63 
getUri()64     public Uri getUri() {
65         return mUri;
66     }
67 
getBitmap()68     public Bitmap getBitmap() {
69         return mBitmap;
70     }
71 
setFromFile(boolean fromFile)72     public void setFromFile(boolean fromFile) {
73         mFromFile = fromFile;
74     }
75 
fromFile()76     public boolean fromFile() {
77         return mFromFile;
78     }
79 
rotateImage(Bitmap bitmap, int orientation)80     private static Bitmap rotateImage(Bitmap bitmap, int orientation) {
81         if (orientation != 0) {
82             // We only rotate the thumbnail once even if we get OOM.
83             Matrix m = new Matrix();
84             m.setRotate(orientation, bitmap.getWidth() * 0.5f,
85                     bitmap.getHeight() * 0.5f);
86 
87             try {
88                 Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0,
89                         bitmap.getWidth(), bitmap.getHeight(), m, true);
90                 // If the rotated bitmap is the original bitmap, then it
91                 // should not be recycled.
92                 if (rotated != bitmap) bitmap.recycle();
93                 return rotated;
94             } catch (Throwable t) {
95                 Log.w(TAG, "Failed to rotate thumbnail", t);
96             }
97         }
98         return bitmap;
99     }
100 
101     // Stores the bitmap to the specified file.
saveLastThumbnailToFile(File filesDir)102     public void saveLastThumbnailToFile(File filesDir) {
103         File file = new File(filesDir, LAST_THUMB_FILENAME);
104         FileOutputStream f = null;
105         BufferedOutputStream b = null;
106         DataOutputStream d = null;
107         synchronized (sLock) {
108             try {
109                 f = new FileOutputStream(file);
110                 b = new BufferedOutputStream(f, BUFSIZE);
111                 d = new DataOutputStream(b);
112                 d.writeUTF(mUri.toString());
113                 mBitmap.compress(Bitmap.CompressFormat.JPEG, 90, d);
114                 d.close();
115             } catch (IOException e) {
116                 Log.e(TAG, "Fail to store bitmap. path=" + file.getPath(), e);
117             } finally {
118                 Util.closeSilently(f);
119                 Util.closeSilently(b);
120                 Util.closeSilently(d);
121             }
122         }
123     }
124 
125     // Loads the data from the specified file.
126     // Returns null if failure or the Uri is invalid.
getLastThumbnailFromFile(File filesDir, ContentResolver resolver)127     public static Thumbnail getLastThumbnailFromFile(File filesDir, ContentResolver resolver) {
128         File file = new File(filesDir, LAST_THUMB_FILENAME);
129         Uri uri = null;
130         Bitmap bitmap = null;
131         FileInputStream f = null;
132         BufferedInputStream b = null;
133         DataInputStream d = null;
134         synchronized (sLock) {
135             try {
136                 f = new FileInputStream(file);
137                 b = new BufferedInputStream(f, BUFSIZE);
138                 d = new DataInputStream(b);
139                 uri = Uri.parse(d.readUTF());
140                 if (!Util.isUriValid(uri, resolver)) {
141                     d.close();
142                     return null;
143                 }
144                 bitmap = BitmapFactory.decodeStream(d);
145                 d.close();
146             } catch (IOException e) {
147                 Log.i(TAG, "Fail to load bitmap. " + e);
148                 return null;
149             } finally {
150                 Util.closeSilently(f);
151                 Util.closeSilently(b);
152                 Util.closeSilently(d);
153             }
154         }
155         Thumbnail thumbnail = createThumbnail(uri, bitmap, 0);
156         if (thumbnail != null) thumbnail.setFromFile(true);
157         return thumbnail;
158     }
159 
160     public static final int THUMBNAIL_NOT_FOUND = 0;
161     public static final int THUMBNAIL_FOUND = 1;
162     // The media is deleted while we are getting its thumbnail from media provider.
163     public static final int THUMBNAIL_DELETED = 2;
164 
getLastThumbnailFromContentResolver(ContentResolver resolver, Thumbnail[] result)165     public static int getLastThumbnailFromContentResolver(ContentResolver resolver, Thumbnail[] result) {
166         Media image = getLastImageThumbnail(resolver);
167         Media video = getLastVideoThumbnail(resolver);
168         if (image == null && video == null) return THUMBNAIL_NOT_FOUND;
169 
170         Bitmap bitmap = null;
171         Media lastMedia;
172         // If there is only image or video, get its thumbnail. If both exist,
173         // get the thumbnail of the one that is newer.
174         if (image != null && (video == null || image.dateTaken >= video.dateTaken)) {
175             bitmap = Images.Thumbnails.getThumbnail(resolver, image.id,
176                     Images.Thumbnails.MINI_KIND, null);
177             lastMedia = image;
178         } else {
179             bitmap = Video.Thumbnails.getThumbnail(resolver, video.id,
180                     Video.Thumbnails.MINI_KIND, null);
181             lastMedia = video;
182         }
183 
184         // Ensure database and storage are in sync.
185         if (Util.isUriValid(lastMedia.uri, resolver)) {
186             result[0] = createThumbnail(lastMedia.uri, bitmap, lastMedia.orientation);
187             return THUMBNAIL_FOUND;
188         }
189         return THUMBNAIL_DELETED;
190     }
191 
192     private static class Media {
Media(long id, int orientation, long dateTaken, Uri uri)193         public Media(long id, int orientation, long dateTaken, Uri uri) {
194             this.id = id;
195             this.orientation = orientation;
196             this.dateTaken = dateTaken;
197             this.uri = uri;
198         }
199 
200         public final long id;
201         public final int orientation;
202         public final long dateTaken;
203         public final Uri uri;
204     }
205 
getLastImageThumbnail(ContentResolver resolver)206     private static Media getLastImageThumbnail(ContentResolver resolver) {
207         Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
208 
209         Uri query = baseUri.buildUpon().appendQueryParameter("limit", "1").build();
210         String[] projection = new String[] {ImageColumns._ID, ImageColumns.ORIENTATION,
211                 ImageColumns.DATE_TAKEN};
212         String selection = ImageColumns.MIME_TYPE + "='image/jpeg' AND " +
213                 ImageColumns.BUCKET_ID + '=' + Storage.BUCKET_ID;
214         String order = ImageColumns.DATE_TAKEN + " DESC," + ImageColumns._ID + " DESC";
215 
216         Cursor cursor = null;
217         try {
218             cursor = resolver.query(query, projection, selection, null, order);
219             if (cursor != null && cursor.moveToFirst()) {
220                 long id = cursor.getLong(0);
221                 return new Media(id, cursor.getInt(1), cursor.getLong(2),
222                                  ContentUris.withAppendedId(baseUri, id));
223             }
224         } finally {
225             if (cursor != null) {
226                 cursor.close();
227             }
228         }
229         return null;
230     }
231 
getLastVideoThumbnail(ContentResolver resolver)232     private static Media getLastVideoThumbnail(ContentResolver resolver) {
233         Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI;
234 
235         Uri query = baseUri.buildUpon().appendQueryParameter("limit", "1").build();
236         String[] projection = new String[] {VideoColumns._ID, MediaColumns.DATA,
237                 VideoColumns.DATE_TAKEN};
238         String selection = VideoColumns.BUCKET_ID + '=' + Storage.BUCKET_ID;
239         String order = VideoColumns.DATE_TAKEN + " DESC," + VideoColumns._ID + " DESC";
240 
241         Cursor cursor = null;
242         try {
243             cursor = resolver.query(query, projection, selection, null, order);
244             if (cursor != null && cursor.moveToFirst()) {
245                 Log.d(TAG, "getLastVideoThumbnail: " + cursor.getString(1));
246                 long id = cursor.getLong(0);
247                 return new Media(id, 0, cursor.getLong(2),
248                         ContentUris.withAppendedId(baseUri, id));
249             }
250         } finally {
251             if (cursor != null) {
252                 cursor.close();
253             }
254         }
255         return null;
256     }
257 
createThumbnail(byte[] jpeg, int orientation, int inSampleSize, Uri uri)258     public static Thumbnail createThumbnail(byte[] jpeg, int orientation, int inSampleSize,
259             Uri uri) {
260         // Create the thumbnail.
261         BitmapFactory.Options options = new BitmapFactory.Options();
262         options.inSampleSize = inSampleSize;
263         Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
264         return createThumbnail(uri, bitmap, orientation);
265     }
266 
createVideoThumbnailBitmap(FileDescriptor fd, int targetWidth)267     public static Bitmap createVideoThumbnailBitmap(FileDescriptor fd, int targetWidth) {
268         return createVideoThumbnailBitmap(null, fd, targetWidth);
269     }
270 
createVideoThumbnailBitmap(String filePath, int targetWidth)271     public static Bitmap createVideoThumbnailBitmap(String filePath, int targetWidth) {
272         return createVideoThumbnailBitmap(filePath, null, targetWidth);
273     }
274 
createVideoThumbnailBitmap(String filePath, FileDescriptor fd, int targetWidth)275     private static Bitmap createVideoThumbnailBitmap(String filePath, FileDescriptor fd,
276             int targetWidth) {
277         Bitmap bitmap = null;
278         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
279         try {
280             if (filePath != null) {
281                 retriever.setDataSource(filePath);
282             } else {
283                 retriever.setDataSource(fd);
284             }
285             bitmap = retriever.getFrameAtTime(-1);
286         } catch (IllegalArgumentException ex) {
287             // Assume this is a corrupt video file
288         } catch (RuntimeException ex) {
289             // Assume this is a corrupt video file.
290         } finally {
291             try {
292                 retriever.release();
293             } catch (RuntimeException ex) {
294                 // Ignore failures while cleaning up.
295             }
296         }
297         if (bitmap == null) return null;
298 
299         // Scale down the bitmap if it is bigger than we need.
300         int width = bitmap.getWidth();
301         int height = bitmap.getHeight();
302         if (width > targetWidth) {
303             float scale = (float) targetWidth / width;
304             int w = Math.round(scale * width);
305             int h = Math.round(scale * height);
306             bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
307         }
308         return bitmap;
309     }
310 
createThumbnail(Uri uri, Bitmap bitmap, int orientation)311     public static Thumbnail createThumbnail(Uri uri, Bitmap bitmap, int orientation) {
312         if (bitmap == null) {
313             Log.e(TAG, "Failed to create thumbnail from null bitmap");
314             return null;
315         }
316         return new Thumbnail(uri, bitmap, orientation);
317     }
318 }
319