CaptureSessionImpl.java revision 995083b53be47e1e27133e403da0b63a03d07ea7
1/*
2 * Copyright (C) 2015 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.session;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.location.Location;
22import android.net.Uri;
23import android.os.AsyncTask;
24
25import com.android.camera.app.MediaSaver;
26import com.android.camera.data.FilmstripItemData;
27import com.android.camera.debug.Log;
28import com.android.camera.exif.ExifInterface;
29import com.android.camera.util.FileUtil;
30import com.android.camera.util.Size;
31import com.google.common.base.Optional;
32
33import java.io.File;
34import java.io.IOException;
35import java.util.HashSet;
36
37/**
38 * The default implementation of the CaptureSession interface. This is the
39 * implementation we use for normal Camera use.
40 */
41public class CaptureSessionImpl implements CaptureSession {
42    private static final Log.Tag TAG = new Log.Tag("CaptureSessionImpl");
43
44    /** The capture session manager responsible for this session. */
45    private final CaptureSessionManager mSessionManager;
46    /** Used to inform about session status updates. */
47    private final SessionNotifier mSessionNotifier;
48    /** Used for adding/removing/updating placeholders for in-progress sessions. */
49    private final PlaceholderManager mPlaceholderManager;
50    /** Used to store images on disk and to add them to the media store. */
51    private final MediaSaver mMediaSaver;
52    /** The title of the item being processed. */
53    private final String mTitle;
54    /** These listeners get informed about progress updates. */
55    private final HashSet<ProgressListener> mProgressListeners = new HashSet<>();
56    private final long mSessionStartMillis;
57    /**
58     * The file that can be used to write the final JPEG output temporarily,
59     * before it is copied to the final location.
60     */
61    private final TemporarySessionFile mTempOutputFile;
62    /** Saver that is used to store a stack of images. */
63    private final StackSaver mStackSaver;
64    /** A URI of the item being processed. */
65    private Uri mUri;
66    /** The location this session was created at. Used for media store. */
67    private Location mLocation;
68    /** The current progress of this session in percent. */
69    private int mProgressPercent = 0;
70    /** A message ID for the current progress state. */
71    private CharSequence mProgressMessage;
72    /** A place holder for this capture session. */
73    private PlaceholderManager.Session mPlaceHolderSession;
74    private Uri mContentUri;
75    /** Whether this image was finished. */
76    private volatile boolean mIsFinished;
77
78    /**
79     * Creates a new {@link CaptureSession}.
80     *
81     * @param title the title of this session.
82     * @param sessionStartMillis the timestamp of this capture session (since
83     *            epoch).
84     * @param location the location of this session, used for media store.
85     * @param temporarySessionFile used to create a temporary session file if
86     *            necessary.
87     * @param captureSessionManager the capture session manager responsible for
88     *            this session.
89     * @param placeholderManager used to add/update/remove session placeholders.
90     * @param mediaSaver used to store images on disk and add them to the media
91     *            store.
92     * @param stackSaver used to save stacks of images that belong to this
93     *            session.
94     */
95    /* package */CaptureSessionImpl(String title,
96            long sessionStartMillis, Location location, TemporarySessionFile temporarySessionFile,
97            CaptureSessionManager captureSessionManager, SessionNotifier sessionNotifier,
98            PlaceholderManager placeholderManager, MediaSaver mediaSaver, StackSaver stackSaver) {
99        mTitle = title;
100        mSessionStartMillis = sessionStartMillis;
101        mLocation = location;
102        mTempOutputFile = temporarySessionFile;
103        mSessionManager = captureSessionManager;
104        mSessionNotifier = sessionNotifier;
105        mPlaceholderManager = placeholderManager;
106        mMediaSaver = mediaSaver;
107        mStackSaver = stackSaver;
108        mIsFinished = false;
109    }
110
111    @Override
112    public String getTitle() {
113        return mTitle;
114    }
115
116    @Override
117    public Location getLocation() {
118        return mLocation;
119    }
120
121    @Override
122    public void setLocation(Location location) {
123        mLocation = location;
124    }
125
126    @Override
127    public synchronized int getProgress() {
128        return mProgressPercent;
129    }
130
131    @Override
132    public synchronized void setProgress(int percent) {
133        mProgressPercent = percent;
134        mSessionNotifier.notifyTaskProgress(mUri, mProgressPercent);
135        for (ProgressListener listener : mProgressListeners) {
136            listener.onProgressChanged(percent);
137        }
138    }
139
140    @Override
141    public synchronized CharSequence getProgressMessage() {
142        return mProgressMessage;
143    }
144
145    @Override
146    public synchronized void setProgressMessage(CharSequence message) {
147        mProgressMessage = message;
148        mSessionNotifier.notifyTaskProgressText(mUri, message);
149        for (ProgressListener listener : mProgressListeners) {
150            listener.onStatusMessageChanged(message);
151        }
152    }
153
154    @Override
155    public void updateThumbnail(Bitmap bitmap) {
156        mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, bitmap);
157        mSessionNotifier.notifySessionUpdated(mUri);
158    }
159
160    @Override
161    public void updateCaptureIndicatorThumbnail(Bitmap indicator, int rotationDegrees) {
162        onCaptureIndicatorUpdate(indicator, rotationDegrees);
163    }
164
165    @Override
166    public synchronized void startEmpty(Size pictureSize) {
167        if (mIsFinished) {
168            return;
169        }
170
171        mProgressMessage = "";
172        mPlaceHolderSession = mPlaceholderManager.insertEmptyPlaceholder(mTitle, pictureSize,
173                mSessionStartMillis);
174        mUri = mPlaceHolderSession.outputUri;
175        mSessionManager.putSession(mUri, this);
176        mSessionNotifier.notifyTaskQueued(mUri);
177    }
178
179    @Override
180    public synchronized void startSession(Bitmap placeholder, CharSequence progressMessage) {
181        if (mIsFinished) {
182            return;
183        }
184
185        mProgressMessage = progressMessage;
186        mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder,
187                mSessionStartMillis);
188        mUri = mPlaceHolderSession.outputUri;
189        mSessionManager.putSession(mUri, this);
190        mSessionNotifier.notifyTaskQueued(mUri);
191        onCaptureIndicatorUpdate(placeholder, 0);
192    }
193
194    @Override
195    public synchronized void startSession(byte[] placeholder, CharSequence progressMessage) {
196        if (mIsFinished) {
197            return;
198        }
199
200        mProgressMessage = progressMessage;
201
202        mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder,
203                mSessionStartMillis);
204        mUri = mPlaceHolderSession.outputUri;
205        mSessionManager.putSession(mUri, this);
206        mSessionNotifier.notifyTaskQueued(mUri);
207        Optional<Bitmap> placeholderBitmap =
208                mPlaceholderManager.getPlaceholder(mPlaceHolderSession);
209        if (placeholderBitmap.isPresent()) {
210            onCaptureIndicatorUpdate(placeholderBitmap.get(), 0);
211        }
212    }
213
214    @Override
215    public synchronized void startSession(Uri uri, CharSequence progressMessage) {
216        mUri = uri;
217        mProgressMessage = progressMessage;
218        mPlaceHolderSession = mPlaceholderManager.convertToPlaceholder(uri);
219
220        mSessionManager.putSession(mUri, this);
221        mSessionNotifier.notifyTaskQueued(mUri);
222    }
223
224    @Override
225    public synchronized void cancel() {
226        if (isStarted()) {
227            mSessionManager.removeSession(mUri);
228        }
229    }
230
231    @Override
232    public synchronized void saveAndFinish(byte[] data, int width, int height, int orientation,
233            ExifInterface exif, final MediaSaver.OnMediaSavedListener listener) {
234        mIsFinished = true;
235        if (mPlaceHolderSession == null) {
236            mMediaSaver.addImage(
237                    data, mTitle, mSessionStartMillis, mLocation, width, height,
238                    orientation, exif, listener);
239            return;
240        }
241        mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
242                orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
243
244        mSessionNotifier.notifyTaskDone(mUri);
245    }
246
247    @Override
248    public StackSaver getStackSaver() {
249        return mStackSaver;
250    }
251
252    @Override
253    public void finish() {
254        if (mPlaceHolderSession == null) {
255            throw new IllegalStateException(
256                    "Cannot call finish without calling startSession first.");
257        }
258
259        mIsFinished = true;
260        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
261            @Override
262            public void run() {
263                byte[] jpegDataTemp;
264                if (mTempOutputFile.isUsable()) {
265                    try {
266                        jpegDataTemp = FileUtil.readFileToByteArray(mTempOutputFile.getFile());
267                    } catch (IOException e) {
268                        return;
269                    }
270                } else {
271                    return;
272                }
273                final byte[] jpegData = jpegDataTemp;
274
275                BitmapFactory.Options options = new BitmapFactory.Options();
276                options.inJustDecodeBounds = true;
277                BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
278                int width = options.outWidth;
279                int height = options.outHeight;
280                int rotation = 0;
281                ExifInterface exif = null;
282                try {
283                    exif = new ExifInterface();
284                    exif.readExif(jpegData);
285                } catch (IOException e) {
286                    Log.w(TAG, "Could not read exif", e);
287                    exif = null;
288                }
289                CaptureSessionImpl.this.saveAndFinish(jpegData, width, height, rotation, exif,
290                        null);
291            }
292        });
293
294    }
295
296    @Override
297    public TemporarySessionFile getTempOutputFile() {
298        return mTempOutputFile;
299    }
300
301    @Override
302    public Uri getUri() {
303        return mUri;
304    }
305
306    @Override
307    public void updatePreview() {
308        final File path;
309        if (mTempOutputFile.isUsable()) {
310            path = mTempOutputFile.getFile();
311        } else {
312            Log.e(TAG, "Cannot update preview");
313            return;
314        }
315        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
316            @Override
317            public void run() {
318                byte[] jpegDataTemp;
319                try {
320                    jpegDataTemp = FileUtil.readFileToByteArray(path);
321                } catch (IOException e) {
322                    return;
323                }
324                final byte[] jpegData = jpegDataTemp;
325
326                BitmapFactory.Options options = new BitmapFactory.Options();
327                Bitmap placeholder = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
328                        options);
329                mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, placeholder);
330                mSessionNotifier.notifySessionUpdated(mUri);
331            }
332        });
333    }
334
335    @Override
336    public void finishWithFailure(CharSequence reason) {
337        if (mPlaceHolderSession == null) {
338            throw new IllegalStateException(
339                    "Cannot call finish without calling startSession first.");
340        }
341        mProgressMessage = reason;
342        mSessionManager.putErrorMessage(mUri, reason);
343        mSessionNotifier.notifyTaskFailed(mUri, reason);
344    }
345
346    @Override
347    public void addProgressListener(ProgressListener listener) {
348        listener.onStatusMessageChanged(mProgressMessage);
349        listener.onProgressChanged(mProgressPercent);
350        mProgressListeners.add(listener);
351    }
352
353    @Override
354    public void removeProgressListener(ProgressListener listener) {
355        mProgressListeners.remove(listener);
356    }
357
358    @Override
359    public void finalize() {
360        mPlaceholderManager.removePlaceholder(mPlaceHolderSession);
361    }
362
363    private void onCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
364        mSessionNotifier.notifySessionCaptureIndicatorAvailable(indicator, rotationDegrees);
365    }
366
367    private boolean isStarted() {
368        return mUri != null;
369    }
370}
371