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.camera; 18 19 import java.io.File; 20 import java.io.FileOutputStream; 21 22 import android.annotation.TargetApi; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.location.Location; 26 import android.net.Uri; 27 import android.os.Build; 28 import android.os.Environment; 29 import android.os.StatFs; 30 import android.provider.MediaStore.Images; 31 import android.provider.MediaStore.Images.ImageColumns; 32 import android.provider.MediaStore.MediaColumns; 33 import android.util.Log; 34 35 import com.android.camera.data.LocalData; 36 import com.android.camera.exif.ExifInterface; 37 import com.android.camera.util.ApiHelper; 38 39 public class Storage { 40 private static final String TAG = "CameraStorage"; 41 42 public static final String DCIM = 43 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString(); 44 45 public static final String DIRECTORY = DCIM + "/Camera"; 46 public static final String JPEG_POSTFIX = ".jpg"; 47 48 // Match the code in MediaProvider.computeBucketValues(). 49 public static final String BUCKET_ID = 50 String.valueOf(DIRECTORY.toLowerCase().hashCode()); 51 52 public static final long UNAVAILABLE = -1L; 53 public static final long PREPARING = -2L; 54 public static final long UNKNOWN_SIZE = -3L; 55 public static final long LOW_STORAGE_THRESHOLD_BYTES = 50000000; 56 57 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 58 private static void setImageSize(ContentValues values, int width, int height) { 59 // The two fields are available since ICS but got published in JB 60 if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) { 61 values.put(MediaColumns.WIDTH, width); 62 values.put(MediaColumns.HEIGHT, height); 63 } 64 } 65 66 public static void writeFile(String path, byte[] jpeg, ExifInterface exif) { 67 if (exif != null) { 68 try { 69 exif.writeExif(jpeg, path); 70 } catch (Exception e) { 71 Log.e(TAG, "Failed to write data", e); 72 } 73 } else { 74 writeFile(path, jpeg); 75 } 76 } 77 78 public static void writeFile(String path, byte[] data) { 79 FileOutputStream out = null; 80 try { 81 out = new FileOutputStream(path); 82 out.write(data); 83 } catch (Exception e) { 84 Log.e(TAG, "Failed to write data", e); 85 } finally { 86 try { 87 out.close(); 88 } catch (Exception e) { 89 Log.e(TAG, "Failed to close file after write", e); 90 } 91 } 92 } 93 94 // Save the image and add it to the MediaStore. 95 public static Uri addImage(ContentResolver resolver, String title, long date, 96 Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, 97 int height) { 98 99 return addImage(resolver, title, date, location, orientation, exif, jpeg, width, height, 100 LocalData.MIME_TYPE_JPEG); 101 } 102 103 // Save the image with a given mimeType and add it the MediaStore. 104 public static Uri addImage(ContentResolver resolver, String title, long date, 105 Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, 106 int height, String mimeType) { 107 108 String path = generateFilepath(title); 109 writeFile(path, jpeg, exif); 110 return addImage(resolver, title, date, location, orientation, 111 jpeg.length, path, width, height, mimeType); 112 } 113 114 // Get a ContentValues object for the given photo data 115 public static ContentValues getContentValuesForData(String title, 116 long date, Location location, int orientation, int jpegLength, 117 String path, int width, int height, String mimeType) { 118 119 ContentValues values = new ContentValues(11); 120 values.put(ImageColumns.TITLE, title); 121 values.put(ImageColumns.DISPLAY_NAME, title + JPEG_POSTFIX); 122 values.put(ImageColumns.DATE_TAKEN, date); 123 values.put(ImageColumns.MIME_TYPE, mimeType); 124 // Clockwise rotation in degrees. 0, 90, 180, or 270. 125 values.put(ImageColumns.ORIENTATION, orientation); 126 values.put(ImageColumns.DATA, path); 127 values.put(ImageColumns.SIZE, jpegLength); 128 129 setImageSize(values, width, height); 130 131 if (location != null) { 132 values.put(ImageColumns.LATITUDE, location.getLatitude()); 133 values.put(ImageColumns.LONGITUDE, location.getLongitude()); 134 } 135 return values; 136 } 137 138 // Add the image to media store. 139 public static Uri addImage(ContentResolver resolver, String title, 140 long date, Location location, int orientation, int jpegLength, 141 String path, int width, int height, String mimeType) { 142 // Insert into MediaStore. 143 ContentValues values = 144 getContentValuesForData(title, date, location, orientation, jpegLength, path, 145 width, height, mimeType); 146 147 return insertImage(resolver, values); 148 } 149 150 // Overwrites the file and updates the MediaStore, or inserts the image if 151 // one does not already exist. 152 public static void updateImage(Uri imageUri, ContentResolver resolver, String title, long date, 153 Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, 154 int height, String mimeType) { 155 String path = generateFilepath(title); 156 writeFile(path, jpeg, exif); 157 updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path, 158 width, height, mimeType); 159 } 160 161 // Updates the image values in MediaStore, or inserts the image if one does 162 // not already exist. 163 public static void updateImage(Uri imageUri, ContentResolver resolver, String title, 164 long date, Location location, int orientation, int jpegLength, 165 String path, int width, int height, String mimeType) { 166 167 ContentValues values = 168 getContentValuesForData(title, date, location, orientation, jpegLength, path, 169 width, height, mimeType); 170 171 // Update the MediaStore 172 int rowsModified = resolver.update(imageUri, values, null, null); 173 174 if (rowsModified == 0) { 175 // If no prior row existed, insert a new one. 176 Log.w(TAG, "updateImage called with no prior image at uri: " + imageUri); 177 insertImage(resolver, values); 178 } else if (rowsModified != 1) { 179 // This should never happen 180 throw new IllegalStateException("Bad number of rows (" + rowsModified 181 + ") updated for uri: " + imageUri); 182 } 183 } 184 185 public static void deleteImage(ContentResolver resolver, Uri uri) { 186 try { 187 resolver.delete(uri, null, null); 188 } catch (Throwable th) { 189 Log.e(TAG, "Failed to delete image: " + uri); 190 } 191 } 192 193 public static String generateFilepath(String title) { 194 return DIRECTORY + '/' + title + ".jpg"; 195 } 196 197 public static long getAvailableSpace() { 198 String state = Environment.getExternalStorageState(); 199 Log.d(TAG, "External storage state=" + state); 200 if (Environment.MEDIA_CHECKING.equals(state)) { 201 return PREPARING; 202 } 203 if (!Environment.MEDIA_MOUNTED.equals(state)) { 204 return UNAVAILABLE; 205 } 206 207 File dir = new File(DIRECTORY); 208 dir.mkdirs(); 209 if (!dir.isDirectory() || !dir.canWrite()) { 210 return UNAVAILABLE; 211 } 212 213 try { 214 StatFs stat = new StatFs(DIRECTORY); 215 return stat.getAvailableBlocks() * (long) stat.getBlockSize(); 216 } catch (Exception e) { 217 Log.i(TAG, "Fail to access external storage", e); 218 } 219 return UNKNOWN_SIZE; 220 } 221 222 /** 223 * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be 224 * imported. This is a temporary fix for bug#1655552. 225 */ 226 public static void ensureOSXCompatible() { 227 File nnnAAAAA = new File(DCIM, "100ANDRO"); 228 if (!(nnnAAAAA.exists() || nnnAAAAA.mkdirs())) { 229 Log.e(TAG, "Failed to create " + nnnAAAAA.getPath()); 230 } 231 } 232 233 private static Uri insertImage(ContentResolver resolver, ContentValues values) { 234 Uri uri = null; 235 try { 236 uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); 237 } catch (Throwable th) { 238 // This can happen when the external volume is already mounted, but 239 // MediaScanner has not notify MediaProvider to add that volume. 240 // The picture is still safe and MediaScanner will find it and 241 // insert it into MediaProvider. The only problem is that the user 242 // cannot click the thumbnail to review the picture. 243 Log.e(TAG, "Failed to write MediaStore" + th); 244 } 245 return uri; 246 } 247 } 248