• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LocalImage(Path path, GalleryApp application, Cursor cursor)85     public LocalImage(Path path, GalleryApp application, Cursor cursor) {
86         super(path, nextVersionNumber());
87         mApplication = application;
88         loadFromCursor(cursor);
89     }
90 
LocalImage(Path path, GalleryApp application, int id)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 
loadFromCursor(Cursor cursor)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
updateFromCursor(Cursor cursor)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
requestImage(int type)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 
LocalImageRequest(GalleryApp application, Path path, int type, String localFilePath)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
onDecodeOriginal(JobContext jc, final int type)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
requestLargeImage()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 
LocalLargeImageRequest(String localFilePath)201         public LocalLargeImageRequest(String localFilePath) {
202             mLocalFilePath = localFilePath;
203         }
204 
run(JobContext jc)205         public BitmapRegionDecoder run(JobContext jc) {
206             return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
207         }
208     }
209 
210     @Override
getSupportedOperations()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
delete()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 
getExifOrientation(int orientation)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
rotate(int degrees)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
getContentUri()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
getMediaType()287     public int getMediaType() {
288         return MEDIA_TYPE_IMAGE;
289     }
290 
291     @Override
getDetails()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
getRotation()300     public int getRotation() {
301         return rotation;
302     }
303 
304     @Override
getWidth()305     public int getWidth() {
306         return width;
307     }
308 
309     @Override
getHeight()310     public int getHeight() {
311         return height;
312     }
313 }
314