Home | History | Annotate | Download | only in camera
      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 
     59     private Thumbnail(Uri uri, Bitmap bitmap, int orientation) {
     60         mUri = uri;
     61         mBitmap = rotateImage(bitmap, orientation);
     62     }
     63 
     64     public Uri getUri() {
     65         return mUri;
     66     }
     67 
     68     public Bitmap getBitmap() {
     69         return mBitmap;
     70     }
     71 
     72     public void setFromFile(boolean fromFile) {
     73         mFromFile = fromFile;
     74     }
     75 
     76     public boolean fromFile() {
     77         return mFromFile;
     78     }
     79 
     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.
    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.
    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 
    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 {
    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 
    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 
    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 
    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 
    267     public static Bitmap createVideoThumbnailBitmap(FileDescriptor fd, int targetWidth) {
    268         return createVideoThumbnailBitmap(null, fd, targetWidth);
    269     }
    270 
    271     public static Bitmap createVideoThumbnailBitmap(String filePath, int targetWidth) {
    272         return createVideoThumbnailBitmap(filePath, null, targetWidth);
    273     }
    274 
    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 
    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