Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2013 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.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.graphics.BitmapFactory;
     22 import android.location.Location;
     23 import android.net.Uri;
     24 import android.os.AsyncTask;
     25 import android.provider.MediaStore.Video;
     26 
     27 import com.android.camera.app.MediaSaver;
     28 import com.android.camera.data.FilmstripItemData;
     29 import com.android.camera.debug.Log;
     30 import com.android.camera.exif.ExifInterface;
     31 
     32 import java.io.File;
     33 import java.io.IOException;
     34 
     35 /**
     36  * A class implementing {@link com.android.camera.app.MediaSaver}.
     37  */
     38 public class MediaSaverImpl implements MediaSaver {
     39     private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl");
     40     private static final String VIDEO_BASE_URI = "content://media/external/video/media";
     41 
     42     /** The memory limit for unsaved image is 30MB. */
     43     // TODO: Revert this back to 20 MB when CaptureSession API supports saving
     44     // bursts.
     45     private static final int SAVE_TASK_MEMORY_LIMIT = 30 * 1024 * 1024;
     46 
     47     private final ContentResolver mContentResolver;
     48 
     49     /** Memory used by the total queued save request, in bytes. */
     50     private long mMemoryUse;
     51 
     52     private QueueListener mQueueListener;
     53 
     54     /**
     55      * @param contentResolver The {@link android.content.ContentResolver} to be
     56      *                 updated.
     57      */
     58     public MediaSaverImpl(ContentResolver contentResolver) {
     59         mContentResolver = contentResolver;
     60         mMemoryUse = 0;
     61     }
     62 
     63     @Override
     64     public boolean isQueueFull() {
     65         return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
     66     }
     67 
     68     @Override
     69     public void addImage(final byte[] data, String title, long date, Location loc, int width,
     70             int height, int orientation, ExifInterface exif, OnMediaSavedListener l) {
     71         addImage(data, title, date, loc, width, height, orientation, exif, l,
     72                 FilmstripItemData.MIME_TYPE_JPEG);
     73     }
     74 
     75     @Override
     76     public void addImage(final byte[] data, String title, long date, Location loc, int width,
     77             int height, int orientation, ExifInterface exif, OnMediaSavedListener l,
     78             String mimeType) {
     79         if (isQueueFull()) {
     80             Log.e(TAG, "Cannot add image when the queue is full");
     81             return;
     82         }
     83         ImageSaveTask t = new ImageSaveTask(data, title, date,
     84                 (loc == null) ? null : new Location(loc),
     85                 width, height, orientation, mimeType, exif, mContentResolver, l);
     86 
     87         mMemoryUse += data.length;
     88         if (isQueueFull()) {
     89             onQueueFull();
     90         }
     91         t.execute();
     92     }
     93 
     94     @Override
     95     public void addImage(final byte[] data, String title, long date, Location loc, int orientation,
     96             ExifInterface exif, OnMediaSavedListener l) {
     97         // When dimensions are unknown, pass 0 as width and height,
     98         // and decode image for width and height later in a background thread
     99         addImage(data, title, date, loc, 0, 0, orientation, exif, l,
    100                 FilmstripItemData.MIME_TYPE_JPEG);
    101     }
    102     @Override
    103     public void addImage(final byte[] data, String title, Location loc, int width, int height,
    104             int orientation, ExifInterface exif, OnMediaSavedListener l) {
    105         addImage(data, title, System.currentTimeMillis(), loc, width, height, orientation, exif, l,
    106                 FilmstripItemData.MIME_TYPE_JPEG);
    107     }
    108 
    109     @Override
    110     public void addVideo(String path, ContentValues values, OnMediaSavedListener l) {
    111         // We don't set a queue limit for video saving because the file
    112         // is already in the storage. Only updating the database.
    113         new VideoSaveTask(path, values, l, mContentResolver).execute();
    114     }
    115 
    116     @Override
    117     public void setQueueListener(QueueListener l) {
    118         mQueueListener = l;
    119         if (l == null) {
    120             return;
    121         }
    122         l.onQueueStatus(isQueueFull());
    123     }
    124 
    125     private void onQueueFull() {
    126         if (mQueueListener != null) {
    127             mQueueListener.onQueueStatus(true);
    128         }
    129     }
    130 
    131     private void onQueueAvailable() {
    132         if (mQueueListener != null) {
    133             mQueueListener.onQueueStatus(false);
    134         }
    135     }
    136 
    137     private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
    138         private final byte[] data;
    139         private final String title;
    140         private final long date;
    141         private final Location loc;
    142         private int width, height;
    143         private final int orientation;
    144         private final String mimeType;
    145         private final ExifInterface exif;
    146         private final ContentResolver resolver;
    147         private final OnMediaSavedListener listener;
    148 
    149         public ImageSaveTask(byte[] data, String title, long date, Location loc,
    150                              int width, int height, int orientation, String mimeType,
    151                              ExifInterface exif, ContentResolver resolver,
    152                              OnMediaSavedListener listener) {
    153             this.data = data;
    154             this.title = title;
    155             this.date = date;
    156             this.loc = loc;
    157             this.width = width;
    158             this.height = height;
    159             this.orientation = orientation;
    160             this.mimeType = mimeType;
    161             this.exif = exif;
    162             this.resolver = resolver;
    163             this.listener = listener;
    164         }
    165 
    166         @Override
    167         protected void onPreExecute() {
    168             // do nothing.
    169         }
    170 
    171         @Override
    172         protected Uri doInBackground(Void... v) {
    173             if (width == 0 || height == 0) {
    174                 // Decode bounds
    175                 BitmapFactory.Options options = new BitmapFactory.Options();
    176                 options.inJustDecodeBounds = true;
    177                 BitmapFactory.decodeByteArray(data, 0, data.length, options);
    178                 width = options.outWidth;
    179                 height = options.outHeight;
    180             }
    181             try {
    182                 return Storage.addImage(
    183                         resolver, title, date, loc, orientation, exif, data, width, height,
    184                         mimeType);
    185             } catch (IOException e) {
    186                 Log.e(TAG, "Failed to write data", e);
    187                 return null;
    188             }
    189         }
    190 
    191         @Override
    192         protected void onPostExecute(Uri uri) {
    193             if (listener != null) {
    194                 listener.onMediaSaved(uri);
    195             }
    196             boolean previouslyFull = isQueueFull();
    197             mMemoryUse -= data.length;
    198             if (isQueueFull() != previouslyFull) {
    199                 onQueueAvailable();
    200             }
    201         }
    202     }
    203 
    204     private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
    205         private String path;
    206         private final ContentValues values;
    207         private final OnMediaSavedListener listener;
    208         private final ContentResolver resolver;
    209 
    210         public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,
    211                              ContentResolver r) {
    212             this.path = path;
    213             this.values = new ContentValues(values);
    214             this.listener = l;
    215             this.resolver = r;
    216         }
    217 
    218         @Override
    219         protected Uri doInBackground(Void... v) {
    220             Uri uri = null;
    221             try {
    222                 Uri videoTable = Uri.parse(VIDEO_BASE_URI);
    223                 uri = resolver.insert(videoTable, values);
    224 
    225                 // Rename the video file to the final name. This avoids other
    226                 // apps reading incomplete data.  We need to do it after we are
    227                 // certain that the previous insert to MediaProvider is completed.
    228                 String finalName = values.getAsString(Video.Media.DATA);
    229                 File finalFile = new File(finalName);
    230                 if (new File(path).renameTo(finalFile)) {
    231                     path = finalName;
    232                 }
    233                 resolver.update(uri, values, null, null);
    234             } catch (Exception e) {
    235                 // We failed to insert into the database. This can happen if
    236                 // the SD card is unmounted.
    237                 Log.e(TAG, "failed to add video to media store", e);
    238                 uri = null;
    239             } finally {
    240                 Log.v(TAG, "Current video URI: " + uri);
    241             }
    242             return uri;
    243         }
    244 
    245         @Override
    246         protected void onPostExecute(Uri uri) {
    247             if (listener != null) {
    248                 listener.onMediaSaved(uri);
    249             }
    250         }
    251     }
    252 }
    253