Storage.java revision 0aaed976b603c56006213d196e6b0703285dafd5
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.camera; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.database.Cursor; 23import android.graphics.Bitmap; 24import android.graphics.BitmapFactory; 25import android.graphics.Matrix; 26import android.location.Location; 27import android.net.Uri; 28import android.os.Environment; 29import android.os.StatFs; 30import android.provider.MediaStore.Images; 31import android.provider.MediaStore.Images.ImageColumns; 32import android.provider.MediaStore.Video; 33import android.provider.MediaStore.Video.VideoColumns; 34import android.util.Log; 35 36import java.io.File; 37import java.io.FileOutputStream; 38 39class Storage { 40 private static final String TAG = "CameraStorage"; 41 42 private static final String DCIM = 43 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString(); 44 45 public static final String DIRECTORY = DCIM + "/Camera"; 46 47 public static final String THUMBNAILS = DCIM + "/.thumbnails"; 48 49 // Match the code in MediaProvider.computeBucketValues(). 50 public static final String BUCKET_ID = 51 String.valueOf(DIRECTORY.toLowerCase().hashCode()); 52 53 public static final long UNAVAILABLE = -1L; 54 public static final long PREPARING = -2L; 55 public static final long UNKNOWN_SIZE = -3L; 56 57 public static class Thumbnail { 58 private Uri mBaseUri; 59 private long mId; 60 private Uri mUri; 61 private Bitmap mBitmap; 62 private int mOrientation; 63 64 private Thumbnail(Uri baseUri, long id, int orientation) { 65 mBaseUri = baseUri; 66 mId = id; 67 mOrientation = orientation; 68 } 69 70 private Thumbnail(Uri uri, Bitmap bitmap, int orientation) { 71 mUri = uri; 72 mBitmap = bitmap; 73 mOrientation = orientation; 74 } 75 76 public Uri getOriginalUri() { 77 if (mUri == null && mBaseUri != null) { 78 mUri = ContentUris.withAppendedId(mBaseUri, mId); 79 } 80 return mUri; 81 } 82 83 public Bitmap getBitmap(ContentResolver resolver) { 84 if (mBitmap == null) { 85 if (Images.Media.EXTERNAL_CONTENT_URI.equals(mBaseUri)) { 86 mBitmap = Images.Thumbnails.getThumbnail(resolver, mId, 87 Images.Thumbnails.MICRO_KIND, null); 88 } else if (Video.Media.EXTERNAL_CONTENT_URI.equals(mBaseUri)) { 89 mBitmap = Video.Thumbnails.getThumbnail(resolver, mId, 90 Video.Thumbnails.MICRO_KIND, null); 91 } 92 } 93 if (mBitmap != null && mOrientation != 0) { 94 // We only rotate the thumbnail once even if we get OOM. 95 Matrix m = new Matrix(); 96 m.setRotate(mOrientation, mBitmap.getWidth() * 0.5f, 97 mBitmap.getHeight() * 0.5f); 98 mOrientation = 0; 99 100 try { 101 Bitmap rotated = Bitmap.createBitmap(mBitmap, 0, 0, 102 mBitmap.getWidth(), mBitmap.getHeight(), m, true); 103 mBitmap.recycle(); 104 mBitmap = rotated; 105 } catch (Throwable t) { 106 Log.w(TAG, "Failed to rotate thumbnail", t); 107 } 108 } 109 return mBitmap; 110 } 111 } 112 113 public static Thumbnail getLastImageThumbnail(ContentResolver resolver) { 114 Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; 115 116 Uri query = baseUri.buildUpon().appendQueryParameter("limit", "1").build(); 117 String[] projection = new String[] {ImageColumns._ID, ImageColumns.ORIENTATION}; 118 String selection = ImageColumns.MIME_TYPE + "='image/jpeg' AND " + 119 ImageColumns.BUCKET_ID + '=' + BUCKET_ID; 120 String order = ImageColumns.DATE_TAKEN + " DESC," + ImageColumns._ID + " DESC"; 121 122 Cursor cursor = null; 123 try { 124 cursor = resolver.query(query, projection, selection, null, order); 125 if (cursor != null && cursor.moveToFirst()) { 126 return new Thumbnail(baseUri, cursor.getLong(0), cursor.getInt(1)); 127 } 128 } finally { 129 if (cursor != null) { 130 cursor.close(); 131 } 132 } 133 return null; 134 } 135 136 public static Thumbnail getLastVideoThumbnail(ContentResolver resolver) { 137 Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI; 138 139 Uri query = baseUri.buildUpon().appendQueryParameter("limit", "1").build(); 140 String[] projection = new String[] {VideoColumns._ID}; 141 String selection = VideoColumns.BUCKET_ID + '=' + BUCKET_ID; 142 String order = VideoColumns.DATE_TAKEN + " DESC," + VideoColumns._ID + " DESC"; 143 144 Cursor cursor = null; 145 try { 146 cursor = resolver.query(query, projection, selection, null, order); 147 if (cursor != null && cursor.moveToFirst()) { 148 return new Thumbnail(baseUri, cursor.getLong(0), 0); 149 } 150 } finally { 151 if (cursor != null) { 152 cursor.close(); 153 } 154 } 155 return null; 156 } 157 158 public static Thumbnail addImage(ContentResolver resolver, String title, 159 long date, Location location, byte[] jpeg) { 160 // Save the image. 161 String path = DIRECTORY + '/' + title + ".jpg"; 162 FileOutputStream out = null; 163 try { 164 out = new FileOutputStream(path); 165 out.write(jpeg); 166 } catch (Exception e) { 167 Log.e(TAG, "Failed to write image", e); 168 return null; 169 } finally { 170 try { 171 out.close(); 172 } catch (Exception e) { 173 } 174 } 175 176 // Get the orientation. 177 int orientation = Exif.getOrientation(jpeg); 178 179 // Insert into MediaStore. 180 ContentValues values = new ContentValues(9); 181 values.put(ImageColumns.TITLE, title); 182 values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); 183 values.put(ImageColumns.DATE_TAKEN, date); 184 values.put(ImageColumns.MIME_TYPE, "image/jpeg"); 185 values.put(ImageColumns.ORIENTATION, orientation); 186 values.put(ImageColumns.DATA, path); 187 values.put(ImageColumns.SIZE, jpeg.length); 188 189 if (location != null) { 190 values.put(ImageColumns.LATITUDE, location.getLatitude()); 191 values.put(ImageColumns.LONGITUDE, location.getLongitude()); 192 } 193 194 Uri uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); 195 if (uri == null) { 196 Log.e(TAG, "Failed to write MediaStore"); 197 return null; 198 } 199 200 // Create the thumbnail. 201 BitmapFactory.Options options = new BitmapFactory.Options(); 202 options.inSampleSize = 16; 203 Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options); 204 if (bitmap == null) { 205 Log.e(TAG, "Failed to create thumbnail"); 206 return null; 207 } 208 return new Thumbnail(uri, bitmap, orientation); 209 } 210 211 public static long getAvailableSpace() { 212 String state = Environment.getExternalStorageState(); 213 if (Environment.MEDIA_CHECKING.equals(state)) { 214 return PREPARING; 215 } 216 if (!Environment.MEDIA_MOUNTED.equals(state)) { 217 return UNAVAILABLE; 218 } 219 220 File dir = new File(DIRECTORY); 221 dir.mkdirs(); 222 if (!dir.isDirectory() || !dir.canWrite()) { 223 return UNAVAILABLE; 224 } 225 226 try { 227 StatFs stat = new StatFs(DIRECTORY); 228 return stat.getAvailableBlocks() * (long) stat.getBlockSize(); 229 } catch (Exception e) { 230 Log.i(TAG, "Fail to access external storage", e); 231 } 232 return UNKNOWN_SIZE; 233 } 234 235 /** 236 * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be 237 * imported. This is a temporary fix for bug#1655552. 238 */ 239 public static void ensureOSXCompatible() { 240 File nnnAAAAA = new File(DIRECTORY, "100ANDRO"); 241 if (!(nnnAAAAA.exists() || nnnAAAAA.mkdirs())) { 242 Log.e(TAG, "Failed to create " + nnnAAAAA.getPath()); 243 } 244 } 245} 246