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