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