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