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