Home | History | Annotate | Download | only in camera
      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