Home | History | Annotate | Download | only in session
      1 /*
      2  * Copyright (C) 2013 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.content.ContentResolver;
     20 import android.graphics.BitmapFactory;
     21 import android.location.Location;
     22 import android.net.Uri;
     23 import android.os.AsyncTask;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 
     27 import com.android.camera.app.MediaSaver;
     28 import com.android.camera.app.MediaSaver.OnMediaSavedListener;
     29 import com.android.camera.data.LocalData;
     30 import com.android.camera.debug.Log;
     31 import com.android.camera.exif.ExifInterface;
     32 import com.android.camera.util.FileUtil;
     33 
     34 import java.io.File;
     35 import java.io.IOException;
     36 import java.util.HashMap;
     37 import java.util.HashSet;
     38 import java.util.LinkedList;
     39 import java.util.Map;
     40 
     41 /**
     42  * Implementation for the {@link CaptureSessionManager}.
     43  */
     44 public class CaptureSessionManagerImpl implements CaptureSessionManager {
     45 
     46     private static final Log.Tag TAG = new Log.Tag("CaptureSessMgrImpl");
     47     public static final String TEMP_SESSIONS = "TEMP_SESSIONS";
     48 
     49     private class CaptureSessionImpl implements CaptureSession {
     50         /** A URI of the item being processed. */
     51         private Uri mUri;
     52         /** The title of the item being processed. */
     53         private final String mTitle;
     54         /** The location this session was created at. Used for media store.*/
     55         private Location mLocation;
     56         /** The current progress of this session in percent. */
     57         private int mProgressPercent = 0;
     58         /** A message ID for the current progress state. */
     59         private CharSequence mProgressMessage;
     60         /** A place holder for this capture session. */
     61         private PlaceholderManager.Session mPlaceHolderSession;
     62         private boolean mNoPlaceHolderRequired = false;
     63         private Uri mContentUri;
     64         /** These listeners get informed about progress updates. */
     65         private final HashSet<ProgressListener> mProgressListeners =
     66                 new HashSet<ProgressListener>();
     67         private final long mSessionStartMillis;
     68 
     69         /**
     70          * Creates a new {@link CaptureSession}.
     71          *
     72          * @param title the title of this session.
     73          * @param sessionStartMillis the timestamp of this capture session (since epoch).
     74          * @param location the location of this session, used for media store.
     75          */
     76         private CaptureSessionImpl(String title, long sessionStartMillis, Location location) {
     77             mTitle = title;
     78             mSessionStartMillis = sessionStartMillis;
     79             mLocation = location;
     80         }
     81 
     82         @Override
     83         public String getTitle() {
     84             return mTitle;
     85         }
     86 
     87         @Override
     88         public Location getLocation() {
     89             return mLocation;
     90         }
     91 
     92         @Override
     93         public void setLocation(Location location) {
     94             mLocation = location;
     95         }
     96 
     97         @Override
     98         public synchronized void setProgress(int percent) {
     99             mProgressPercent = percent;
    100             notifyTaskProgress(mUri, mProgressPercent);
    101             for (ProgressListener listener : mProgressListeners) {
    102                 listener.onProgressChanged(percent);
    103             }
    104         }
    105 
    106         @Override
    107         public synchronized int getProgress() {
    108             return mProgressPercent;
    109         }
    110 
    111         @Override
    112         public synchronized CharSequence getProgressMessage() {
    113             return mProgressMessage;
    114         }
    115 
    116         @Override
    117         public synchronized void setProgressMessage(CharSequence message) {
    118             mProgressMessage = message;
    119             notifyTaskProgressText(mUri, message);
    120             for (ProgressListener listener : mProgressListeners) {
    121                 listener.onStatusMessageChanged(message);
    122             }
    123         }
    124 
    125         @Override
    126         public void startEmpty() {
    127             mNoPlaceHolderRequired = true;
    128         }
    129 
    130         @Override
    131         public synchronized void startSession(byte[] placeholder, CharSequence progressMessage) {
    132             mProgressMessage = progressMessage;
    133 
    134             // TODO: This needs to happen outside the UI thread.
    135             mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder,
    136                     mSessionStartMillis);
    137             mUri = mPlaceHolderSession.outputUri;
    138             putSession(mUri, this);
    139             notifyTaskQueued(mUri);
    140         }
    141 
    142         @Override
    143         public synchronized void startSession(Uri uri, CharSequence progressMessage) {
    144             mUri = uri;
    145             mProgressMessage = progressMessage;
    146             mPlaceHolderSession = mPlaceholderManager.convertToPlaceholder(uri);
    147 
    148             mSessions.put(mUri.toString(), this);
    149             notifyTaskQueued(mUri);
    150         }
    151 
    152         @Override
    153         public synchronized void cancel() {
    154             if (mUri != null) {
    155                 removeSession(mUri.toString());
    156             }
    157         }
    158 
    159         @Override
    160         public synchronized void saveAndFinish(byte[] data, int width, int height, int orientation,
    161                 ExifInterface exif, final OnMediaSavedListener listener) {
    162             if (mNoPlaceHolderRequired) {
    163                 mMediaSaver.addImage(
    164                         data, mTitle, mSessionStartMillis, null, width, height,
    165                         orientation, exif, listener, mContentResolver);
    166                 return;
    167             }
    168 
    169             if (mPlaceHolderSession == null) {
    170                 throw new IllegalStateException(
    171                         "Cannot call saveAndFinish without calling startSession first.");
    172             }
    173 
    174             // TODO: This needs to happen outside the UI thread.
    175             mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
    176                     orientation, exif, data, width, height, LocalData.MIME_TYPE_JPEG);
    177 
    178             removeSession(mUri.toString());
    179             notifyTaskDone(mPlaceHolderSession.outputUri);
    180         }
    181 
    182         @Override
    183         public void finish() {
    184             if (mPlaceHolderSession == null) {
    185                 throw new IllegalStateException(
    186                         "Cannot call finish without calling startSession first.");
    187             }
    188 
    189             final String path = this.getPath();
    190 
    191             AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
    192                 @Override
    193                 public void run() {
    194                     byte[] jpegDataTemp;
    195                     try {
    196                         jpegDataTemp = FileUtil.readFileToByteArray(new File(path));
    197                     } catch (IOException e) {
    198                         return;
    199                     }
    200                     final byte[] jpegData = jpegDataTemp;
    201 
    202                     BitmapFactory.Options options = new BitmapFactory.Options();
    203                     options.inJustDecodeBounds = true;
    204                     BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
    205                     int width = options.outWidth;
    206                     int height = options.outHeight;
    207                     int rotation = 0;
    208                     ExifInterface exif = null;
    209                     try {
    210                         exif = new ExifInterface();
    211                         exif.readExif(jpegData);
    212                     } catch (IOException e) {
    213                         Log.w(TAG, "Could not read exif", e);
    214                         exif = null;
    215                     }
    216                     CaptureSessionImpl.this.saveAndFinish(jpegData, width, height, rotation, exif,
    217                             null);
    218                 }
    219             });
    220 
    221         }
    222 
    223         @Override
    224         public String getPath() {
    225             if (mUri == null) {
    226                 throw new IllegalStateException("Cannot retrieve URI of not started session.");
    227             }
    228 
    229             File tempDirectory = null;
    230             try {
    231                 tempDirectory = new File(
    232                         getSessionDirectory(TEMP_SESSIONS), mTitle);
    233             } catch (IOException e) {
    234                 Log.e(TAG, "Could not get temp session directory", e);
    235                 throw new RuntimeException("Could not get temp session directory", e);
    236             }
    237             tempDirectory.mkdirs();
    238             File tempFile = new File(tempDirectory, mTitle  + ".jpg");
    239             try {
    240                 if (!tempFile.exists()) {
    241                     tempFile.createNewFile();
    242                 }
    243             } catch (IOException e) {
    244                 Log.e(TAG, "Could not create temp session file", e);
    245                 throw new RuntimeException("Could not create temp session file", e);
    246             }
    247             return tempFile.getPath();
    248         }
    249 
    250         @Override
    251         public Uri getUri() {
    252             return mUri;
    253         }
    254 
    255         @Override
    256         public Uri getContentUri() {
    257             return mContentUri;
    258         }
    259 
    260         @Override
    261         public boolean hasPath() {
    262             return mUri != null;
    263         }
    264 
    265         @Override
    266         public void onPreviewAvailable() {
    267             notifySessionPreviewAvailable(mPlaceHolderSession.outputUri);
    268         }
    269 
    270         @Override
    271         public void updatePreview(String previewPath) {
    272 
    273             final String path = this.getPath();
    274 
    275             AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
    276                 @Override
    277                 public void run() {
    278                     byte[] jpegDataTemp;
    279                     try {
    280                         jpegDataTemp = FileUtil.readFileToByteArray(new File(path));
    281                     } catch (IOException e) {
    282                         return;
    283                     }
    284                     final byte[] jpegData = jpegDataTemp;
    285 
    286                     BitmapFactory.Options options = new BitmapFactory.Options();
    287                     options.inJustDecodeBounds = true;
    288                     BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
    289                     int width = options.outWidth;
    290                     int height = options.outHeight;
    291 
    292                     mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, jpegData, width, height);
    293                     onPreviewAvailable();
    294                 }
    295             });
    296         }
    297 
    298         @Override
    299         public void finishWithFailure(CharSequence reason) {
    300             if (mPlaceHolderSession == null) {
    301                 throw new IllegalStateException(
    302                         "Cannot call finish without calling startSession first.");
    303             }
    304             mProgressMessage = reason;
    305 
    306             removeSession(mUri.toString());
    307             mFailedSessionMessages.put(mPlaceHolderSession.outputUri, reason);
    308             notifyTaskFailed(mPlaceHolderSession.outputUri, reason);
    309         }
    310 
    311         @Override
    312         public void addProgressListener(ProgressListener listener) {
    313             listener.onStatusMessageChanged(mProgressMessage);
    314             listener.onProgressChanged(mProgressPercent);
    315             mProgressListeners.add(listener);
    316         }
    317 
    318         @Override
    319         public void removeProgressListener(ProgressListener listener) {
    320             mProgressListeners.remove(listener);
    321         }
    322     }
    323 
    324     private final MediaSaver mMediaSaver;
    325     private final PlaceholderManager mPlaceholderManager;
    326     private final SessionStorageManager mSessionStorageManager;
    327     private final ContentResolver mContentResolver;
    328 
    329     /** Failed session messages. Uri -> message. */
    330     private final HashMap<Uri, CharSequence> mFailedSessionMessages =
    331             new HashMap<Uri, CharSequence>();
    332 
    333     /**
    334      * We use this to fire events to the session listeners from the main thread.
    335      */
    336     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    337 
    338     /** Sessions in progress, keyed by URI. */
    339     private final Map<String, CaptureSession> mSessions;
    340 
    341     /** Listeners interested in task update events. */
    342     private final LinkedList<SessionListener> mTaskListeners = new LinkedList<SessionListener>();
    343 
    344     /**
    345      * Initializes a new {@link CaptureSessionManager} implementation.
    346      *
    347      * @param mediaSaver used to store the resulting media item
    348      * @param contentResolver required by the media saver
    349      * @param placeholderManager used to manage placeholders in the filmstrip
    350      *            before the final result is ready
    351      * @param sessionStorageManager used to tell modules where to store
    352      *            temporary session data
    353      */
    354     public CaptureSessionManagerImpl(MediaSaver mediaSaver, ContentResolver contentResolver,
    355             PlaceholderManager placeholderManager, SessionStorageManager sessionStorageManager) {
    356         mSessions = new HashMap<String, CaptureSession>();
    357         mMediaSaver = mediaSaver;
    358         mContentResolver = contentResolver;
    359         mPlaceholderManager = placeholderManager;
    360         mSessionStorageManager = sessionStorageManager;
    361     }
    362 
    363     @Override
    364     public CaptureSession createNewSession(String title, long sessionStartTime, Location location) {
    365         return new CaptureSessionImpl(title, sessionStartTime, location);
    366     }
    367 
    368     @Override
    369     public CaptureSession createSession() {
    370         return new CaptureSessionImpl(null, System.currentTimeMillis(), null);
    371     }
    372 
    373     @Override
    374     public void putSession(Uri sessionUri, CaptureSession session) {
    375         synchronized (mSessions)  {
    376             mSessions.put(sessionUri.toString(), session);
    377         }
    378     }
    379 
    380     @Override
    381     public CaptureSession getSession(Uri sessionUri) {
    382         synchronized (mSessions)  {
    383             return mSessions.get(sessionUri.toString());
    384         }
    385     }
    386 
    387     @Override
    388     public void saveImage(byte[] data, String title, long date, Location loc,
    389             int width, int height, int orientation, ExifInterface exif,
    390             OnMediaSavedListener listener) {
    391         mMediaSaver.addImage(data, title, date, loc, width, height, orientation, exif,
    392                 listener, mContentResolver);
    393     }
    394 
    395     @Override
    396     public void addSessionListener(SessionListener listener) {
    397         synchronized (mTaskListeners) {
    398             mTaskListeners.add(listener);
    399         }
    400     }
    401 
    402     @Override
    403     public void removeSessionListener(SessionListener listener) {
    404         synchronized (mTaskListeners) {
    405             mTaskListeners.remove(listener);
    406         }
    407     }
    408 
    409     @Override
    410     public File getSessionDirectory(String subDirectory) throws IOException {
    411       return mSessionStorageManager.getSessionDirectory(subDirectory);
    412     }
    413 
    414     private void removeSession(String sessionUri) {
    415         synchronized (mSessions) {
    416             mSessions.remove(sessionUri);
    417         }
    418     }
    419 
    420     /**
    421      * Notifies all task listeners that the task with the given URI has been
    422      * queued.
    423      */
    424     private void notifyTaskQueued(final Uri uri) {
    425         mMainHandler.post(new Runnable() {
    426             @Override
    427             public void run() {
    428                 synchronized (mTaskListeners) {
    429                     for (SessionListener listener : mTaskListeners) {
    430                         listener.onSessionQueued(uri);
    431                     }
    432                 }
    433             }
    434         });
    435     }
    436 
    437     /**
    438      * Notifies all task listeners that the task with the given URI has been
    439      * finished.
    440      */
    441     private void notifyTaskDone(final Uri uri) {
    442         mMainHandler.post(new Runnable() {
    443             @Override
    444             public void run() {
    445                 synchronized (mTaskListeners) {
    446                     for (SessionListener listener : mTaskListeners) {
    447                         listener.onSessionDone(uri);
    448                     }
    449                 }
    450             }
    451         });
    452     }
    453 
    454     /**
    455      * Notifies all task listeners that the task with the given URI has been
    456      * failed to process.
    457      */
    458     private void notifyTaskFailed(final Uri uri, final CharSequence reason) {
    459         mMainHandler.post(new Runnable() {
    460             @Override
    461             public void run() {
    462                 synchronized (mTaskListeners) {
    463                     for (SessionListener listener : mTaskListeners) {
    464                         listener.onSessionFailed(uri, reason);
    465                     }
    466                 }
    467             }
    468         });
    469     }
    470 
    471     /**
    472      * Notifies all task listeners that the task with the given URI has
    473      * progressed to the given state.
    474      */
    475     private void notifyTaskProgress(final Uri uri, final int progressPercent) {
    476         mMainHandler.post(new Runnable() {
    477             @Override
    478             public void run() {
    479                 synchronized (mTaskListeners) {
    480                     for (SessionListener listener : mTaskListeners) {
    481                         listener.onSessionProgress(uri, progressPercent);
    482                     }
    483                 }
    484             }
    485         });
    486     }
    487 
    488     /**
    489      * Notifies all task listeners that the task with the given URI has
    490      * changed its progress message.
    491      */
    492     private void notifyTaskProgressText(final Uri uri, final CharSequence message) {
    493         mMainHandler.post(new Runnable() {
    494             @Override
    495             public void run() {
    496                 synchronized (mTaskListeners) {
    497                     for (SessionListener listener : mTaskListeners) {
    498                         listener.onSessionProgressText(uri, message);
    499                     }
    500                 }
    501             }
    502         });
    503     }
    504 
    505     /**
    506      * Notifies all task listeners that the task with the given URI has updated
    507      * its media.
    508      */
    509     private void notifySessionPreviewAvailable(final Uri uri) {
    510         mMainHandler.post(new Runnable() {
    511             @Override
    512             public void run() {
    513                 synchronized (mTaskListeners) {
    514                     for (SessionListener listener : mTaskListeners) {
    515                         listener.onSessionPreviewAvailable(uri);
    516                     }
    517                 }
    518             }
    519         });
    520     }
    521 
    522     @Override
    523     public boolean hasErrorMessage(Uri uri) {
    524         return mFailedSessionMessages.containsKey(uri);
    525     }
    526 
    527     @Override
    528     public CharSequence getErrorMesage(Uri uri) {
    529         return mFailedSessionMessages.get(uri);
    530     }
    531 
    532     @Override
    533     public void removeErrorMessage(Uri uri) {
    534         mFailedSessionMessages.remove(uri);
    535     }
    536 
    537     @Override
    538     public void fillTemporarySession(final SessionListener listener) {
    539         mMainHandler.post(new Runnable() {
    540             @Override
    541             public void run() {
    542                 synchronized (mSessions) {
    543                     for (String sessionUri : mSessions.keySet()) {
    544                         CaptureSession session = mSessions.get(sessionUri);
    545                         listener.onSessionQueued(session.getUri());
    546                         listener.onSessionProgress(session.getUri(), session.getProgress());
    547                         listener.onSessionProgressText(session.getUri(),
    548                                 session.getProgressMessage());
    549                     }
    550                 }
    551             }
    552         });
    553     }
    554 }
    555