LocalImage.java revision 214993dc4abf87c386123af50e3c34184ba11cb6
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 17package com.android.gallery3d.data; 18 19import 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; 29import android.util.Log; 30 31import 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 38import java.io.File; 39import java.io.IOException; 40 41// LocalImage represents an image in the local storage. 42public 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 public int width; 85 public int height; 86 87 public LocalImage(Path path, GalleryApp application, Cursor cursor) { 88 super(path, nextVersionNumber()); 89 mApplication = application; 90 loadFromCursor(cursor); 91 } 92 93 public LocalImage(Path path, GalleryApp application, int id) { 94 super(path, nextVersionNumber()); 95 mApplication = application; 96 ContentResolver resolver = mApplication.getContentResolver(); 97 Uri uri = Images.Media.EXTERNAL_CONTENT_URI; 98 Cursor cursor = LocalAlbum.getItemCursor(resolver, uri, PROJECTION, id); 99 if (cursor == null) { 100 throw new RuntimeException("cannot get cursor for: " + path); 101 } 102 try { 103 if (cursor.moveToNext()) { 104 loadFromCursor(cursor); 105 } else { 106 throw new RuntimeException("cannot find data for: " + path); 107 } 108 } finally { 109 cursor.close(); 110 } 111 } 112 113 private void loadFromCursor(Cursor cursor) { 114 id = cursor.getInt(INDEX_ID); 115 caption = cursor.getString(INDEX_CAPTION); 116 mimeType = cursor.getString(INDEX_MIME_TYPE); 117 latitude = cursor.getDouble(INDEX_LATITUDE); 118 longitude = cursor.getDouble(INDEX_LONGITUDE); 119 dateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN); 120 filePath = cursor.getString(INDEX_DATA); 121 rotation = cursor.getInt(INDEX_ORIENTATION); 122 bucketId = cursor.getInt(INDEX_BUCKET_ID); 123 fileSize = cursor.getLong(INDEX_SIZE); 124 width = cursor.getInt(INDEX_WIDTH); 125 height = cursor.getInt(INDEX_HEIGHT); 126 } 127 128 @Override 129 protected boolean updateFromCursor(Cursor cursor) { 130 UpdateHelper uh = new UpdateHelper(); 131 id = uh.update(id, cursor.getInt(INDEX_ID)); 132 caption = uh.update(caption, cursor.getString(INDEX_CAPTION)); 133 mimeType = uh.update(mimeType, cursor.getString(INDEX_MIME_TYPE)); 134 latitude = uh.update(latitude, cursor.getDouble(INDEX_LATITUDE)); 135 longitude = uh.update(longitude, cursor.getDouble(INDEX_LONGITUDE)); 136 dateTakenInMs = uh.update( 137 dateTakenInMs, cursor.getLong(INDEX_DATE_TAKEN)); 138 dateAddedInSec = uh.update( 139 dateAddedInSec, cursor.getLong(INDEX_DATE_ADDED)); 140 dateModifiedInSec = uh.update( 141 dateModifiedInSec, cursor.getLong(INDEX_DATE_MODIFIED)); 142 filePath = uh.update(filePath, cursor.getString(INDEX_DATA)); 143 rotation = uh.update(rotation, cursor.getInt(INDEX_ORIENTATION)); 144 bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID)); 145 fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE)); 146 width = uh.update(width, cursor.getInt(INDEX_WIDTH)); 147 height = uh.update(height, cursor.getInt(INDEX_HEIGHT)); 148 return uh.isUpdated(); 149 } 150 151 @Override 152 public Job<Bitmap> requestImage(int type) { 153 return new LocalImageRequest(mApplication, mPath, type, filePath); 154 } 155 156 public static class LocalImageRequest extends ImageCacheRequest { 157 private String mLocalFilePath; 158 159 LocalImageRequest(GalleryApp application, Path path, int type, 160 String localFilePath) { 161 super(application, path, type, MediaItem.getTargetSize(type)); 162 mLocalFilePath = localFilePath; 163 } 164 165 @Override 166 public Bitmap onDecodeOriginal(JobContext jc, final int type) { 167 BitmapFactory.Options options = new BitmapFactory.Options(); 168 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 169 int targetSize = MediaItem.getTargetSize(type); 170 171 // try to decode from JPEG EXIF 172 if (type == MediaItem.TYPE_MICROTHUMBNAIL) { 173 ExifInterface exif = null; 174 byte [] thumbData = null; 175 try { 176 exif = new ExifInterface(mLocalFilePath); 177 if (exif != null) { 178 thumbData = exif.getThumbnail(); 179 } 180 } catch (Throwable t) { 181 Log.w(TAG, "fail to get exif thumb", t); 182 } 183 if (thumbData != null) { 184 Bitmap bitmap = DecodeUtils.decodeIfBigEnough( 185 jc, thumbData, options, targetSize); 186 if (bitmap != null) return bitmap; 187 } 188 } 189 190 return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type); 191 } 192 } 193 194 @Override 195 public Job<BitmapRegionDecoder> requestLargeImage() { 196 return new LocalLargeImageRequest(filePath); 197 } 198 199 public static class LocalLargeImageRequest 200 implements Job<BitmapRegionDecoder> { 201 String mLocalFilePath; 202 203 public LocalLargeImageRequest(String localFilePath) { 204 mLocalFilePath = localFilePath; 205 } 206 207 public BitmapRegionDecoder run(JobContext jc) { 208 return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false); 209 } 210 } 211 212 @Override 213 public int getSupportedOperations() { 214 int operation = SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_CROP 215 | SUPPORT_SETAS | SUPPORT_EDIT | SUPPORT_INFO; 216 if (BitmapUtils.isSupportedByRegionDecoder(mimeType)) { 217 operation |= SUPPORT_FULL_IMAGE; 218 } 219 220 if (BitmapUtils.isRotationSupported(mimeType)) { 221 operation |= SUPPORT_ROTATE; 222 } 223 224 if (GalleryUtils.isValidLocation(latitude, longitude)) { 225 operation |= SUPPORT_SHOW_ON_MAP; 226 } 227 return operation; 228 } 229 230 @Override 231 public void delete() { 232 GalleryUtils.assertNotInRenderThread(); 233 Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; 234 mApplication.getContentResolver().delete(baseUri, "_id=?", 235 new String[]{String.valueOf(id)}); 236 } 237 238 private static String getExifOrientation(int orientation) { 239 switch (orientation) { 240 case 0: 241 return String.valueOf(ExifInterface.ORIENTATION_NORMAL); 242 case 90: 243 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90); 244 case 180: 245 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180); 246 case 270: 247 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270); 248 default: 249 throw new AssertionError("invalid: " + orientation); 250 } 251 } 252 253 @Override 254 public void rotate(int degrees) { 255 GalleryUtils.assertNotInRenderThread(); 256 Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; 257 ContentValues values = new ContentValues(); 258 int rotation = (this.rotation + degrees) % 360; 259 if (rotation < 0) rotation += 360; 260 261 if (mimeType.equalsIgnoreCase("image/jpeg")) { 262 try { 263 ExifInterface exif = new ExifInterface(filePath); 264 exif.setAttribute(ExifInterface.TAG_ORIENTATION, 265 getExifOrientation(rotation)); 266 exif.saveAttributes(); 267 } catch (IOException e) { 268 Log.w(TAG, "cannot set exif data: " + filePath); 269 } 270 271 // We need to update the filesize as well 272 fileSize = new File(filePath).length(); 273 values.put(Images.Media.SIZE, fileSize); 274 } 275 276 values.put(Images.Media.ORIENTATION, rotation); 277 mApplication.getContentResolver().update(baseUri, values, "_id=?", 278 new String[]{String.valueOf(id)}); 279 } 280 281 @Override 282 public Uri getContentUri() { 283 Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; 284 return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); 285 } 286 287 @Override 288 public int getMediaType() { 289 return MEDIA_TYPE_IMAGE; 290 } 291 292 @Override 293 public MediaDetails getDetails() { 294 MediaDetails details = super.getDetails(); 295 details.addDetail(MediaDetails.INDEX_ORIENTATION, Integer.valueOf(rotation)); 296 MediaDetails.extractExifInfo(details, filePath); 297 return details; 298 } 299 300 @Override 301 public int getRotation() { 302 return rotation; 303 } 304 305 @Override 306 public int getWidth() { 307 return width; 308 } 309 310 @Override 311 public int getHeight() { 312 return height; 313 } 314} 315