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