Home | History | Annotate | Download | only in camera
      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.IImage;
     21 import com.android.camera.gallery.IImageList;
     22 import com.android.camera.gallery.ImageList;
     23 import com.android.camera.gallery.ImageListUber;
     24 import com.android.camera.gallery.VideoList;
     25 
     26 import android.content.ContentResolver;
     27 import android.content.ContentValues;
     28 import android.database.Cursor;
     29 import android.graphics.Bitmap;
     30 import android.graphics.Bitmap.CompressFormat;
     31 import android.location.Location;
     32 import android.media.ExifInterface;
     33 import android.net.Uri;
     34 import android.os.Environment;
     35 import android.os.Parcel;
     36 import android.os.Parcelable;
     37 import android.provider.MediaStore;
     38 import android.provider.MediaStore.Images;
     39 import android.util.Log;
     40 import android.view.OrientationEventListener;
     41 
     42 import java.io.File;
     43 import java.io.FileNotFoundException;
     44 import java.io.FileOutputStream;
     45 import java.io.IOException;
     46 import java.io.OutputStream;
     47 import java.util.ArrayList;
     48 import java.util.Iterator;
     49 
     50 /**
     51  * {@code ImageManager} is used to retrieve and store images
     52  * in the media content provider.
     53  */
     54 public class ImageManager {
     55     private static final String TAG = "ImageManager";
     56 
     57     private static final Uri STORAGE_URI = Images.Media.EXTERNAL_CONTENT_URI;
     58     private static final Uri VIDEO_STORAGE_URI =
     59             Uri.parse("content://media/external/video/media");
     60 
     61     private ImageManager() {
     62     }
     63 
     64     /**
     65      * {@code ImageListParam} specifies all the parameters we need to create an
     66      * image list (we also need a ContentResolver).
     67      */
     68     public static class ImageListParam implements Parcelable {
     69         public DataLocation mLocation;
     70         public int mInclusion;
     71         public int mSort;
     72         public String mBucketId;
     73 
     74         // This is only used if we are creating an empty image list.
     75         public boolean mIsEmptyImageList;
     76 
     77         public ImageListParam() {
     78         }
     79 
     80         public void writeToParcel(Parcel out, int flags) {
     81             out.writeInt(mLocation.ordinal());
     82             out.writeInt(mInclusion);
     83             out.writeInt(mSort);
     84             out.writeString(mBucketId);
     85             out.writeInt(mIsEmptyImageList ? 1 : 0);
     86         }
     87 
     88         private ImageListParam(Parcel in) {
     89             mLocation = DataLocation.values()[in.readInt()];
     90             mInclusion = in.readInt();
     91             mSort = in.readInt();
     92             mBucketId = in.readString();
     93             mIsEmptyImageList = (in.readInt() != 0);
     94         }
     95 
     96         @Override
     97         public String toString() {
     98             return String.format("ImageListParam{loc=%s,inc=%d,sort=%d," +
     99                 "bucket=%s,empty=%b}", mLocation, mInclusion,
    100                 mSort, mBucketId, mIsEmptyImageList);
    101         }
    102 
    103         public static final Parcelable.Creator<ImageListParam> CREATOR
    104                 = new Parcelable.Creator<ImageListParam>() {
    105             public ImageListParam createFromParcel(Parcel in) {
    106                 return new ImageListParam(in);
    107             }
    108 
    109             public ImageListParam[] newArray(int size) {
    110                 return new ImageListParam[size];
    111             }
    112         };
    113 
    114         public int describeContents() {
    115             return 0;
    116         }
    117     }
    118 
    119     // Location
    120     public static enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL }
    121 
    122     // Inclusion
    123     public static final int INCLUDE_IMAGES = (1 << 0);
    124     public static final int INCLUDE_VIDEOS = (1 << 2);
    125 
    126     // Sort
    127     public static final int SORT_ASCENDING = 1;
    128     public static final int SORT_DESCENDING = 2;
    129 
    130     public static final String CAMERA_IMAGE_BUCKET_NAME =
    131             Environment.getExternalStorageDirectory().toString()
    132             + "/DCIM/Camera";
    133     public static final String CAMERA_IMAGE_BUCKET_ID =
    134             getBucketId(CAMERA_IMAGE_BUCKET_NAME);
    135 
    136     /**
    137      * Matches code in MediaProvider.computeBucketValues. Should be a common
    138      * function.
    139      */
    140     public static String getBucketId(String path) {
    141         return String.valueOf(path.toLowerCase().hashCode());
    142     }
    143 
    144     /**
    145      * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
    146      * imported. This is a temporary fix for bug#1655552.
    147      */
    148     public static void ensureOSXCompatibleFolder() {
    149         File nnnAAAAA = new File(
    150             Environment.getExternalStorageDirectory().toString()
    151             + "/DCIM/100ANDRO");
    152         if ((!nnnAAAAA.exists()) && (!nnnAAAAA.mkdir())) {
    153             Log.e(TAG, "create NNNAAAAA file: " + nnnAAAAA.getPath()
    154                     + " failed");
    155         }
    156     }
    157 
    158     public static int roundOrientation(int orientationInput) {
    159         int orientation = orientationInput;
    160 
    161         if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
    162             orientation = 0;
    163         }
    164 
    165         orientation = orientation % 360;
    166         int retVal;
    167         if (orientation < (0 * 90) + 45) {
    168             retVal = 0;
    169         } else if (orientation < (1 * 90) + 45) {
    170             retVal = 90;
    171         } else if (orientation < (2 * 90) + 45) {
    172             retVal = 180;
    173         } else if (orientation < (3 * 90) + 45) {
    174             retVal = 270;
    175         } else {
    176             retVal = 0;
    177         }
    178 
    179         return retVal;
    180     }
    181 
    182     //
    183     // Stores a bitmap or a jpeg byte array to a file (using the specified
    184     // directory and filename). Also add an entry to the media store for
    185     // this picture. The title, dateTaken, location are attributes for the
    186     // picture. The degree is a one element array which returns the orientation
    187     // of the picture.
    188     //
    189     public static Uri addImage(ContentResolver cr, String title, long dateTaken,
    190             Location location, String directory, String filename,
    191             Bitmap source, byte[] jpegData, int[] degree) {
    192         // We should store image data earlier than insert it to ContentProvider,
    193         // otherwise we may not be able to generate thumbnail in time.
    194         OutputStream outputStream = null;
    195         String filePath = directory + "/" + filename;
    196         try {
    197             File dir = new File(directory);
    198             if (!dir.exists()) dir.mkdirs();
    199             File file = new File(directory, filename);
    200             outputStream = new FileOutputStream(file);
    201             if (source != null) {
    202                 source.compress(CompressFormat.JPEG, 75, outputStream);
    203                 degree[0] = 0;
    204             } else {
    205                 outputStream.write(jpegData);
    206                 degree[0] = getExifOrientation(filePath);
    207             }
    208         } catch (FileNotFoundException ex) {
    209             Log.w(TAG, ex);
    210             return null;
    211         } catch (IOException ex) {
    212             Log.w(TAG, ex);
    213             return null;
    214         } finally {
    215             Util.closeSilently(outputStream);
    216         }
    217 
    218         // Read back the compressed file size.
    219         long size = new File(directory, filename).length();
    220 
    221         ContentValues values = new ContentValues(9);
    222         values.put(Images.Media.TITLE, title);
    223 
    224         // That filename is what will be handed to Gmail when a user shares a
    225         // photo. Gmail gets the name of the picture attachment from the
    226         // "DISPLAY_NAME" field.
    227         values.put(Images.Media.DISPLAY_NAME, filename);
    228         values.put(Images.Media.DATE_TAKEN, dateTaken);
    229         values.put(Images.Media.MIME_TYPE, "image/jpeg");
    230         values.put(Images.Media.ORIENTATION, degree[0]);
    231         values.put(Images.Media.DATA, filePath);
    232         values.put(Images.Media.SIZE, size);
    233 
    234         if (location != null) {
    235             values.put(Images.Media.LATITUDE, location.getLatitude());
    236             values.put(Images.Media.LONGITUDE, location.getLongitude());
    237         }
    238 
    239         return cr.insert(STORAGE_URI, values);
    240     }
    241 
    242     public static int getExifOrientation(String filepath) {
    243         int degree = 0;
    244         ExifInterface exif = null;
    245         try {
    246             exif = new ExifInterface(filepath);
    247         } catch (IOException ex) {
    248             Log.e(TAG, "cannot read exif", ex);
    249         }
    250         if (exif != null) {
    251             int orientation = exif.getAttributeInt(
    252                 ExifInterface.TAG_ORIENTATION, -1);
    253             if (orientation != -1) {
    254                 // We only recognize a subset of orientation tag values.
    255                 switch(orientation) {
    256                     case ExifInterface.ORIENTATION_ROTATE_90:
    257                         degree = 90;
    258                         break;
    259                     case ExifInterface.ORIENTATION_ROTATE_180:
    260                         degree = 180;
    261                         break;
    262                     case ExifInterface.ORIENTATION_ROTATE_270:
    263                         degree = 270;
    264                         break;
    265                 }
    266 
    267             }
    268         }
    269         return degree;
    270     }
    271 
    272     // This is the factory function to create an image list.
    273     public static IImageList makeImageList(ContentResolver cr,
    274             ImageListParam param) {
    275         DataLocation location = param.mLocation;
    276         int inclusion = param.mInclusion;
    277         int sort = param.mSort;
    278         String bucketId = param.mBucketId;
    279         boolean isEmptyImageList = param.mIsEmptyImageList;
    280 
    281         if (isEmptyImageList || cr == null) {
    282             return new EmptyImageList();
    283         }
    284 
    285         // false ==> don't require write access
    286         boolean haveSdCard = hasStorage(false);
    287 
    288         // use this code to merge videos and stills into the same list
    289         ArrayList<BaseImageList> l = new ArrayList<BaseImageList>();
    290 
    291         if (haveSdCard && location != DataLocation.INTERNAL) {
    292             if ((inclusion & INCLUDE_IMAGES) != 0) {
    293                 l.add(new ImageList(cr, STORAGE_URI, sort, bucketId));
    294             }
    295             if ((inclusion & INCLUDE_VIDEOS) != 0) {
    296                 l.add(new VideoList(cr, VIDEO_STORAGE_URI, sort, bucketId));
    297             }
    298         }
    299         if (location == DataLocation.INTERNAL || location == DataLocation.ALL) {
    300             if ((inclusion & INCLUDE_IMAGES) != 0) {
    301                 l.add(new ImageList(cr,
    302                         Images.Media.INTERNAL_CONTENT_URI, sort, bucketId));
    303             }
    304         }
    305 
    306         // Optimization: If some of the lists are empty, remove them.
    307         // If there is only one remaining list, return it directly.
    308         Iterator<BaseImageList> iter = l.iterator();
    309         while (iter.hasNext()) {
    310             BaseImageList sublist = iter.next();
    311             if (sublist.isEmpty()) {
    312                 sublist.close();
    313                 iter.remove();
    314             }
    315         }
    316 
    317         if (l.size() == 1) {
    318             BaseImageList list = l.get(0);
    319             return list;
    320         }
    321 
    322         ImageListUber uber = new ImageListUber(
    323                 l.toArray(new IImageList[l.size()]), sort);
    324         return uber;
    325     }
    326 
    327     private static class EmptyImageList implements IImageList {
    328         public void close() {
    329         }
    330 
    331         public int getCount() {
    332             return 0;
    333         }
    334 
    335         public IImage getImageAt(int i) {
    336             return null;
    337         }
    338     }
    339 
    340     public static ImageListParam getImageListParam(DataLocation location,
    341          int inclusion, int sort, String bucketId) {
    342          ImageListParam param = new ImageListParam();
    343          param.mLocation = location;
    344          param.mInclusion = inclusion;
    345          param.mSort = sort;
    346          param.mBucketId = bucketId;
    347          return param;
    348     }
    349 
    350     public static IImageList makeImageList(ContentResolver cr,
    351             DataLocation location, int inclusion, int sort, String bucketId) {
    352         ImageListParam param = getImageListParam(location, inclusion, sort,
    353                 bucketId);
    354         return makeImageList(cr, param);
    355     }
    356 
    357     private static boolean checkFsWritable() {
    358         // Create a temporary file to see whether a volume is really writeable.
    359         // It's important not to put it in the root directory which may have a
    360         // limit on the number of files.
    361         String directoryName =
    362                 Environment.getExternalStorageDirectory().toString() + "/DCIM";
    363         File directory = new File(directoryName);
    364         if (!directory.isDirectory()) {
    365             if (!directory.mkdirs()) {
    366                 return false;
    367             }
    368         }
    369         return directory.canWrite();
    370     }
    371 
    372     public static boolean hasStorage() {
    373         return hasStorage(true);
    374     }
    375 
    376     public static boolean hasStorage(boolean requireWriteAccess) {
    377         String state = Environment.getExternalStorageState();
    378 
    379         if (Environment.MEDIA_MOUNTED.equals(state)) {
    380             if (requireWriteAccess) {
    381                 boolean writable = checkFsWritable();
    382                 return writable;
    383             } else {
    384                 return true;
    385             }
    386         } else if (!requireWriteAccess
    387                 && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    388             return true;
    389         }
    390         return false;
    391     }
    392 
    393     private static Cursor query(ContentResolver resolver, Uri uri,
    394             String[] projection, String selection, String[] selectionArgs,
    395             String sortOrder) {
    396         try {
    397             if (resolver == null) {
    398                 return null;
    399             }
    400             return resolver.query(
    401                     uri, projection, selection, selectionArgs, sortOrder);
    402          } catch (UnsupportedOperationException ex) {
    403             return null;
    404         }
    405 
    406     }
    407 
    408     public static boolean isMediaScannerScanning(ContentResolver cr) {
    409         boolean result = false;
    410         Cursor cursor = query(cr, MediaStore.getMediaScannerUri(),
    411                 new String [] {MediaStore.MEDIA_SCANNER_VOLUME},
    412                 null, null, null);
    413         if (cursor != null) {
    414             if (cursor.getCount() == 1) {
    415                 cursor.moveToFirst();
    416                 result = "external".equals(cursor.getString(0));
    417             }
    418             cursor.close();
    419         }
    420 
    421         return result;
    422     }
    423 
    424     public static String getLastImageThumbPath() {
    425         return Environment.getExternalStorageDirectory().toString() +
    426                "/DCIM/.thumbnails/image_last_thumb";
    427     }
    428 
    429     public static String getLastVideoThumbPath() {
    430         return Environment.getExternalStorageDirectory().toString() +
    431                "/DCIM/.thumbnails/video_last_thumb";
    432     }
    433 
    434     public static String getTempJpegPath() {
    435         return Environment.getExternalStorageDirectory().toString() +
    436                "/DCIM/.tempjpeg";
    437     }
    438 }
    439