import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
import com.android.camera.app.MediaSaver;
import com.android.camera.app.MediaSaver.OnMediaSavedListener;
+import com.android.camera.async.MainThread;
import com.android.camera.data.FilmstripItemData;
import com.android.camera.debug.Log;
import com.android.camera.exif.ExifInterface;
import com.android.camera.util.FileUtil;
+import com.android.camera.util.Size;
import java.io.File;
import java.io.IOException;
/**
* Implementation for the {@link CaptureSessionManager}.
+ * <p>
+ * Basic usage:
+ * <ul>
+ * <li>Create a new capture session.</li>
+ * <li>Pass it around to anywhere where the status of a session needs to be
+ * updated.</li>
+ * <li>If this is a longer operation, use one of the start* methods to indicate
+ * that processing of this session has started. The Camera app right now will
+ * use this to add a new item to the filmstrip and indicate the current
+ * progress.</li>
+ * <li>If the final result is already available and no processing is required,
+ * store the final image using saveAndFinish</li>
+ * <li>For longer operations, update the thumbnail and status message using the
+ * provided methods.</li>
+ * <li>For longer operations, update the thumbnail and status message using the
+ * provided methods.</li>
+ * <li>Once processing is done, the final image can be saved using saveAndFinish
+ * </li>
+ * </ul>
+ * </p>
+ * It's OK to call saveAndFinish either before or after the session has been
+ * started.
+ * <p>
+ * If startSession is called after the session has been finished, it will be
+ * treated as a no-op.
+ * </p>
*/
public class CaptureSessionManagerImpl implements CaptureSessionManager {
- private static final Log.Tag TAG = new Log.Tag("CaptureSessMgrImpl");
- public static final String TEMP_SESSIONS = "TEMP_SESSIONS";
-
private class CaptureSessionImpl implements CaptureSession {
- /** A URI of the item being processed. */
- private Uri mUri;
/** The title of the item being processed. */
private final String mTitle;
- /** The location this session was created at. Used for media store. */
- private Location mLocation;
- /** The current progress of this session in percent. */
- private int mProgressPercent = 0;
- /** A message ID for the current progress state. */
- private CharSequence mProgressMessage;
- /** A place holder for this capture session. */
- private PlaceholderManager.Session mPlaceHolderSession;
- private boolean mNoPlaceHolderRequired = false;
- private Uri mContentUri;
/** These listeners get informed about progress updates. */
- private final HashSet<ProgressListener> mProgressListeners =
- new HashSet<ProgressListener>();
+ private final HashSet<ProgressListener> mProgressListeners = new HashSet<>();
private final long mSessionStartMillis;
/**
* The path that can be used to write the final JPEG output temporarily,
* before it is copied to the final location.
*/
private final String mTempOutputPath;
-
/** Saver that is used to store a stack of images. */
private final StackSaver mStackSaver;
+ /** A URI of the item being processed. */
+ private Uri mUri;
+ /** The location this session was created at. Used for media store. */
+ private Location mLocation;
+ /** The current progress of this session in percent. */
+ private int mProgressPercent = 0;
+ /** A message ID for the current progress state. */
+ private CharSequence mProgressMessage;
+ /** A place holder for this capture session. */
+ private PlaceholderManager.Session mPlaceHolderSession;
+ private Uri mContentUri;
+ /** Whether this image was finished. */
+ private volatile boolean mIsFinished;
/**
* Creates a new {@link CaptureSession}.
* @param sessionStartMillis the timestamp of this capture session
* (since epoch).
* @param location the location of this session, used for media store.
+ * @throws IOException in case the storage required to store session
+ * data is not available.
*/
private CaptureSessionImpl(String title, long sessionStartMillis, Location location,
- StackSaver stackSaver) {
+ StackSaver stackSaver) throws IOException {
mTitle = title;
mSessionStartMillis = sessionStartMillis;
mLocation = location;
- mTempOutputPath = createTempOutputPath(mTitle);
+ mTempOutputPath = mSessionStorageManager.createTemporaryOutputPath(TEMP_SESSIONS,
+ mTitle);
mStackSaver = stackSaver;
+ mIsFinished = false;
}
@Override
}
@Override
+ public synchronized int getProgress() {
+ return mProgressPercent;
+ }
+
+ @Override
public synchronized void setProgress(int percent) {
mProgressPercent = percent;
notifyTaskProgress(mUri, mProgressPercent);
}
@Override
- public synchronized int getProgress() {
- return mProgressPercent;
- }
-
- @Override
public synchronized CharSequence getProgressMessage() {
return mProgressMessage;
}
}
@Override
- public void startEmpty() {
- mNoPlaceHolderRequired = true;
+ public void updateThumbnail(Bitmap bitmap) {
+ mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, bitmap);
+ notifyTaskQueued(mUri);
+ onPreviewAvailable();
+ }
+
+ @Override
+ public synchronized void startEmpty(Size pictureSize) {
+ if (mIsFinished) {
+ return;
+ }
+
+ mProgressMessage = "";
+ mPlaceHolderSession = mPlaceholderManager.insertEmptyPlaceholder(mTitle, pictureSize,
+ mSessionStartMillis);
+ mUri = mPlaceHolderSession.outputUri;
+ putSession(mUri, this);
+ notifyTaskQueued(mUri);
+ onPreviewAvailable();
}
@Override
public synchronized void startSession(Bitmap placeholder, CharSequence progressMessage) {
- mProgressMessage = progressMessage;
+ if (mIsFinished) {
+ return;
+ }
+ mProgressMessage = progressMessage;
mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder,
mSessionStartMillis);
mUri = mPlaceHolderSession.outputUri;
putSession(mUri, this);
notifyTaskQueued(mUri);
+ onPreviewAvailable();
}
@Override
public synchronized void startSession(byte[] placeholder, CharSequence progressMessage) {
+ if (mIsFinished) {
+ return;
+ }
+
mProgressMessage = progressMessage;
mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder,
mUri = mPlaceHolderSession.outputUri;
putSession(mUri, this);
notifyTaskQueued(mUri);
+ onPreviewAvailable();
}
@Override
@Override
public synchronized void saveAndFinish(byte[] data, int width, int height, int orientation,
ExifInterface exif, final OnMediaSavedListener listener) {
- if (mNoPlaceHolderRequired) {
+ mIsFinished = true;
+ if (mPlaceHolderSession == null) {
mMediaSaver.addImage(
- data, mTitle, mSessionStartMillis, null, width, height,
+ data, mTitle, mSessionStartMillis, mLocation, width, height,
orientation, exif, listener, mContentResolver);
return;
}
-
- if (mPlaceHolderSession == null) {
- throw new IllegalStateException(
- "Cannot call saveAndFinish without calling startSession first.");
- }
-
- // TODO: This needs to happen outside the UI thread.
mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
"Cannot call finish without calling startSession first.");
}
+ mIsFinished = true;
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
return mTempOutputPath;
}
- /**
- * Initializes the directories for storing the final output temporarily
- * before it is copied to the final location after calling
- * {@link #finish()}.
- * <p>
- * This method will make sure the directories and file exists and is
- * writeable, otherwise it will throw an exception.
- *
- * @param title the title of this session. Will be used to create a
- * unique sub-directory.
- * @return The path to a JPEG file which can be used to write the final
- * output to.
- */
- private String createTempOutputPath(String title) {
- File tempDirectory = null;
- try {
- tempDirectory = new File(
- getSessionDirectory(TEMP_SESSIONS), title);
- } catch (IOException e) {
- Log.e(TAG, "Could not get temp session directory", e);
- throw new RuntimeException("Could not get temp session directory", e);
- }
- if (!tempDirectory.mkdirs()) {
- throw new IllegalStateException("Could not create output data directory.");
- }
- File tempFile = new File(tempDirectory, mTitle + ".jpg");
- try {
- if (!tempFile.exists()) {
- if (!tempFile.createNewFile()) {
- throw new IllegalStateException("Could not create output data file.");
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "Could not create temp session file", e);
- throw new RuntimeException("Could not create temp session file", e);
- }
-
- if (!tempFile.canWrite()) {
- throw new RuntimeException("Temporary output file is not writeable.");
- }
- return tempFile.getPath();
- }
-
@Override
public Uri getUri() {
return mUri;
}
@Override
- public Uri getContentUri() {
- return mContentUri;
- }
-
- @Override
- public boolean isStarted() {
- return mUri != null;
- }
-
- @Override
- public void onPreviewAvailable() {
- notifySessionPreviewAvailable(mPlaceHolderSession.outputUri);
- }
-
- @Override
public void updatePreview(String previewPath) {
final String path = this.getTempOutputPath();
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
public void removeProgressListener(ProgressListener listener) {
mProgressListeners.remove(listener);
}
- }
+ private void onPreviewAvailable() {
+ notifySessionPreviewAvailable(mPlaceHolderSession.outputUri);
+ }
+
+ private boolean isStarted() {
+ return mUri != null;
+ }
+ }
+ public static final String TEMP_SESSIONS = "TEMP_SESSIONS";
+ private static final Log.Tag TAG = new Log.Tag("CaptureSessMgrImpl");
private final MediaSaver mMediaSaver;
private final ContentResolver mContentResolver;
private final PlaceholderManager mPlaceholderManager;
private final SessionStorageManager mSessionStorageManager;
private final StackSaverFactory mStackSaverFactory;
+ private static final Bitmap NO_PREVIEW_BITMAP = null;
/** Failed session messages. Uri -> message. */
private final HashMap<Uri, CharSequence> mFailedSessionMessages =
/**
* We use this to fire events to the session listeners from the main thread.
*/
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final MainThread mMainHandler;
/** Sessions in progress, keyed by URI. */
private final Map<String, CaptureSession> mSessions;
*/
public CaptureSessionManagerImpl(MediaSaver mediaSaver, ContentResolver contentResolver,
PlaceholderManager placeholderManager, SessionStorageManager sessionStorageManager,
- StackSaverFactory stackSaverProvider) {
+ StackSaverFactory stackSaverProvider, MainThread mainHandler) {
mSessions = new HashMap<String, CaptureSession>();
mMediaSaver = mediaSaver;
mContentResolver = contentResolver;
mPlaceholderManager = placeholderManager;
mSessionStorageManager = sessionStorageManager;
mStackSaverFactory = stackSaverProvider;
+ mMainHandler = mainHandler;
}
@Override
- public CaptureSession createNewSession(String title, long sessionStartTime, Location location) {
+ public CaptureSession createNewSession(String title, long sessionStartTime, Location location) throws IOException {
return new CaptureSessionImpl(title, sessionStartTime, location, mStackSaverFactory.create(
title, location));
}
@Override
- public CaptureSession createSession() {
- return new CaptureSessionImpl(null, System.currentTimeMillis(), null, null);
- }
-
- @Override
public void putSession(Uri sessionUri, CaptureSession session) {
synchronized (mSessions) {
mSessions.put(sessionUri.toString(), session);
* queued.
*/
private void notifyTaskQueued(final Uri uri) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
* finished.
*/
private void notifyTaskDone(final Uri uri) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
* failed to process.
*/
private void notifyTaskFailed(final Uri uri, final CharSequence reason) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
* progressed to the given state.
*/
private void notifyTaskProgress(final Uri uri, final int progressPercent) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
* its progress message.
*/
private void notifyTaskProgressText(final Uri uri, final CharSequence message) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
* its media.
*/
private void notifySessionPreviewAvailable(final Uri uri) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mTaskListeners) {
@Override
public void fillTemporarySession(final SessionListener listener) {
- mMainHandler.post(new Runnable() {
+ mMainHandler.execute(new Runnable() {
@Override
public void run() {
synchronized (mSessions) {