blob: c3095a069e214136c30fce86155c3a23fd9992f7 [file] [log] [blame]
Owen Linf9a0a432011-08-17 22:07:43 +08001/*
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
17package com.android.gallery3d.data;
18
Owen Linf9a0a432011-08-17 22:07:43 +080019import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.database.Cursor;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.graphics.BitmapRegionDecoder;
25import android.media.ExifInterface;
26import android.net.Uri;
27import android.provider.MediaStore.Images;
28import android.provider.MediaStore.Images.ImageColumns;
Owen Lin113bfc72011-08-30 10:38:59 +080029import android.util.Log;
Owen Linf9a0a432011-08-17 22:07:43 +080030
Owen Lin4bb59122012-03-07 17:39:56 +080031import com.android.gallery3d.app.GalleryApp;
32import com.android.gallery3d.common.BitmapUtils;
33import com.android.gallery3d.util.GalleryUtils;
34import com.android.gallery3d.util.ThreadPool.Job;
35import com.android.gallery3d.util.ThreadPool.JobContext;
36import com.android.gallery3d.util.UpdateHelper;
37
Owen Linf9a0a432011-08-17 22:07:43 +080038import java.io.File;
39import java.io.IOException;
40
41// LocalImage represents an image in the local storage.
42public class LocalImage extends LocalMediaItem {
Owen Linf9a0a432011-08-17 22:07:43 +080043 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;
Chih-Chung Chang214993d2012-05-11 17:20:53 +080060 private static final int INDEX_SIZE = 11;
Chih-Chung Changcac63eb2011-09-20 20:41:33 +080061 private static final int INDEX_WIDTH = 12;
62 private static final int INDEX_HEIGHT = 13;
Owen Linf9a0a432011-08-17 22:07:43 +080063
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
Chih-Chung Changcac63eb2011-09-20 20:41:33 +080076 ImageColumns.SIZE, // 11
Chih-Chung Chang214993d2012-05-11 17:20:53 +080077 ImageColumns.WIDTH, // 12
78 ImageColumns.HEIGHT // 13
Owen Linf9a0a432011-08-17 22:07:43 +080079 };
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);
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800121 fileSize = cursor.getLong(INDEX_SIZE);
Chih-Chung Changcac63eb2011-09-20 20:41:33 +0800122 width = cursor.getInt(INDEX_WIDTH);
123 height = cursor.getInt(INDEX_HEIGHT);
Owen Linf9a0a432011-08-17 22:07:43 +0800124 }
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));
Chih-Chung Chang214993d2012-05-11 17:20:53 +0800143 fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
Chih-Chung Changcac63eb2011-09-20 20:41:33 +0800144 width = uh.update(width, cursor.getInt(INDEX_WIDTH));
145 height = uh.update(height, cursor.getInt(INDEX_HEIGHT));
Owen Linf9a0a432011-08-17 22:07:43 +0800146 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) {
Owen Lin4bb59122012-03-07 17:39:56 +0800159 super(application, path, type, MediaItem.getTargetSize(type));
Owen Linf9a0a432011-08-17 22:07:43 +0800160 mLocalFilePath = localFilePath;
161 }
162
163 @Override
Owen Lin4bb59122012-03-07 17:39:56 +0800164 public Bitmap onDecodeOriginal(JobContext jc, final int type) {
Owen Linf9a0a432011-08-17 22:07:43 +0800165 BitmapFactory.Options options = new BitmapFactory.Options();
166 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Owen Lin4bb59122012-03-07 17:39:56 +0800167 int targetSize = MediaItem.getTargetSize(type);
Owen Lin113bfc72011-08-30 10:38:59 +0800168
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) {
Owen Lin4bb59122012-03-07 17:39:56 +0800182 Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
183 jc, thumbData, options, targetSize);
Owen Lin113bfc72011-08-30 10:38:59 +0800184 if (bitmap != null) return bitmap;
185 }
186 }
Owen Linf9a0a432011-08-17 22:07:43 +0800187
Owen Lin4bb59122012-03-07 17:39:56 +0800188 return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
Owen Linf9a0a432011-08-17 22:07:43 +0800189 }
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) {
Owen Lin4bb59122012-03-07 17:39:56 +0800206 return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
Owen Linf9a0a432011-08-17 22:07:43 +0800207 }
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)});
Chih-Chung Changc7e89da2012-06-26 19:12:09 +0800234 mApplication.getDataManager().broadcastLocalDeletion();
Owen Linf9a0a432011-08-17 22:07:43 +0800235 }
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 }
Chih-Chung Changbc215412011-09-19 11:09:39 +0800303
304 @Override
305 public int getWidth() {
Chih-Chung Changcac63eb2011-09-20 20:41:33 +0800306 return width;
Chih-Chung Changbc215412011-09-19 11:09:39 +0800307 }
308
309 @Override
310 public int getHeight() {
Chih-Chung Changcac63eb2011-09-20 20:41:33 +0800311 return height;
Chih-Chung Changbc215412011-09-19 11:09:39 +0800312 }
Owen Linf9a0a432011-08-17 22:07:43 +0800313}