• 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.BaseCancelable;
20 import com.android.camera.gallery.BaseImageList;
21 import com.android.camera.gallery.Cancelable;
22 import com.android.camera.gallery.DrmImageList;
23 import com.android.camera.gallery.IImage;
24 import com.android.camera.gallery.IImageList;
25 import com.android.camera.gallery.Image;
26 import com.android.camera.gallery.ImageList;
27 import com.android.camera.gallery.ImageListUber;
28 import com.android.camera.gallery.SingleImageList;
29 import com.android.camera.gallery.VideoList;
30 import com.android.camera.gallery.VideoObject;
31 
32 import android.content.ContentResolver;
33 import android.content.ContentUris;
34 import android.content.ContentValues;
35 import android.database.Cursor;
36 import android.graphics.Bitmap;
37 import android.location.Location;
38 import android.net.Uri;
39 import android.os.Environment;
40 import android.os.Parcel;
41 import android.provider.DrmStore;
42 import android.provider.MediaStore;
43 import android.provider.MediaStore.Images;
44 import android.provider.MediaStore.Images.ImageColumns;
45 import android.util.Log;
46 
47 import java.io.File;
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.Iterator;
52 import java.util.concurrent.ExecutionException;
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     /**
69      * Enumerate type for the location of the images in gallery.
70      */
71     public static enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL }
72 
73     public static final Bitmap DEFAULT_THUMBNAIL =
74             Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565);
75     public static final Bitmap NO_IMAGE_BITMAP =
76             Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
77 
78     public static final int SORT_ASCENDING = 1;
79     public static final int SORT_DESCENDING = 2;
80 
81     public static final int INCLUDE_IMAGES = (1 << 0);
82     public static final int INCLUDE_DRM_IMAGES = (1 << 1);
83     public static final int INCLUDE_VIDEOS = (1 << 2);
84 
85     public static final String CAMERA_IMAGE_BUCKET_NAME =
86             Environment.getExternalStorageDirectory().toString()
87             + "/DCIM/Camera";
88     public static final String CAMERA_IMAGE_BUCKET_ID =
89             getBucketId(CAMERA_IMAGE_BUCKET_NAME);
90 
91     /**
92      * Matches code in MediaProvider.computeBucketValues. Should be a common
93      * function.
94      */
getBucketId(String path)95     public static String getBucketId(String path) {
96         return String.valueOf(path.toLowerCase().hashCode());
97     }
98 
99     /**
100      * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
101      * imported. This is a temporary fix for bug#1655552.
102      */
ensureOSXCompatibleFolder()103     public static void ensureOSXCompatibleFolder() {
104         File nnnAAAAA = new File(
105             Environment.getExternalStorageDirectory().toString()
106             + "/DCIM/100ANDRO");
107         if ((!nnnAAAAA.exists()) && (!nnnAAAAA.mkdir())) {
108             Log.e(TAG, "create NNNAAAAA file: " + nnnAAAAA.getPath()
109                     + " failed");
110         }
111     }
112 
roundOrientation(int orientationInput)113     public static int roundOrientation(int orientationInput) {
114         int orientation = orientationInput;
115         if (orientation == -1) {
116             orientation = 0;
117         }
118 
119         orientation = orientation % 360;
120         int retVal;
121         if (orientation < (0 * 90) + 45) {
122             retVal = 0;
123         } else if (orientation < (1 * 90) + 45) {
124             retVal = 90;
125         } else if (orientation < (2 * 90) + 45) {
126             retVal = 180;
127         } else if (orientation < (3 * 90) + 45) {
128             retVal = 270;
129         } else {
130             retVal = 0;
131         }
132 
133         return retVal;
134     }
135 
136     /**
137      * @return true if the mimetype is an image mimetype.
138      */
isImageMimeType(String mimeType)139     public static boolean isImageMimeType(String mimeType) {
140         return mimeType.startsWith("image/");
141     }
142 
143     /**
144      * @return true if the mimetype is a video mimetype.
145      */
isVideoMimeType(String mimeType)146     public static boolean isVideoMimeType(String mimeType) {
147         return mimeType.startsWith("video/");
148     }
149 
150     /**
151      * @return true if the image is an image.
152      */
isImage(IImage image)153     public static boolean isImage(IImage image) {
154         return isImageMimeType(image.getMimeType());
155     }
156 
157     /**
158      * @return true if the image is a video.
159      */
isVideo(IImage image)160     public static boolean isVideo(IImage image) {
161         // This is the right implementation, but we use instanceof for speed.
162         //return isVideoMimeType(image.getMimeType());
163         return (image instanceof VideoObject);
164     }
165 
setImageSize(ContentResolver cr, Uri uri, long size)166     public static void setImageSize(ContentResolver cr, Uri uri, long size) {
167         ContentValues values = new ContentValues();
168         values.put(Images.Media.SIZE, size);
169         cr.update(uri, values, null, null);
170     }
171 
addImage(ContentResolver cr, String title, long dateTaken, Location location, int orientation, String directory, String filename)172     public static Uri addImage(ContentResolver cr, String title,
173             long dateTaken, Location location,
174             int orientation, String directory, String filename) {
175 
176         ContentValues values = new ContentValues(7);
177         values.put(Images.Media.TITLE, title);
178 
179         // That filename is what will be handed to Gmail when a user shares a
180         // photo. Gmail gets the name of the picture attachment from the
181         // "DISPLAY_NAME" field.
182         values.put(Images.Media.DISPLAY_NAME, filename);
183         values.put(Images.Media.DATE_TAKEN, dateTaken);
184         values.put(Images.Media.MIME_TYPE, "image/jpeg");
185         values.put(Images.Media.ORIENTATION, orientation);
186 
187         if (location != null) {
188             values.put(Images.Media.LATITUDE, location.getLatitude());
189             values.put(Images.Media.LONGITUDE, location.getLongitude());
190         }
191 
192         if (directory != null && filename != null) {
193             String value = directory + "/" + filename;
194             values.put(Images.Media.DATA, value);
195         }
196 
197         return cr.insert(STORAGE_URI, values);
198     }
199 
200     private static class AddImageCancelable extends BaseCancelable<Void> {
201         private final Uri mUri;
202         private final ContentResolver mCr;
203         private final int mOrientation;
204         private final Bitmap mSource;
205         private final byte [] mJpegData;
206 
AddImageCancelable(Uri uri, ContentResolver cr, int orientation, Bitmap source, byte[] jpegData)207         public AddImageCancelable(Uri uri, ContentResolver cr,
208                 int orientation, Bitmap source, byte[] jpegData) {
209             if (source == null && jpegData == null || uri == null) {
210                 throw new IllegalArgumentException("source cannot be null");
211             }
212             mUri = uri;
213             mCr = cr;
214             mOrientation = orientation;
215             mSource = source;
216             mJpegData = jpegData;
217         }
218 
219         @Override
execute()220         protected Void execute() throws InterruptedException,
221                 ExecutionException {
222             boolean complete = false;
223             try {
224                 long id = ContentUris.parseId(mUri);
225                 BaseImageList il = new ImageList(
226                         STORAGE_URI, THUMB_URI, SORT_ASCENDING, null);
227                 il.open(mCr);
228 
229                 // TODO: Redesign the process of adding new images. We should
230                 //     create an <code>IImage</code> in "ImageManager.addImage"
231                 //     and pass the image object to here.
232                 Image image = new Image(il, mCr, id, 0, il.contentUri(id), null,
233                         0, null, 0, null, null, 0);
234                 String[] projection = new String[] {
235                         ImageColumns._ID,
236                         ImageColumns.MINI_THUMB_MAGIC, ImageColumns.DATA};
237                 Cursor c = mCr.query(mUri, projection, null, null, null);
238                 String filepath;
239                 try {
240                     c.moveToPosition(0);
241                     filepath = c.getString(2);
242                 } finally {
243                     c.close();
244                 }
245                 runSubTask(image.saveImageContents(
246                         mSource, mJpegData, mOrientation, true, filepath));
247 
248                 ContentValues values = new ContentValues();
249                 values.put(ImageColumns.MINI_THUMB_MAGIC, 0);
250                 mCr.update(mUri, values, null, null);
251                 complete = true;
252                 return null;
253             } finally {
254                 if (!complete) {
255                     try {
256                         mCr.delete(mUri, null, null);
257                     } catch (Throwable t) {
258                         // ignore it while clean up.
259                     }
260                 }
261             }
262         }
263     }
264 
storeImage( Uri uri, ContentResolver cr, int orientation, Bitmap source, byte [] jpegData)265     public static Cancelable<Void> storeImage(
266             Uri uri, ContentResolver cr, int orientation,
267             Bitmap source, byte [] jpegData) {
268         return new AddImageCancelable(
269                 uri, cr, orientation, source, jpegData);
270     }
271 
makeImageList(Uri uri, ContentResolver cr, int sort)272     public static IImageList makeImageList(Uri uri, ContentResolver cr,
273             int sort) {
274         String uriString = (uri != null) ? uri.toString() : "";
275 
276         // TODO: we need to figure out whether we're viewing
277         // DRM images in a better way.  Is there a constant
278         // for content://drm somewhere??
279         IImageList imageList;
280 
281         if (uriString.startsWith("content://drm")) {
282             imageList = ImageManager.allImages(
283                     cr, ImageManager.DataLocation.ALL,
284                     ImageManager.INCLUDE_DRM_IMAGES, sort);
285         } else if (uriString.startsWith("content://media/external/video")) {
286             imageList = ImageManager.allImages(
287                 cr, ImageManager.DataLocation.EXTERNAL,
288                 ImageManager.INCLUDE_VIDEOS, sort);
289         } else if (isSingleImageMode(uriString)) {
290             imageList = new SingleImageList(uri);
291             ((SingleImageList) imageList).open(cr);
292         } else {
293             String bucketId = uri.getQueryParameter("bucketId");
294             imageList = ImageManager.allImages(
295                 cr, ImageManager.DataLocation.ALL,
296                 ImageManager.INCLUDE_IMAGES, sort, bucketId);
297         }
298         return imageList;
299     }
300 
isSingleImageMode(String uriString)301     static boolean isSingleImageMode(String uriString) {
302         return !uriString.startsWith(
303                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
304                 && !uriString.startsWith(
305                 MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
306     }
307 
308     private static class EmptyImageList implements IImageList {
309         public static final Creator<EmptyImageList> CREATOR =
310                 new Creator<EmptyImageList>() {
311             public EmptyImageList createFromParcel(Parcel in) {
312                 return new EmptyImageList();
313             }
314 
315             public EmptyImageList[] newArray(int size) {
316                 return new EmptyImageList[size];
317             }
318         };
319 
open(ContentResolver resolver)320         public void open(ContentResolver resolver) {
321         }
322 
close()323         public void close() {
324         }
325 
checkThumbnail(int index)326         public void checkThumbnail(int index) {
327         }
328 
deactivate()329         public void deactivate() {
330         }
331 
getBucketIds()332         public HashMap<String, String> getBucketIds() {
333             return new HashMap<String, String>();
334         }
335 
getCount()336         public int getCount() {
337             return 0;
338         }
339 
isEmpty()340         public boolean isEmpty() {
341             return true;
342         }
343 
getImageAt(int i)344         public IImage getImageAt(int i) {
345             return null;
346         }
347 
getImageForUri(Uri uri)348         public IImage getImageForUri(Uri uri) {
349             return null;
350         }
351 
removeImage(IImage image)352         public boolean removeImage(IImage image) {
353             return false;
354         }
355 
removeImageAt(int i)356         public boolean removeImageAt(int i) {
357             return false;
358         }
359 
getImageIndex(IImage image)360         public int getImageIndex(IImage image) {
361             throw new UnsupportedOperationException();
362         }
363 
describeContents()364         public int describeContents() {
365             return 0;
366         }
367 
writeToParcel(Parcel dest, int flags)368         public void writeToParcel(Parcel dest, int flags) {
369         }
370     }
371 
emptyImageList()372     public static IImageList emptyImageList() {
373         return new EmptyImageList();
374     }
375 
allImages(ContentResolver cr, DataLocation location, int inclusion, int sort)376     public static IImageList allImages(ContentResolver cr,
377             DataLocation location, int inclusion, int sort) {
378         return allImages(cr, location, inclusion, sort, null);
379     }
380 
allImages(ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId)381     public static IImageList allImages(ContentResolver cr,
382             DataLocation location, int inclusion, int sort, String bucketId) {
383         if (cr == null) {
384             return null;
385         }
386 
387         // false ==> don't require write access
388         boolean haveSdCard = hasStorage(false);
389 
390         // use this code to merge videos and stills into the same list
391         ArrayList<BaseImageList> l = new ArrayList<BaseImageList>();
392 
393         if (haveSdCard && location != DataLocation.INTERNAL) {
394             if ((inclusion & INCLUDE_IMAGES) != 0) {
395                 l.add(new ImageList(
396                         STORAGE_URI, THUMB_URI, sort, bucketId));
397             }
398             if ((inclusion & INCLUDE_VIDEOS) != 0) {
399                 l.add(new VideoList(VIDEO_STORAGE_URI, sort, bucketId));
400             }
401         }
402         if (location == DataLocation.INTERNAL || location == DataLocation.ALL) {
403             if ((inclusion & INCLUDE_IMAGES) != 0) {
404                 l.add(new ImageList(
405                         Images.Media.INTERNAL_CONTENT_URI,
406                         Images.Thumbnails.INTERNAL_CONTENT_URI,
407                         sort, bucketId));
408             }
409             if ((inclusion & INCLUDE_DRM_IMAGES) != 0) {
410                 l.add(new DrmImageList(
411                         DrmStore.Images.CONTENT_URI, sort, bucketId));
412             }
413         }
414 
415         // Optimization: If some of the lists are empty, remove them.
416         // If there is only one remaining list, return it directly.
417         Iterator<BaseImageList> iter = l.iterator();
418         while (iter.hasNext()) {
419             BaseImageList sublist = iter.next();
420             sublist.open(cr);
421             if (sublist.isEmpty()) iter.remove();
422             sublist.close();
423         }
424 
425         if (l.size() == 1) {
426             BaseImageList list = l.get(0);
427             list.open(cr);
428             return list;
429         }
430 
431         ImageListUber uber = new ImageListUber(
432                 l.toArray(new IImageList[l.size()]), sort);
433         uber.open(cr);
434         return uber;
435     }
436 
checkFsWritable()437     private static boolean checkFsWritable() {
438         // Create a temporary file to see whether a volume is really writeable.
439         // It's important not to put it in the root directory which may have a
440         // limit on the number of files.
441         String directoryName =
442                 Environment.getExternalStorageDirectory().toString() + "/DCIM";
443         File directory = new File(directoryName);
444         if (!directory.isDirectory()) {
445             if (!directory.mkdirs()) {
446                 return false;
447             }
448         }
449         File f = new File(directoryName, ".probe");
450         try {
451             // Remove stale file if any
452             if (f.exists()) {
453                 f.delete();
454             }
455             if (!f.createNewFile()) {
456                 return false;
457             }
458             f.delete();
459             return true;
460         } catch (IOException ex) {
461             return false;
462         }
463     }
464 
hasStorage()465     public static boolean hasStorage() {
466         return hasStorage(true);
467     }
468 
hasStorage(boolean requireWriteAccess)469     public static boolean hasStorage(boolean requireWriteAccess) {
470         String state = Environment.getExternalStorageState();
471 
472         if (Environment.MEDIA_MOUNTED.equals(state)) {
473             if (requireWriteAccess) {
474                 boolean writable = checkFsWritable();
475                 return writable;
476             } else {
477                 return true;
478             }
479         } else if (!requireWriteAccess
480                 && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
481             return true;
482         }
483         return false;
484     }
485 
query(ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)486     private static Cursor query(ContentResolver resolver, Uri uri,
487             String[] projection, String selection, String[] selectionArgs,
488             String sortOrder) {
489         try {
490             if (resolver == null) {
491                 return null;
492             }
493             return resolver.query(
494                     uri, projection, selection, selectionArgs, sortOrder);
495          } catch (UnsupportedOperationException ex) {
496             return null;
497         }
498 
499     }
500 
isMediaScannerScanning(ContentResolver cr)501     public static boolean isMediaScannerScanning(ContentResolver cr) {
502         boolean result = false;
503         Cursor cursor = query(cr, MediaStore.getMediaScannerUri(),
504                 new String [] {MediaStore.MEDIA_SCANNER_VOLUME},
505                 null, null, null);
506         if (cursor != null) {
507             if (cursor.getCount() == 1) {
508                 cursor.moveToFirst();
509                 result = "external".equals(cursor.getString(0));
510             }
511             cursor.close();
512         }
513 
514         return result;
515     }
516 
getLastImageThumbPath()517     public static String getLastImageThumbPath() {
518         return Environment.getExternalStorageDirectory().toString() +
519                "/DCIM/.thumbnails/image_last_thumb";
520     }
521 
getLastVideoThumbPath()522     public static String getLastVideoThumbPath() {
523         return Environment.getExternalStorageDirectory().toString() +
524                "/DCIM/.thumbnails/video_last_thumb";
525     }
526 }
527