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 android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.location.Location; 22 import android.net.Uri; 23 import android.os.Environment; 24 import android.os.StatFs; 25 import android.provider.MediaStore.Images; 26 import android.provider.MediaStore.Images.ImageColumns; 27 import android.util.Log; 28 29 import java.io.File; 30 import java.io.FileOutputStream; 31 32 public class Storage { 33 private static final String TAG = "CameraStorage"; 34 35 public static final String DCIM = 36 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString(); 37 38 public static final String DIRECTORY = DCIM + "/Camera"; 39 40 // Match the code in MediaProvider.computeBucketValues(). 41 public static final String BUCKET_ID = 42 String.valueOf(DIRECTORY.toLowerCase().hashCode()); 43 44 public static final long UNAVAILABLE = -1L; 45 public static final long PREPARING = -2L; 46 public static final long UNKNOWN_SIZE = -3L; 47 public static final long LOW_STORAGE_THRESHOLD= 50000000; 48 49 public static Uri addImage(ContentResolver resolver, String title, long date, 50 Location location, int orientation, byte[] jpeg, int width, int height) { 51 // Save the image. 52 String path = generateFilepath(title); 53 FileOutputStream out = null; 54 try { 55 out = new FileOutputStream(path); 56 out.write(jpeg); 57 } catch (Exception e) { 58 Log.e(TAG, "Failed to write image", e); 59 return null; 60 } finally { 61 try { 62 out.close(); 63 } catch (Exception e) { 64 } 65 } 66 67 // Insert into MediaStore. 68 ContentValues values = new ContentValues(9); 69 values.put(ImageColumns.TITLE, title); 70 values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); 71 values.put(ImageColumns.DATE_TAKEN, date); 72 values.put(ImageColumns.MIME_TYPE, "image/jpeg"); 73 // Clockwise rotation in degrees. 0, 90, 180, or 270. 74 values.put(ImageColumns.ORIENTATION, orientation); 75 values.put(ImageColumns.DATA, path); 76 values.put(ImageColumns.SIZE, jpeg.length); 77 values.put(ImageColumns.WIDTH, width); 78 values.put(ImageColumns.HEIGHT, height); 79 80 if (location != null) { 81 values.put(ImageColumns.LATITUDE, location.getLatitude()); 82 values.put(ImageColumns.LONGITUDE, location.getLongitude()); 83 } 84 85 Uri uri = null; 86 try { 87 uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); 88 } catch (Throwable th) { 89 // This can happen when the external volume is already mounted, but 90 // MediaScanner has not notify MediaProvider to add that volume. 91 // The picture is still safe and MediaScanner will find it and 92 // insert it into MediaProvider. The only problem is that the user 93 // cannot click the thumbnail to review the picture. 94 Log.e(TAG, "Failed to write MediaStore" + th); 95 } 96 return uri; 97 } 98 99 // newImage() and updateImage() together do the same work as 100 // addImage. newImage() is the first step, and it inserts the DATE_TAKEN and 101 // DATA fields into the database. 102 // 103 // We also insert hint values for the WIDTH and HEIGHT fields to give 104 // correct aspect ratio before the real values are updated in updateImage(). 105 public static Uri newImage(ContentResolver resolver, String title, 106 long date, int width, int height) { 107 String path = generateFilepath(title); 108 109 // Insert into MediaStore. 110 ContentValues values = new ContentValues(4); 111 values.put(ImageColumns.DATE_TAKEN, date); 112 values.put(ImageColumns.DATA, path); 113 values.put(ImageColumns.WIDTH, width); 114 values.put(ImageColumns.HEIGHT, height); 115 116 Uri uri = null; 117 try { 118 uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); 119 } catch (Throwable th) { 120 // This can happen when the external volume is already mounted, but 121 // MediaScanner has not notify MediaProvider to add that volume. 122 // The picture is still safe and MediaScanner will find it and 123 // insert it into MediaProvider. The only problem is that the user 124 // cannot click the thumbnail to review the picture. 125 Log.e(TAG, "Failed to new image" + th); 126 } 127 return uri; 128 } 129 130 // This is the second step. It completes the partial data added by 131 // newImage. All columns other than DATE_TAKEN and DATA are inserted 132 // here. This method also save the image data into the file. 133 // 134 // Returns true if the update is successful. 135 public static boolean updateImage(ContentResolver resolver, Uri uri, 136 String title, Location location, int orientation, byte[] jpeg, 137 int width, int height) { 138 // Save the image. 139 String path = generateFilepath(title); 140 String tmpPath = path + ".tmp"; 141 FileOutputStream out = null; 142 try { 143 // Write to a temporary file and rename it to the final name. This 144 // avoids other apps reading incomplete data. 145 out = new FileOutputStream(tmpPath); 146 out.write(jpeg); 147 out.close(); 148 new File(tmpPath).renameTo(new File(path)); 149 } catch (Exception e) { 150 Log.e(TAG, "Failed to write image", e); 151 return false; 152 } finally { 153 try { 154 out.close(); 155 } catch (Exception e) { 156 } 157 } 158 159 // Insert into MediaStore. 160 ContentValues values = new ContentValues(9); 161 values.put(ImageColumns.TITLE, title); 162 values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); 163 values.put(ImageColumns.MIME_TYPE, "image/jpeg"); 164 // Clockwise rotation in degrees. 0, 90, 180, or 270. 165 values.put(ImageColumns.ORIENTATION, orientation); 166 values.put(ImageColumns.SIZE, jpeg.length); 167 values.put(ImageColumns.WIDTH, width); 168 values.put(ImageColumns.HEIGHT, height); 169 170 if (location != null) { 171 values.put(ImageColumns.LATITUDE, location.getLatitude()); 172 values.put(ImageColumns.LONGITUDE, location.getLongitude()); 173 } 174 175 try { 176 resolver.update(uri, values, null, null); 177 } catch (Throwable th) { 178 Log.e(TAG, "Failed to update image" + th); 179 return false; 180 } 181 182 return true; 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