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