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