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