Home | History | Annotate | Download | only in session
      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      */
    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
    124     public CaptureSessionStatsCollector getCollector() {
    125         return mCaptureSessionStatsCollector;
    126     }
    127 
    128     @Override
    129     public String getTitle() {
    130         return mTitle;
    131     }
    132 
    133     @Override
    134     public Location getLocation() {
    135         return mLocation;
    136     }
    137 
    138     @Override
    139     public void setLocation(Location location) {
    140         mLocation = location;
    141     }
    142 
    143     @Override
    144     public synchronized int getProgress() {
    145         return mProgressPercent;
    146     }
    147 
    148     @Override
    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
    162     public synchronized int getProgressMessageId() {
    163         return mProgressMessageId;
    164     }
    165 
    166     @Override
    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
    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
    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
    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
    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
    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
    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
    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
    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
    343     public StackSaver getStackSaver() {
    344         return mStackSaver;
    345     }
    346 
    347     @Override
    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
    391     public TemporarySessionFile getTempOutputFile() {
    392         return mTempOutputFile;
    393     }
    394 
    395     @Override
    396     public Uri getUri() {
    397         return mUri;
    398     }
    399 
    400     @Override
    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
    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
    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
    450     public void removeProgressListener(ProgressListener listener) {
    451         mProgressListeners.remove(listener);
    452     }
    453 
    454     @Override
    455     public void finalizeSession() {
    456         mPlaceholderManager.removePlaceholder(mPlaceHolder);
    457     }
    458 
    459     private void onCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
    460         mSessionNotifier.notifySessionCaptureIndicatorAvailable(indicator, rotationDegrees);
    461     }
    462 
    463     private boolean isStarted() {
    464         return mUri != null;
    465     }
    466 }
    467