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