Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2010 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.gallery3d.data;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.database.Cursor;
     22 import android.graphics.Bitmap;
     23 import android.graphics.BitmapFactory;
     24 import android.graphics.BitmapRegionDecoder;
     25 import android.media.ExifInterface;
     26 import android.net.Uri;
     27 import android.provider.MediaStore.Images;
     28 import android.provider.MediaStore.Images.ImageColumns;
     29 import android.util.Log;
     30 
     31 import com.android.gallery3d.app.GalleryApp;
     32 import com.android.gallery3d.common.BitmapUtils;
     33 import com.android.gallery3d.util.GalleryUtils;
     34 import com.android.gallery3d.util.ThreadPool.Job;
     35 import com.android.gallery3d.util.ThreadPool.JobContext;
     36 import com.android.gallery3d.util.UpdateHelper;
     37 
     38 import java.io.File;
     39 import java.io.IOException;
     40 
     41 // LocalImage represents an image in the local storage.
     42 public class LocalImage extends LocalMediaItem {
     43     private static final String TAG = "LocalImage";
     44 
     45     static final Path ITEM_PATH = Path.fromString("/local/image/item");
     46 
     47     // Must preserve order between these indices and the order of the terms in
     48     // the following PROJECTION array.
     49     private static final int INDEX_ID = 0;
     50     private static final int INDEX_CAPTION = 1;
     51     private static final int INDEX_MIME_TYPE = 2;
     52     private static final int INDEX_LATITUDE = 3;
     53     private static final int INDEX_LONGITUDE = 4;
     54     private static final int INDEX_DATE_TAKEN = 5;
     55     private static final int INDEX_DATE_ADDED = 6;
     56     private static final int INDEX_DATE_MODIFIED = 7;
     57     private static final int INDEX_DATA = 8;
     58     private static final int INDEX_ORIENTATION = 9;
     59     private static final int INDEX_BUCKET_ID = 10;
     60     private static final int INDEX_SIZE = 11;
     61     private static final int INDEX_WIDTH = 12;
     62     private static final int INDEX_HEIGHT = 13;
     63 
     64     static final String[] PROJECTION =  {
     65             ImageColumns._ID,           // 0
     66             ImageColumns.TITLE,         // 1
     67             ImageColumns.MIME_TYPE,     // 2
     68             ImageColumns.LATITUDE,      // 3
     69             ImageColumns.LONGITUDE,     // 4
     70             ImageColumns.DATE_TAKEN,    // 5
     71             ImageColumns.DATE_ADDED,    // 6
     72             ImageColumns.DATE_MODIFIED, // 7
     73             ImageColumns.DATA,          // 8
     74             ImageColumns.ORIENTATION,   // 9
     75             ImageColumns.BUCKET_ID,     // 10
     76             ImageColumns.SIZE,          // 11
     77             ImageColumns.WIDTH,         // 12
     78             ImageColumns.HEIGHT         // 13
     79     };
     80 
     81     private final GalleryApp mApplication;
     82 
     83     public int rotation;
     84 
     85     public LocalImage(Path path, GalleryApp application, Cursor cursor) {
     86         super(path, nextVersionNumber());
     87         mApplication = application;
     88         loadFromCursor(cursor);
     89     }
     90 
     91     public LocalImage(Path path, GalleryApp application, int id) {
     92         super(path, nextVersionNumber());
     93         mApplication = application;
     94         ContentResolver resolver = mApplication.getContentResolver();
     95         Uri uri = Images.Media.EXTERNAL_CONTENT_URI;
     96         Cursor cursor = LocalAlbum.getItemCursor(resolver, uri, PROJECTION, id);
     97         if (cursor == null) {
     98             throw new RuntimeException("cannot get cursor for: " + path);
     99         }
    100         try {
    101             if (cursor.moveToNext()) {
    102                 loadFromCursor(cursor);
    103             } else {
    104                 throw new RuntimeException("cannot find data for: " + path);
    105             }
    106         } finally {
    107             cursor.close();
    108         }
    109     }
    110 
    111     private void loadFromCursor(Cursor cursor) {
    112         id = cursor.getInt(INDEX_ID);
    113         caption = cursor.getString(INDEX_CAPTION);
    114         mimeType = cursor.getString(INDEX_MIME_TYPE);
    115         latitude = cursor.getDouble(INDEX_LATITUDE);
    116         longitude = cursor.getDouble(INDEX_LONGITUDE);
    117         dateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN);
    118         filePath = cursor.getString(INDEX_DATA);
    119         rotation = cursor.getInt(INDEX_ORIENTATION);
    120         bucketId = cursor.getInt(INDEX_BUCKET_ID);
    121         fileSize = cursor.getLong(INDEX_SIZE);
    122         width = cursor.getInt(INDEX_WIDTH);
    123         height = cursor.getInt(INDEX_HEIGHT);
    124     }
    125 
    126     @Override
    127     protected boolean updateFromCursor(Cursor cursor) {
    128         UpdateHelper uh = new UpdateHelper();
    129         id = uh.update(id, cursor.getInt(INDEX_ID));
    130         caption = uh.update(caption, cursor.getString(INDEX_CAPTION));
    131         mimeType = uh.update(mimeType, cursor.getString(INDEX_MIME_TYPE));
    132         latitude = uh.update(latitude, cursor.getDouble(INDEX_LATITUDE));
    133         longitude = uh.update(longitude, cursor.getDouble(INDEX_LONGITUDE));
    134         dateTakenInMs = uh.update(
    135                 dateTakenInMs, cursor.getLong(INDEX_DATE_TAKEN));
    136         dateAddedInSec = uh.update(
    137                 dateAddedInSec, cursor.getLong(INDEX_DATE_ADDED));
    138         dateModifiedInSec = uh.update(
    139                 dateModifiedInSec, cursor.getLong(INDEX_DATE_MODIFIED));
    140         filePath = uh.update(filePath, cursor.getString(INDEX_DATA));
    141         rotation = uh.update(rotation, cursor.getInt(INDEX_ORIENTATION));
    142         bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID));
    143         fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
    144         width = uh.update(width, cursor.getInt(INDEX_WIDTH));
    145         height = uh.update(height, cursor.getInt(INDEX_HEIGHT));
    146         return uh.isUpdated();
    147     }
    148 
    149     @Override
    150     public Job<Bitmap> requestImage(int type) {
    151         return new LocalImageRequest(mApplication, mPath, type, filePath);
    152     }
    153 
    154     public static class LocalImageRequest extends ImageCacheRequest {
    155         private String mLocalFilePath;
    156 
    157         LocalImageRequest(GalleryApp application, Path path, int type,
    158                 String localFilePath) {
    159             super(application, path, type, MediaItem.getTargetSize(type));
    160             mLocalFilePath = localFilePath;
    161         }
    162 
    163         @Override
    164         public Bitmap onDecodeOriginal(JobContext jc, final int type) {
    165             BitmapFactory.Options options = new BitmapFactory.Options();
    166             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    167             int targetSize = MediaItem.getTargetSize(type);
    168 
    169             // try to decode from JPEG EXIF
    170             if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
    171                 ExifInterface exif = null;
    172                 byte [] thumbData = null;
    173                 try {
    174                     exif = new ExifInterface(mLocalFilePath);
    175                     if (exif != null) {
    176                         thumbData = exif.getThumbnail();
    177                     }
    178                 } catch (Throwable t) {
    179                     Log.w(TAG, "fail to get exif thumb", t);
    180                 }
    181                 if (thumbData != null) {
    182                     Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
    183                             jc, thumbData, options, targetSize);
    184                     if (bitmap != null) return bitmap;
    185                 }
    186             }
    187 
    188             return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
    189         }
    190     }
    191 
    192     @Override
    193     public Job<BitmapRegionDecoder> requestLargeImage() {
    194         return new LocalLargeImageRequest(filePath);
    195     }
    196 
    197     public static class LocalLargeImageRequest
    198             implements Job<BitmapRegionDecoder> {
    199         String mLocalFilePath;
    200 
    201         public LocalLargeImageRequest(String localFilePath) {
    202             mLocalFilePath = localFilePath;
    203         }
    204 
    205         public BitmapRegionDecoder run(JobContext jc) {
    206             return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
    207         }
    208     }
    209 
    210     @Override
    211     public int getSupportedOperations() {
    212         int operation = SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_CROP
    213                 | SUPPORT_SETAS | SUPPORT_EDIT | SUPPORT_INFO;
    214         if (BitmapUtils.isSupportedByRegionDecoder(mimeType)) {
    215             operation |= SUPPORT_FULL_IMAGE;
    216         }
    217 
    218         if (BitmapUtils.isRotationSupported(mimeType)) {
    219             operation |= SUPPORT_ROTATE;
    220         }
    221 
    222         if (GalleryUtils.isValidLocation(latitude, longitude)) {
    223             operation |= SUPPORT_SHOW_ON_MAP;
    224         }
    225         return operation;
    226     }
    227 
    228     @Override
    229     public void delete() {
    230         GalleryUtils.assertNotInRenderThread();
    231         Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
    232         mApplication.getContentResolver().delete(baseUri, "_id=?",
    233                 new String[]{String.valueOf(id)});
    234         mApplication.getDataManager().broadcastLocalDeletion();
    235     }
    236 
    237     private static String getExifOrientation(int orientation) {
    238         switch (orientation) {
    239             case 0:
    240                 return String.valueOf(ExifInterface.ORIENTATION_NORMAL);
    241             case 90:
    242                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
    243             case 180:
    244                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
    245             case 270:
    246                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
    247             default:
    248                 throw new AssertionError("invalid: " + orientation);
    249         }
    250     }
    251 
    252     @Override
    253     public void rotate(int degrees) {
    254         GalleryUtils.assertNotInRenderThread();
    255         Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
    256         ContentValues values = new ContentValues();
    257         int rotation = (this.rotation + degrees) % 360;
    258         if (rotation < 0) rotation += 360;
    259 
    260         if (mimeType.equalsIgnoreCase("image/jpeg")) {
    261             try {
    262                 ExifInterface exif = new ExifInterface(filePath);
    263                 exif.setAttribute(ExifInterface.TAG_ORIENTATION,
    264                         getExifOrientation(rotation));
    265                 exif.saveAttributes();
    266             } catch (IOException e) {
    267                 Log.w(TAG, "cannot set exif data: " + filePath);
    268             }
    269 
    270             // We need to update the filesize as well
    271             fileSize = new File(filePath).length();
    272             values.put(Images.Media.SIZE, fileSize);
    273         }
    274 
    275         values.put(Images.Media.ORIENTATION, rotation);
    276         mApplication.getContentResolver().update(baseUri, values, "_id=?",
    277                 new String[]{String.valueOf(id)});
    278     }
    279 
    280     @Override
    281     public Uri getContentUri() {
    282         Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
    283         return baseUri.buildUpon().appendPath(String.valueOf(id)).build();
    284     }
    285 
    286     @Override
    287     public int getMediaType() {
    288         return MEDIA_TYPE_IMAGE;
    289     }
    290 
    291     @Override
    292     public MediaDetails getDetails() {
    293         MediaDetails details = super.getDetails();
    294         details.addDetail(MediaDetails.INDEX_ORIENTATION, Integer.valueOf(rotation));
    295         MediaDetails.extractExifInfo(details, filePath);
    296         return details;
    297     }
    298 
    299     @Override
    300     public int getRotation() {
    301         return rotation;
    302     }
    303 
    304     @Override
    305     public int getWidth() {
    306         return width;
    307     }
    308 
    309     @Override
    310     public int getHeight() {
    311         return height;
    312     }
    313 }
    314