• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 com.android.camera.gallery.BaseImageList;
20 import com.android.camera.gallery.DrmImageList;
21 import com.android.camera.gallery.IImage;
22 import com.android.camera.gallery.IImageList;
23 import com.android.camera.gallery.ImageList;
24 import com.android.camera.gallery.ImageListUber;
25 import com.android.camera.gallery.SingleImageList;
26 import com.android.camera.gallery.VideoList;
27 import com.android.camera.gallery.VideoObject;
28 
29 import android.content.ContentResolver;
30 import android.content.ContentValues;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.graphics.Bitmap.CompressFormat;
34 import android.location.Location;
35 import android.media.ExifInterface;
36 import android.net.Uri;
37 import android.os.Environment;
38 import android.os.Parcel;
39 import android.os.Parcelable;
40 import android.provider.DrmStore;
41 import android.provider.MediaStore;
42 import android.provider.MediaStore.Images;
43 import android.util.Log;
44 
45 import java.io.File;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.OutputStream;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 
54 /**
55  * ImageManager is used to retrieve and store images
56  * in the media content provider.
57  */
58 public class ImageManager {
59     private static final String TAG = "ImageManager";
60 
61     private static final Uri STORAGE_URI = Images.Media.EXTERNAL_CONTENT_URI;
62     private static final Uri THUMB_URI
63             = Images.Thumbnails.EXTERNAL_CONTENT_URI;
64 
65     private static final Uri VIDEO_STORAGE_URI =
66             Uri.parse("content://media/external/video/media");
67 
68     // ImageListParam specifies all the parameters we need to create an image
69     // list (we also need a ContentResolver).
70     public static class ImageListParam implements Parcelable {
71         public DataLocation mLocation;
72         public int mInclusion;
73         public int mSort;
74         public String mBucketId;
75 
76         // This is only used if we are creating a single image list.
77         public Uri mSingleImageUri;
78 
79         // This is only used if we are creating an empty image list.
80         public boolean mIsEmptyImageList;
81 
ImageListParam()82         public ImageListParam() {}
83 
writeToParcel(Parcel out, int flags)84         public void writeToParcel(Parcel out, int flags) {
85             out.writeInt(mLocation.ordinal());
86             out.writeInt(mInclusion);
87             out.writeInt(mSort);
88             out.writeString(mBucketId);
89             out.writeParcelable(mSingleImageUri, flags);
90             out.writeInt(mIsEmptyImageList ? 1 : 0);
91         }
92 
ImageListParam(Parcel in)93         private ImageListParam(Parcel in) {
94             mLocation = DataLocation.values()[in.readInt()];
95             mInclusion = in.readInt();
96             mSort = in.readInt();
97             mBucketId = in.readString();
98             mSingleImageUri = in.readParcelable(null);
99             mIsEmptyImageList = (in.readInt() != 0);
100         }
101 
toString()102         public String toString() {
103             return String.format("ImageListParam{loc=%s,inc=%d,sort=%d," +
104                 "bucket=%s,empty=%b,single=%s}", mLocation, mInclusion,
105                 mSort, mBucketId, mIsEmptyImageList, mSingleImageUri);
106         }
107 
108         public static final Parcelable.Creator CREATOR
109                 = new Parcelable.Creator() {
110             public ImageListParam createFromParcel(Parcel in) {
111                 return new ImageListParam(in);
112             }
113 
114             public ImageListParam[] newArray(int size) {
115                 return new ImageListParam[size];
116             }
117         };
118 
describeContents()119         public int describeContents() {
120             return 0;
121         }
122     }
123 
124     // Location
125     public static enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL }
126 
127     // Inclusion
128     public static final int INCLUDE_IMAGES = (1 << 0);
129     public static final int INCLUDE_DRM_IMAGES = (1 << 1);
130     public static final int INCLUDE_VIDEOS = (1 << 2);
131 
132     // Sort
133     public static final int SORT_ASCENDING = 1;
134     public static final int SORT_DESCENDING = 2;
135 
136     public static final String CAMERA_IMAGE_BUCKET_NAME =
137             Environment.getExternalStorageDirectory().toString()
138             + "/DCIM/Camera";
139     public static final String CAMERA_IMAGE_BUCKET_ID =
140             getBucketId(CAMERA_IMAGE_BUCKET_NAME);
141 
142     /**
143      * Matches code in MediaProvider.computeBucketValues. Should be a common
144      * function.
145      */
getBucketId(String path)146     public static String getBucketId(String path) {
147         return String.valueOf(path.toLowerCase().hashCode());
148     }
149 
150     /**
151      * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
152      * imported. This is a temporary fix for bug#1655552.
153      */
ensureOSXCompatibleFolder()154     public static void ensureOSXCompatibleFolder() {
155         File nnnAAAAA = new File(
156             Environment.getExternalStorageDirectory().toString()
157             + "/DCIM/100ANDRO");
158         if ((!nnnAAAAA.exists()) && (!nnnAAAAA.mkdir())) {
159             Log.e(TAG, "create NNNAAAAA file: " + nnnAAAAA.getPath()
160                     + " failed");
161         }
162     }
163 
roundOrientation(int orientationInput)164     public static int roundOrientation(int orientationInput) {
165         int orientation = orientationInput;
166         if (orientation == -1) {
167             orientation = 0;
168         }
169 
170         orientation = orientation % 360;
171         int retVal;
172         if (orientation < (0 * 90) + 45) {
173             retVal = 0;
174         } else if (orientation < (1 * 90) + 45) {
175             retVal = 90;
176         } else if (orientation < (2 * 90) + 45) {
177             retVal = 180;
178         } else if (orientation < (3 * 90) + 45) {
179             retVal = 270;
180         } else {
181             retVal = 0;
182         }
183 
184         return retVal;
185     }
186 
187     /**
188      * @return true if the mimetype is an image mimetype.
189      */
isImageMimeType(String mimeType)190     public static boolean isImageMimeType(String mimeType) {
191         return mimeType.startsWith("image/");
192     }
193 
194     /**
195      * @return true if the mimetype is a video mimetype.
196      */
197     /* This is commented out because isVideo is not calling this now.
198     public static boolean isVideoMimeType(String mimeType) {
199         return mimeType.startsWith("video/");
200     }
201     */
202 
203     /**
204      * @return true if the image is an image.
205      */
isImage(IImage image)206     public static boolean isImage(IImage image) {
207         return isImageMimeType(image.getMimeType());
208     }
209 
210     /**
211      * @return true if the image is a video.
212      */
isVideo(IImage image)213     public static boolean isVideo(IImage image) {
214         // This is the right implementation, but we use instanceof for speed.
215         //return isVideoMimeType(image.getMimeType());
216         return (image instanceof VideoObject);
217     }
218 
setImageSize(ContentResolver cr, Uri uri, long size)219     public static void setImageSize(ContentResolver cr, Uri uri, long size) {
220         ContentValues values = new ContentValues();
221         values.put(Images.Media.SIZE, size);
222         cr.update(uri, values, null, null);
223     }
224 
225     //
226     // Stores a bitmap or a jpeg byte array to a file (using the specified
227     // directory and filename). Also add an entry to the media store for
228     // this picture. The title, dateTaken, location are attributes for the
229     // picture. The degree is a one element array which returns the orientation
230     // of the picture.
231     //
addImage(ContentResolver cr, String title, long dateTaken, Location location, String directory, String filename, Bitmap source, byte[] jpegData, int[] degree)232     public static Uri addImage(ContentResolver cr, String title, long dateTaken,
233             Location location, String directory, String filename,
234             Bitmap source, byte[] jpegData, int[] degree) {
235         // We should store image data earlier than insert it to ContentProvider, otherwise
236         // we may not be able to generate thumbnail in time.
237         OutputStream outputStream = null;
238         String filePath = directory + "/" + filename;
239         try {
240             File dir = new File(directory);
241             if (!dir.exists()) dir.mkdirs();
242             File file = new File(directory, filename);
243             outputStream = new FileOutputStream(file);
244             if (source != null) {
245                 source.compress(CompressFormat.JPEG, 75, outputStream);
246                 degree[0] = 0;
247             } else {
248                 outputStream.write(jpegData);
249                 degree[0] = getExifOrientation(filePath);
250             }
251         } catch (FileNotFoundException ex) {
252             Log.w(TAG, ex);
253             return null;
254         } catch (IOException ex) {
255             Log.w(TAG, ex);
256             return null;
257         } finally {
258             Util.closeSilently(outputStream);
259         }
260 
261         ContentValues values = new ContentValues(7);
262         values.put(Images.Media.TITLE, title);
263 
264         // That filename is what will be handed to Gmail when a user shares a
265         // photo. Gmail gets the name of the picture attachment from the
266         // "DISPLAY_NAME" field.
267         values.put(Images.Media.DISPLAY_NAME, filename);
268         values.put(Images.Media.DATE_TAKEN, dateTaken);
269         values.put(Images.Media.MIME_TYPE, "image/jpeg");
270         values.put(Images.Media.ORIENTATION, degree[0]);
271         values.put(Images.Media.DATA, filePath);
272 
273         if (location != null) {
274             values.put(Images.Media.LATITUDE, location.getLatitude());
275             values.put(Images.Media.LONGITUDE, location.getLongitude());
276         }
277 
278         return cr.insert(STORAGE_URI, values);
279     }
280 
getExifOrientation(String filepath)281     public static int getExifOrientation(String filepath) {
282         int degree = 0;
283         ExifInterface exif = null;
284         try {
285             exif = new ExifInterface(filepath);
286         } catch (IOException ex) {
287             Log.e(TAG, "cannot read exif", ex);
288         }
289         if (exif != null) {
290             int orientation = exif.getAttributeInt(
291                 ExifInterface.TAG_ORIENTATION, -1);
292             if (orientation != -1) {
293                 // We only recognize a subset of orientation tag values.
294                 switch(orientation) {
295                     case ExifInterface.ORIENTATION_ROTATE_90:
296                         degree = 90;
297                         break;
298                     case ExifInterface.ORIENTATION_ROTATE_180:
299                         degree = 180;
300                         break;
301                     case ExifInterface.ORIENTATION_ROTATE_270:
302                         degree = 270;
303                         break;
304                 }
305 
306             }
307         }
308         return degree;
309     }
310 
311     // This is the factory function to create an image list.
makeImageList(ContentResolver cr, ImageListParam param)312     public static IImageList makeImageList(ContentResolver cr,
313             ImageListParam param) {
314         DataLocation location = param.mLocation;
315         int inclusion = param.mInclusion;
316         int sort = param.mSort;
317         String bucketId = param.mBucketId;
318         Uri singleImageUri = param.mSingleImageUri;
319         boolean isEmptyImageList = param.mIsEmptyImageList;
320 
321         if (isEmptyImageList || cr == null) {
322             return new EmptyImageList();
323         }
324 
325         if (singleImageUri != null) {
326             return new SingleImageList(cr, singleImageUri);
327         }
328 
329         // false ==> don't require write access
330         boolean haveSdCard = hasStorage(false);
331 
332         // use this code to merge videos and stills into the same list
333         ArrayList<BaseImageList> l = new ArrayList<BaseImageList>();
334 
335         if (haveSdCard && location != DataLocation.INTERNAL) {
336             if ((inclusion & INCLUDE_IMAGES) != 0) {
337                 l.add(new ImageList(
338                         cr, STORAGE_URI, THUMB_URI, sort, bucketId));
339             }
340             if ((inclusion & INCLUDE_VIDEOS) != 0) {
341                 l.add(new VideoList(cr, VIDEO_STORAGE_URI, sort, bucketId));
342             }
343         }
344         if (location == DataLocation.INTERNAL || location == DataLocation.ALL) {
345             if ((inclusion & INCLUDE_IMAGES) != 0) {
346                 l.add(new ImageList(cr,
347                         Images.Media.INTERNAL_CONTENT_URI,
348                         Images.Thumbnails.INTERNAL_CONTENT_URI,
349                         sort, bucketId));
350             }
351             if ((inclusion & INCLUDE_DRM_IMAGES) != 0) {
352                 l.add(new DrmImageList(
353                         cr, DrmStore.Images.CONTENT_URI, sort, bucketId));
354             }
355         }
356 
357         // Optimization: If some of the lists are empty, remove them.
358         // If there is only one remaining list, return it directly.
359         Iterator<BaseImageList> iter = l.iterator();
360         while (iter.hasNext()) {
361             BaseImageList sublist = iter.next();
362             if (sublist.isEmpty()) {
363                 sublist.close();
364                 iter.remove();
365             }
366         }
367 
368         if (l.size() == 1) {
369             BaseImageList list = l.get(0);
370             return list;
371         }
372 
373         ImageListUber uber = new ImageListUber(
374                 l.toArray(new IImageList[l.size()]), sort);
375         return uber;
376     }
377 
378     // This is a convenience function to create an image list from a Uri.
makeImageList(ContentResolver cr, Uri uri, int sort)379     public static IImageList makeImageList(ContentResolver cr, Uri uri,
380             int sort) {
381         String uriString = (uri != null) ? uri.toString() : "";
382 
383         // TODO: we need to figure out whether we're viewing
384         // DRM images in a better way.  Is there a constant
385         // for content://drm somewhere??
386 
387         if (uriString.startsWith("content://drm")) {
388             return makeImageList(cr, DataLocation.ALL, INCLUDE_DRM_IMAGES, sort,
389                     null);
390         } else if (uriString.startsWith("content://media/external/video")) {
391             return makeImageList(cr, DataLocation.EXTERNAL, INCLUDE_VIDEOS,
392                     sort, null);
393         } else if (isSingleImageMode(uriString)) {
394             return makeSingleImageList(cr, uri);
395         } else {
396             String bucketId = uri.getQueryParameter("bucketId");
397             return makeImageList(cr, DataLocation.ALL, INCLUDE_IMAGES, sort,
398                     bucketId);
399         }
400     }
401 
isSingleImageMode(String uriString)402     static boolean isSingleImageMode(String uriString) {
403         return !uriString.startsWith(
404                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
405                 && !uriString.startsWith(
406                 MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
407     }
408 
409     private static class EmptyImageList implements IImageList {
close()410         public void close() {
411         }
412 
getBucketIds()413         public HashMap<String, String> getBucketIds() {
414             return new HashMap<String, String>();
415         }
416 
getCount()417         public int getCount() {
418             return 0;
419         }
420 
isEmpty()421         public boolean isEmpty() {
422             return true;
423         }
424 
getImageAt(int i)425         public IImage getImageAt(int i) {
426             return null;
427         }
428 
getImageForUri(Uri uri)429         public IImage getImageForUri(Uri uri) {
430             return null;
431         }
432 
removeImage(IImage image)433         public boolean removeImage(IImage image) {
434             return false;
435         }
436 
removeImageAt(int i)437         public boolean removeImageAt(int i) {
438             return false;
439         }
440 
getImageIndex(IImage image)441         public int getImageIndex(IImage image) {
442             throw new UnsupportedOperationException();
443         }
444     }
445 
getImageListParam(DataLocation location, int inclusion, int sort, String bucketId)446     public static ImageListParam getImageListParam(DataLocation location,
447          int inclusion, int sort, String bucketId) {
448          ImageListParam param = new ImageListParam();
449          param.mLocation = location;
450          param.mInclusion = inclusion;
451          param.mSort = sort;
452          param.mBucketId = bucketId;
453          return param;
454     }
455 
getSingleImageListParam(Uri uri)456     public static ImageListParam getSingleImageListParam(Uri uri) {
457         ImageListParam param = new ImageListParam();
458         param.mSingleImageUri = uri;
459         return param;
460     }
461 
getEmptyImageListParam()462     public static ImageListParam getEmptyImageListParam() {
463         ImageListParam param = new ImageListParam();
464         param.mIsEmptyImageList = true;
465         return param;
466     }
467 
makeImageList(ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId)468     public static IImageList makeImageList(ContentResolver cr,
469             DataLocation location, int inclusion, int sort, String bucketId) {
470         ImageListParam param = getImageListParam(location, inclusion, sort,
471                 bucketId);
472         return makeImageList(cr, param);
473     }
474 
makeEmptyImageList()475     public static IImageList makeEmptyImageList() {
476         return makeImageList(null, getEmptyImageListParam());
477     }
478 
makeSingleImageList(ContentResolver cr, Uri uri)479     public static IImageList  makeSingleImageList(ContentResolver cr, Uri uri) {
480         return makeImageList(cr, getSingleImageListParam(uri));
481     }
482 
checkFsWritable()483     private static boolean checkFsWritable() {
484         // Create a temporary file to see whether a volume is really writeable.
485         // It's important not to put it in the root directory which may have a
486         // limit on the number of files.
487         String directoryName =
488                 Environment.getExternalStorageDirectory().toString() + "/DCIM";
489         File directory = new File(directoryName);
490         if (!directory.isDirectory()) {
491             if (!directory.mkdirs()) {
492                 return false;
493             }
494         }
495         File f = new File(directoryName, ".probe");
496         try {
497             // Remove stale file if any
498             if (f.exists()) {
499                 f.delete();
500             }
501             if (!f.createNewFile()) {
502                 return false;
503             }
504             f.delete();
505             return true;
506         } catch (IOException ex) {
507             return false;
508         }
509     }
510 
hasStorage()511     public static boolean hasStorage() {
512         return hasStorage(true);
513     }
514 
hasStorage(boolean requireWriteAccess)515     public static boolean hasStorage(boolean requireWriteAccess) {
516         String state = Environment.getExternalStorageState();
517 
518         if (Environment.MEDIA_MOUNTED.equals(state)) {
519             if (requireWriteAccess) {
520                 boolean writable = checkFsWritable();
521                 return writable;
522             } else {
523                 return true;
524             }
525         } else if (!requireWriteAccess
526                 && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
527             return true;
528         }
529         return false;
530     }
531 
query(ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)532     private static Cursor query(ContentResolver resolver, Uri uri,
533             String[] projection, String selection, String[] selectionArgs,
534             String sortOrder) {
535         try {
536             if (resolver == null) {
537                 return null;
538             }
539             return resolver.query(
540                     uri, projection, selection, selectionArgs, sortOrder);
541          } catch (UnsupportedOperationException ex) {
542             return null;
543         }
544 
545     }
546 
isMediaScannerScanning(ContentResolver cr)547     public static boolean isMediaScannerScanning(ContentResolver cr) {
548         boolean result = false;
549         Cursor cursor = query(cr, MediaStore.getMediaScannerUri(),
550                 new String [] {MediaStore.MEDIA_SCANNER_VOLUME},
551                 null, null, null);
552         if (cursor != null) {
553             if (cursor.getCount() == 1) {
554                 cursor.moveToFirst();
555                 result = "external".equals(cursor.getString(0));
556             }
557             cursor.close();
558         }
559 
560         return result;
561     }
562 
getLastImageThumbPath()563     public static String getLastImageThumbPath() {
564         return Environment.getExternalStorageDirectory().toString() +
565                "/DCIM/.thumbnails/image_last_thumb";
566     }
567 
getLastVideoThumbPath()568     public static String getLastVideoThumbPath() {
569         return Environment.getExternalStorageDirectory().toString() +
570                "/DCIM/.thumbnails/video_last_thumb";
571     }
572 
getTempJpegPath()573     public static String getTempJpegPath() {
574         return Environment.getExternalStorageDirectory().toString() +
575                "/DCIM/.tempjpeg";
576     }
577 }
578