summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSascha Haeberling <haeberling@google.com>2014-12-17 01:43:21 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-12-17 01:43:22 +0000
commitb79279f49e365cc6da75c1bc44b4c786036d0aa7 (patch)
tree58df3ae5fc952cd87bfa14ac7bd14dc4cebe13c5 /src
parentb530f7d637117e7c014d72d7692579d9dda6caa9 (diff)
parent24069e7a9cc9b4f908f18a71301285ccf5e164f6 (diff)
downloadandroid_packages_apps_Camera2-b79279f49e365cc6da75c1bc44b4c786036d0aa7.tar.gz
android_packages_apps_Camera2-b79279f49e365cc6da75c1bc44b4c786036d0aa7.tar.bz2
android_packages_apps_Camera2-b79279f49e365cc6da75c1bc44b4c786036d0aa7.zip
Merge "Adding stack support and hooking SmartBurst up to the new API." into ub-camera-haleakala
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/CameraActivity.java1
-rw-r--r--src/com/android/camera/CaptureModule.java59
-rw-r--r--src/com/android/camera/Storage.java48
-rw-r--r--src/com/android/camera/app/CameraApp.java7
-rw-r--r--src/com/android/camera/burst/BurstFacade.java17
-rw-r--r--src/com/android/camera/burst/BurstFacadeFactory.java52
-rw-r--r--src/com/android/camera/burst/BurstFacadeImpl.java194
-rw-r--r--src/com/android/camera/burst/BurstReadyStateChangeListener.java30
-rw-r--r--src/com/android/camera/burst/ToastingBurstFacadeDecorator.java13
-rw-r--r--src/com/android/camera/data/LocalMediaData.java24
-rw-r--r--src/com/android/camera/session/CaptureSession.java16
-rw-r--r--src/com/android/camera/session/CaptureSessionManager.java19
-rw-r--r--src/com/android/camera/session/CaptureSessionManagerImpl.java32
-rw-r--r--src/com/android/camera/session/StackSaver.java44
-rw-r--r--src/com/android/camera/session/StackSaverFactory.java55
-rw-r--r--src/com/android/camera/session/StackSaverImpl.java79
16 files changed, 455 insertions, 235 deletions
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index d18cca684..eade2a2b6 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -143,7 +143,6 @@ import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.MemoryCategory;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
-
import com.google.common.logging.eventprotos;
import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
import com.google.common.logging.eventprotos.MediaInteraction;
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 7142d5119..6fd69b2ef 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -45,6 +45,7 @@ import com.android.camera.app.LocationManager;
import com.android.camera.app.MediaSaver;
import com.android.camera.burst.BurstFacade;
import com.android.camera.burst.BurstFacadeFactory;
+import com.android.camera.burst.BurstReadyStateChangeListener;
import com.android.camera.burst.ToastingBurstFacadeDecorator;
import com.android.camera.burst.ToastingBurstFacadeDecorator.BurstToaster;
import com.android.camera.debug.DebugPropertyHelper;
@@ -80,6 +81,7 @@ import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
@@ -249,9 +251,6 @@ public class CaptureModule extends CameraModule
/** TODO: This is N5 specific. */
public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
- /** A directory to store debug information in during development. */
- private final File mDebugDataDir;
-
/** Used to distribute camera frames to consumers. */
private final FrameDistributorWrapper mFrameDistributor;
@@ -260,6 +259,7 @@ public class CaptureModule extends CameraModule
/** The burst manager for controlling the burst. */
private final BurstFacade mBurstController;
+ private static final String BURST_SESSIONS_DIR = "burst_sessions";
public CaptureModule(AppController appController) {
this(appController, false);
@@ -272,15 +272,20 @@ public class CaptureModule extends CameraModule
mContext = mAppController.getAndroidContext();
mSettingsManager = mAppController.getSettingsManager();
mSettingsManager.addListener(this);
- mDebugDataDir = mContext.getExternalCacheDir();
mStickyGcamCamera = stickyHdr;
mLocationManager = mAppController.getLocationManager();
mPreviewConsumer = new SurfaceTextureConsumer();
mFrameDistributor = new FrameDistributorWrapper();
- BurstFacade burstController = BurstFacadeFactory.create(mAppController, getServices()
- .getMediaSaver(),
- mLocationManager, mAppController.getOrientationManager(), mDebugDataDir);
+ BurstFacade burstController = BurstFacadeFactory.create(mContext, mAppController
+ .getOrientationManager(), new BurstReadyStateChangeListener() {
+ @Override
+ public void onBurstReadyStateChanged(boolean ready) {
+ // TODO: This needs to take into account the state of the whole
+ // system, not just burst.
+ mAppController.setShutterEnabled(ready);
+ }
+ });
BurstToaster toaster = new BurstToaster(appController.getAndroidContext());
mBurstController = new ToastingBurstFacadeDecorator(burstController, toaster);
}
@@ -301,7 +306,6 @@ public class CaptureModule extends CameraModule
mLayoutListener);
mAppController.setPreviewStatusListener(mUI);
- mBurstController.setContentResolver(activity.getContentResolver());
// Set the preview texture from UI for the SurfaceTextureConsumer.
mPreviewConsumer.setSurfaceTexture(
mAppController.getCameraAppUI().getSurfaceTexture(),
@@ -327,7 +331,16 @@ public class CaptureModule extends CameraModule
@Override
public void onShutterButtonLongPressed() {
- mBurstController.startBurst();
+ File tempSessionDataDirectory;
+ try {
+ tempSessionDataDirectory = getServices().getCaptureSessionManager()
+ .getSessionDirectory(BURST_SESSIONS_DIR);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot start burst", e);
+ return;
+ }
+ CaptureSession session = createCaptureSession();
+ mBurstController.startBurst(session, tempSessionDataDirectory);
}
@Override
@@ -365,22 +378,32 @@ public class CaptureModule extends CameraModule
}
private void takePictureNow() {
- Location location = mLocationManager.getCurrentLocation();
-
- // Set up the capture session.
- long sessionTime = System.currentTimeMillis();
- String title = CameraUtil.createJpegName(sessionTime);
- CaptureSession session = getServices().getCaptureSessionManager()
- .createNewSession(title, sessionTime, location);
+ CaptureSession session = createCaptureSession();
int orientation = mAppController.getOrientationManager().getDeviceOrientation()
.getDegrees();
+ // TODO: This should really not use getExternalCacheDir and instead use
+ // the SessionStorage API. Need to sync with gcam if that's OK.
PhotoCaptureParameters params = new PhotoCaptureParameters(
- title, orientation, location, mDebugDataDir, this, mHeading,
- getFlashModeFromSettings(), mZoomValue, 0);
+ session.getTitle(), orientation, session.getLocation(),
+ mContext.getExternalCacheDir(), this,
+ mHeading, getFlashModeFromSettings(), mZoomValue, 0);
mCamera.takePicture(params, session);
}
+ private CaptureSession createCaptureSession() {
+ long sessionTime = getSessionTime();
+ Location location = mLocationManager.getCurrentLocation();
+ String title = CameraUtil.createJpegName(sessionTime);
+ return getServices().getCaptureSessionManager()
+ .createNewSession(title, sessionTime, location);
+ }
+
+ private long getSessionTime() {
+ // TODO: Replace with a mockable TimeProvider interface.
+ return System.currentTimeMillis();
+ }
+
@Override
public void onCountDownFinished() {
mAppController.getCameraAppUI().transitionToCapture();
diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java
index 3616bc145..856ba77cc 100644
--- a/src/com/android/camera/Storage.java
+++ b/src/com/android/camera/Storage.java
@@ -44,6 +44,7 @@ public class Storage {
public static final String DCIM =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
public static final String DIRECTORY = DCIM + "/Camera";
+ public static final File DIRECTORY_FILE = new File(DIRECTORY);
public static final String JPEG_POSTFIX = ".jpg";
public static final String GIF_POSTFIX = ".gif";
// Match the code in MediaProvider.computeBucketValues().
@@ -68,7 +69,7 @@ public class Storage {
*
* @param resolver The The content resolver to use.
* @param title The title of the media file.
- * @param date The date fo the media file.
+ * @param date The date for the media file.
* @param location The location of the media file.
* @param orientation The orientation of the media file.
* @param exif The EXIF info. Can be {@code null}.
@@ -95,7 +96,7 @@ public class Storage {
* @param resolver The The content resolver to use.
* @param title The title of the media file.
* @param data The data to save.
- * @param date The date fo the media file.
+ * @param date The date for the media file.
* @param location The location of the media file.
* @param orientation The orientation of the media file.
* @param exif The EXIF info. Can be {@code null}.
@@ -107,7 +108,7 @@ public class Storage {
* @return The URI of the added image, or null if the image could not be
* added.
*/
- static Uri addImage(ContentResolver resolver, String title, long date,
+ public static Uri addImage(ContentResolver resolver, String title, long date,
Location location, int orientation, ExifInterface exif, byte[] data, int width,
int height, String mimeType) {
@@ -125,7 +126,7 @@ public class Storage {
*
* @param resolver The The content resolver to use.
* @param title The title of the media file.
- * @param date The date fo the media file.
+ * @param date The date for the media file.
* @param location The location of the media file.
* @param orientation The orientation of the media file.
* @param width The width of the media file after the orientation is
@@ -136,7 +137,7 @@ public class Storage {
* @return The content URI of the inserted media file or null, if the image
* could not be added.
*/
- private static Uri addImageToMediaStore(ContentResolver resolver, String title, long date,
+ public static Uri addImageToMediaStore(ContentResolver resolver, String title, long date,
Location location, int orientation, long jpegLength, String path, int width, int height,
String mimeType) {
// Insert into MediaStore.
@@ -264,7 +265,11 @@ public class Storage {
*
* @return The size of the file. -1 if failed.
*/
- private static long writeFile(String path, byte[] jpeg, ExifInterface exif) {
+ public static long writeFile(String path, byte[] jpeg, ExifInterface exif) {
+ if (!createDirectoryIfNeeded(path)) {
+ Log.e(TAG, "Failed to create parent directory for file: " + path);
+ return -1;
+ }
if (exif != null) {
try {
exif.writeExif(jpeg, path);
@@ -305,7 +310,30 @@ public class Storage {
return -1;
}
- // Updates the image values in MediaStore
+ /**
+ * Given a file path, makes sure the directory it's in exists, and if not
+ * that it is created.
+ *
+ * @param filePath the absolute path of a file, e.g. '/foo/bar/file.jpg'.
+ * @return Whether the directory exists. If 'false' is returned, this file
+ * cannot be written to since the parent directory could not be
+ * created.
+ */
+ private static boolean createDirectoryIfNeeded(String filePath) {
+ File parentFile = new File(filePath).getParentFile();
+
+ // If the parent exists, return 'true' if it is a directory. If it's a
+ // file, return 'false'.
+ if (parentFile.exists()) {
+ return parentFile.isDirectory();
+ }
+
+ // If the parent does not exists, attempt to create it and return
+ // whether creating it succeeded.
+ return parentFile.mkdirs();
+ }
+
+ /** Updates the image values in MediaStore. */
private static Uri updateImage(Uri imageUri, ContentResolver resolver, String title,
long date, Location location, int orientation, int jpegLength,
String path, int width, int height, String mimeType) {
@@ -330,6 +358,10 @@ public class Storage {
}
private static String generateFilepath(String title, String mimeType) {
+ return generateFilepath(DIRECTORY, title, mimeType);
+ }
+
+ public static String generateFilepath(String directory, String title, String mimeType) {
String extension = null;
if (LocalData.MIME_TYPE_JPEG.equals(mimeType)) {
extension = JPEG_POSTFIX;
@@ -338,7 +370,7 @@ public class Storage {
} else {
throw new IllegalArgumentException("Invalid mimeType: " + mimeType);
}
- return DIRECTORY + '/' + title + extension;
+ return (new File(directory, title + extension)).getAbsolutePath();
}
/**
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index 6c35c5358..3bcbf93d6 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.os.Debug;
import com.android.camera.MediaSaverImpl;
+import com.android.camera.Storage;
import com.android.camera.debug.LogHelper;
import com.android.camera.processing.ProcessingServiceManager;
import com.android.camera.remote.RemoteShutterListener;
@@ -30,6 +31,7 @@ import com.android.camera.session.CaptureSessionManagerImpl;
import com.android.camera.session.PlaceholderManager;
import com.android.camera.session.SessionStorageManager;
import com.android.camera.session.SessionStorageManagerImpl;
+import com.android.camera.session.StackSaverFactory;
import com.android.camera.settings.SettingsManager;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.RemoteShutterHelper;
@@ -52,6 +54,7 @@ public class CameraApp extends Application implements CameraServices {
private MediaSaver mMediaSaver;
private CaptureSessionManager mSessionManager;
+ private StackSaverFactory mStackSaverFactory;
private SessionStorageManager mSessionStorageManager;
private MemoryManagerImpl mMemoryManager;
private PlaceholderManager mPlaceHolderManager;
@@ -81,8 +84,10 @@ public class CameraApp extends Application implements CameraServices {
mMediaSaver = new MediaSaverImpl();
mPlaceHolderManager = new PlaceholderManager(context);
mSessionStorageManager = SessionStorageManagerImpl.create(this);
+
+ mStackSaverFactory = new StackSaverFactory(Storage.DIRECTORY, getContentResolver());
mSessionManager = new CaptureSessionManagerImpl(mMediaSaver, getContentResolver(),
- mPlaceHolderManager, mSessionStorageManager);
+ mPlaceHolderManager, mSessionStorageManager, mStackSaverFactory);
mMemoryManager = MemoryManagerImpl.create(getApplicationContext(), mMediaSaver);
mRemoteShutterListener = RemoteShutterHelper.create(this);
mSettingsManager = new SettingsManager(this);
diff --git a/src/com/android/camera/burst/BurstFacade.java b/src/com/android/camera/burst/BurstFacade.java
index e57420d2f..93f96bdb3 100644
--- a/src/com/android/camera/burst/BurstFacade.java
+++ b/src/com/android/camera/burst/BurstFacade.java
@@ -16,23 +16,17 @@
package com.android.camera.burst;
-import android.content.ContentResolver;
-
import com.android.camera.gl.FrameDistributor.FrameConsumer;
import com.android.camera.one.OneCamera;
+import com.android.camera.session.CaptureSession;
+
+import java.io.File;
/**
* Facade for {@link BurstController} provides a simpler interface.
*/
public interface BurstFacade {
/**
- * Set the content resolver to be updated when saving burst results.
- *
- * @param contentResolver to be updated when burst results are saved.
- */
- public void setContentResolver(ContentResolver contentResolver);
-
- /**
* Called when camera is available.
*
* @param camera an instance of {@link OneCamera} that is used to start or
@@ -52,8 +46,11 @@ public interface BurstFacade {
/**
* Starts the burst.
+ *
+ * @param captureSession the capture session to use for this burst.
+ * @param tempSessionDirectory a directory in which temporary data can be put.
*/
- public void startBurst();
+ public void startBurst(CaptureSession captureSession, File tempSessionDirectory);
/**
* @return Whether this burst controller is ready to start another burst.
diff --git a/src/com/android/camera/burst/BurstFacadeFactory.java b/src/com/android/camera/burst/BurstFacadeFactory.java
index 5a79d4531..95a16da51 100644
--- a/src/com/android/camera/burst/BurstFacadeFactory.java
+++ b/src/com/android/camera/burst/BurstFacadeFactory.java
@@ -16,15 +16,13 @@
package com.android.camera.burst;
-import android.content.ContentResolver;
+import android.content.Context;
-import com.android.camera.app.AppController;
-import com.android.camera.app.LocationManager;
-import com.android.camera.app.MediaSaver;
import com.android.camera.app.OrientationManager;
import com.android.camera.gl.FrameDistributor;
import com.android.camera.gl.FrameDistributor.FrameConsumer;
import com.android.camera.one.OneCamera;
+import com.android.camera.session.CaptureSession;
import java.io.File;
@@ -32,39 +30,44 @@ import java.io.File;
* Factory for creating burst manager objects.
*/
public class BurstFacadeFactory {
- private BurstFacadeFactory() {/* cannot be instantiated */}
+ private BurstFacadeFactory() {
+ /* cannot be instantiated */
+ }
/**
* An empty burst manager that is instantiated when burst is not supported.
*/
private static class BurstFacadeStub implements BurstFacade {
@Override
- public void setContentResolver(ContentResolver contentResolver) {}
-
- @Override
- public void onCameraAttached(OneCamera camera) {}
+ public void onCameraAttached(OneCamera camera) {
+ }
@Override
- public void onCameraDetached() {}
+ public void onCameraDetached() {
+ }
@Override
public FrameConsumer getPreviewFrameConsumer() {
return new FrameConsumer() {
@Override
- public void onStop() {}
+ public void onStop() {
+ }
@Override
- public void onStart() {}
+ public void onStart() {
+ }
@Override
public void onNewFrameAvailable(FrameDistributor frameDistributor,
- long timestampNs) {}
+ long timestampNs) {
+ }
};
}
@Override
- public void startBurst() {}
+ public void startBurst(CaptureSession captureSession, File tempSessionDirectory) {
+ }
@Override
public boolean isReady() {
@@ -80,23 +83,16 @@ public class BurstFacadeFactory {
/**
* Creates and returns an instance of {@link BurstFacade}
*
- * @param appController the app level controller for controlling the shutter
- * button.
- * @param mediaSaver the {@link MediaSaver} instance for saving results of
- * burst.
- * @param locationManager for querying location of burst.
+ * @param appContext the Android application context which is passes through
+ * to the burst controller.
* @param orientationManager for querying orientation of burst.
- * @param debugDataDir the debug directory to use for burst.
+ * @param readyStateListener gets called when the ready state of Burst
+ * changes.
*/
- public static BurstFacade create(AppController appController,
- MediaSaver mediaSaver,
- LocationManager locationManager,
- OrientationManager orientationManager,
- File debugDataDir) {
+ public static BurstFacade create(Context appContext, OrientationManager orientationManager,
+ BurstReadyStateChangeListener readyStateListener) {
if (BurstControllerImpl.isBurstModeSupported()) {
- return new BurstFacadeImpl(appController, mediaSaver,
- locationManager, orientationManager,
- debugDataDir);
+ return new BurstFacadeImpl(appContext, orientationManager, readyStateListener);
} else {
// Burst is not supported return a stub instance.
return new BurstFacadeStub();
diff --git a/src/com/android/camera/burst/BurstFacadeImpl.java b/src/com/android/camera/burst/BurstFacadeImpl.java
index 9206c6e71..6c591334d 100644
--- a/src/com/android/camera/burst/BurstFacadeImpl.java
+++ b/src/com/android/camera/burst/BurstFacadeImpl.java
@@ -14,15 +14,11 @@
package com.android.camera.burst;
-import android.content.ContentResolver;
+import android.content.Context;
import android.location.Location;
-import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
-import com.android.camera.app.AppController;
-import com.android.camera.app.LocationManager;
-import com.android.camera.app.MediaSaver;
import com.android.camera.app.OrientationManager;
import com.android.camera.app.OrientationManager.DeviceOrientation;
import com.android.camera.app.OrientationManager.OnOrientationChangeListener;
@@ -36,6 +32,7 @@ import com.android.camera.one.OneCamera.BurstParameters;
import com.android.camera.one.OneCamera.BurstResultsCallback;
import com.android.camera.one.OneCamera.Facing;
import com.android.camera.session.CaptureSession;
+import com.android.camera.session.StackSaver;
import java.io.File;
import java.util.ArrayList;
@@ -47,13 +44,10 @@ import java.util.concurrent.atomic.AtomicReference;
/**
* Helper to manage burst, listen to burst results and saves media items.
* <p/>
- * The UI feedback is rudimentary in form of a toast that is displayed on start of the
- * burst and when artifacts are saved.
- *
- * TODO: Move functionality of saving burst items to a
- * {@link com.android.camera.processing.ProcessingTask} and change API to use
- * {@link com.android.camera.processing.ProcessingService}.
- *
+ * The UI feedback is rudimentary in form of a toast that is displayed on start
+ * of the burst and when artifacts are saved. TODO: Move functionality of saving
+ * burst items to a {@link com.android.camera.processing.ProcessingTask} and
+ * change API to use {@link com.android.camera.processing.ProcessingService}.
* TODO: Hook UI to the listener.
*/
class BurstFacadeImpl implements BurstFacade {
@@ -76,12 +70,6 @@ class BurstFacadeImpl implements BurstFacade {
* timestamp
*/
private static final String MEDIA_ITEM_FILENAME_FORMAT_STRING = "Burst_%s_%d_%d_%d";
- /**
- * The title of Capture session for Burst.
- * <p/>
- * Title is of format: Burst_timestamp
- */
- private static final String BURST_TITLE_FORMAT_STRING = "Burst_%d";
private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
@@ -92,30 +80,22 @@ class BurstFacadeImpl implements BurstFacade {
private final Object mStartStopBurstLock = new Object();
private final BurstController mBurstController;
- private final AppController mAppController;
- private final File mDebugDataDir;
-
- private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
- new MediaSaver.OnMediaSavedListener() {
- @Override
- public void onMediaSaved(Uri uri) {
- if (uri != null) {
- mAppController.notifyNewMedia(uri);
- }
- }
- };
-
/**
* Results callback that is invoked by camera when results are available.
*/
- private final BurstResultsCallback
- mBurstExtractsResultsCallback = new BurstResultsCallback() {
- @Override
- public void onBurstComplete(ResultsAccessor resultAccessor) {
- // Pass the results accessor to the controller.
- mBurstController.stopBurst(resultAccessor);
- }
- };
+ private final BurstResultsCallback mBurstExtractsResultsCallback = new BurstResultsCallback() {
+ @Override
+ public void onBurstComplete(ResultsAccessor resultAccessor) {
+ // Pass the results accessor to the controller.
+ mBurstController.stopBurst(resultAccessor);
+ }
+ };
+
+ /** A stack saver for the outstanding burst request. */
+ private StackSaver mActiveStackSaver;
+
+ /** The image orientation of the active burst. */
+ private int mImageOrientation;
/**
* Listener for burst controller. Saves the results and interacts with the
@@ -130,14 +110,18 @@ class BurstFacadeImpl implements BurstFacade {
@Override
public void onBurstError(Exception error) {
Log.e(TAG, "Exception while running the burst" + error);
+ mActiveStackSaver = null;
+
+ // Transition to idle state, ready to take another burst.
mBurstModuleState.set(BurstModuleState.IDLE);
+
// Re-enable the shutter button.
- mAppController.setShutterEnabled(true);
+ mReadyStateListener.onBurstReadyStateChanged(true);
}
@Override
public void onBurstCompleted(BurstResult burstResult) {
- saveBurstResultAndEnableShutterButton(burstResult);
+ saveBurstResultAndEnableShutterButton(burstResult, mActiveStackSaver);
}
@Override
@@ -147,8 +131,8 @@ class BurstFacadeImpl implements BurstFacade {
}
};
- private final OrientationManager.OnOrientationChangeListener
- mOrientationChangeListener = new OnOrientationChangeListener() {
+ private final OrientationManager.OnOrientationChangeListener mOrientationChangeListener =
+ new OnOrientationChangeListener() {
@Override
public void onOrientationChanged(OrientationManager orientationManager,
DeviceOrientation orientation) {
@@ -160,45 +144,22 @@ class BurstFacadeImpl implements BurstFacade {
/** Camera instance for starting/stopping the burst. */
private OneCamera mCamera;
- private final MediaSaver mMediaSaver;
- private final LocationManager mLocationManager;
private final OrientationManager mOrientationManager;
- private volatile ContentResolver mContentResolver;
+ private final BurstReadyStateChangeListener mReadyStateListener;
/**
* Create a new BurstManagerImpl instance.
*
- * @param appController the app level controller for controlling the shutter
- * button.
- * @param mediaSaver the {@link MediaSaver} instance for saving results of
- * burst.
- * @param locationManager for querying location of burst.
- * @param orientationManager for querying orientation of burst.
- * @param debugDataDir the debug directory to use for burst.
+ * @param appContext the Android application context.
+ * @param orientationManager orientationManager
+ * @param readyStateListener gets called when the ready state of Burst
+ * changes.
*/
- public BurstFacadeImpl(AppController appController,
- MediaSaver mediaSaver,
- LocationManager locationManager,
- OrientationManager orientationManager,
- File debugDataDir) {
- mAppController = appController;
- mMediaSaver = mediaSaver;
- mLocationManager = locationManager;
- mDebugDataDir = debugDataDir;
+ public BurstFacadeImpl(Context appContext, OrientationManager orientationManager,
+ BurstReadyStateChangeListener readyStateListener) {
mOrientationManager = orientationManager;
- mBurstController = new BurstControllerImpl(
- mAppController.getAndroidContext(),
- mBurstResultsListener);
- }
-
- /**
- * Set the content resolver to be updated when saving burst results.
- *
- * @param contentResolver to be updated when burst results are saved.
- */
- @Override
- public void setContentResolver(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ mBurstController = new BurstControllerImpl(appContext, mBurstResultsListener);
+ mReadyStateListener = readyStateListener;
}
@Override
@@ -221,31 +182,13 @@ class BurstFacadeImpl implements BurstFacade {
}
@Override
- public void startBurst() {
- startBurstImpl();
- }
-
- @Override
- public boolean isReady() {
- return mBurstModuleState.get() == BurstModuleState.IDLE;
- }
-
- private void startBurstImpl() {
+ public void startBurst(CaptureSession captureSession, File tempSessionDirectory) {
synchronized (mStartStopBurstLock) {
if (mCamera != null &&
mBurstModuleState.compareAndSet(BurstModuleState.IDLE,
BurstModuleState.RUNNING)) {
- // TODO: Use localized strings everywhere.
Log.d(TAG, "Starting burst.");
- Location location = mLocationManager.getCurrentLocation();
-
- // Set up the capture session.
- long sessionTime = System.currentTimeMillis();
- String title = String.format(BURST_TITLE_FORMAT_STRING, sessionTime);
-
- // TODO: Fix the capture session and use it for saving
- // intermediate results.
- CaptureSession session = null;
+ Location location = captureSession.getLocation();
mOrientationManager.addOnOrientationChangeListener(mOrientationChangeListener);
int orientation = mOrientationManager.getDeviceOrientation().getDegrees();
@@ -253,19 +196,32 @@ class BurstFacadeImpl implements BurstFacade {
mCamera.getDirection() == Facing.FRONT);
BurstConfiguration burstConfig = mBurstController.startBurst();
- BurstParameters params = new BurstParameters(title, orientation, location,
- mDebugDataDir, burstConfig, mBurstExtractsResultsCallback);
+ BurstParameters params = new BurstParameters(captureSession.getTitle(),
+ orientation, location, tempSessionDirectory, burstConfig,
+ mBurstExtractsResultsCallback);
+
+ if (mActiveStackSaver != null) {
+ throw new IllegalStateException(
+ "Cannot start a burst while another is in progress.");
+ }
+ mActiveStackSaver = captureSession.getStackSaver();
+ mImageOrientation = mOrientationManager.getDeviceOrientation().getDegrees();
// Disable the shutter button.
- mAppController.setShutterEnabled(false);
+ mReadyStateListener.onBurstReadyStateChanged(false);
- // start burst.
- mCamera.startBurst(params, session);
+ // Start burst.
+ mCamera.startBurst(params, captureSession);
}
}
}
@Override
+ public boolean isReady() {
+ return mBurstModuleState.get() == BurstModuleState.IDLE;
+ }
+
+ @Override
public boolean stopBurst() {
synchronized (mStartStopBurstLock) {
boolean wasStopped = false;
@@ -286,7 +242,8 @@ class BurstFacadeImpl implements BurstFacade {
*
* @param burstResult the result of the burst.
*/
- private void saveBurstResultAndEnableShutterButton(final BurstResult burstResult) {
+ private void saveBurstResultAndEnableShutterButton(final BurstResult burstResult,
+ final StackSaver stackSaver) {
Log.i(TAG, "Saving results of of the burst.");
AsyncTask<Void, String, Void> saveTask =
@@ -295,7 +252,7 @@ class BurstFacadeImpl implements BurstFacade {
protected Void doInBackground(Void... arg0) {
for (String artifactType : burstResult.getTypes()) {
publishProgress(artifactType);
- saveArtifacts(burstResult, artifactType);
+ saveArtifacts(stackSaver, burstResult, artifactType);
}
return null;
}
@@ -304,7 +261,7 @@ class BurstFacadeImpl implements BurstFacade {
protected void onPostExecute(Void result) {
mBurstModuleState.set(BurstModuleState.IDLE);
// Re-enable the shutter button.
- mAppController.setShutterEnabled(true);
+ mReadyStateListener.onBurstReadyStateChanged(true);
}
@Override
@@ -318,27 +275,24 @@ class BurstFacadeImpl implements BurstFacade {
/**
* Save individual artifacts for bursts.
*/
- private void saveArtifacts(final BurstResult burstResult,
+ private void saveArtifacts(final StackSaver stackSaver, final BurstResult burstResult,
final String artifactType) {
List<BurstArtifact> artifactList = burstResult.getArtifactsByType(artifactType);
for (int artifactIndex = 0; artifactIndex < artifactList.size(); artifactIndex++) {
List<BurstMediaItem> mediaItems = artifactList.get(artifactIndex).getMediaItems();
for (int index = 0; index < mediaItems.size(); index++) {
- saveBurstMediaItem(mediaItems.get(index),
+ saveBurstMediaItem(stackSaver, mediaItems.get(index),
artifactType, artifactIndex + 1, index + 1);
}
}
}
- private void saveBurstMediaItem(BurstMediaItem mediaItem,
- String artifactType,
- int artifactIndex,
- int index) {
+ private void saveBurstMediaItem(StackSaver stackSaver, BurstMediaItem mediaItem,
+ String artifactType, int artifactIndex, int index) {
long timestamp = mediaItem.getTimestamp();
- final String mimeType = mediaItem.getMimeType();
- final String title = String.format(MEDIA_ITEM_FILENAME_FORMAT_STRING,
+ String title = String.format(MEDIA_ITEM_FILENAME_FORMAT_STRING,
artifactType, artifactIndex, index, timestamp);
- byte[] data = mediaItem.getData();
+ String mimeType = mediaItem.getMimeType();
ExifInterface exif = null;
if (LocalData.MIME_TYPE_JPEG.equals(mimeType)) {
exif = new ExifInterface();
@@ -346,26 +300,22 @@ class BurstFacadeImpl implements BurstFacade {
ExifInterface.TAG_DATE_TIME,
timestamp,
UTC_TIMEZONE);
-
}
- mMediaSaver.addImage(data,
+
+ stackSaver.saveStackedImage(mediaItem.getData(),
title,
- timestamp,
- mLocationManager.getCurrentLocation(),
mediaItem.getWidth(),
mediaItem.getHeight(),
- mOrientationManager.getDeviceOrientation().getDegrees(),
- exif, // exif,
- mOnMediaSavedListener,
- mContentResolver,
+ mImageOrientation,
+ exif,
+ timestamp,
mimeType);
}
private void logArtifactCount(final Map<String, Integer> artifactTypeCount) {
final String prefix = "Finished burst. Creating ";
List<String> artifactDescription = new ArrayList<String>();
- for (Map.Entry<String, Integer> entry :
- artifactTypeCount.entrySet()) {
+ for (Map.Entry<String, Integer> entry : artifactTypeCount.entrySet()) {
artifactDescription.add(entry.getValue() + " " + entry.getKey());
}
diff --git a/src/com/android/camera/burst/BurstReadyStateChangeListener.java b/src/com/android/camera/burst/BurstReadyStateChangeListener.java
new file mode 100644
index 000000000..75d26f6ec
--- /dev/null
+++ b/src/com/android/camera/burst/BurstReadyStateChangeListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.burst;
+
+/**
+ * Classes implementing this interface can listen to the ready-state of Burst
+ * changing.
+ */
+public interface BurstReadyStateChangeListener {
+ /**
+ * Called when the ready-state of Burst changes.
+ *
+ * @param ready whether Burst is ready
+ */
+ public void onBurstReadyStateChanged(boolean ready);
+}
diff --git a/src/com/android/camera/burst/ToastingBurstFacadeDecorator.java b/src/com/android/camera/burst/ToastingBurstFacadeDecorator.java
index 4612a8bcd..9962d56d7 100644
--- a/src/com/android/camera/burst/ToastingBurstFacadeDecorator.java
+++ b/src/com/android/camera/burst/ToastingBurstFacadeDecorator.java
@@ -16,12 +16,14 @@
package com.android.camera.burst;
-import android.content.ContentResolver;
import android.content.Context;
import android.widget.Toast;
import com.android.camera.gl.FrameDistributor.FrameConsumer;
import com.android.camera.one.OneCamera;
+import com.android.camera.session.CaptureSession;
+
+import java.io.File;
/**
* A simle decorator for a {@link BurstFacade} that shows toasts for when a
@@ -68,11 +70,6 @@ public class ToastingBurstFacadeDecorator implements BurstFacade {
}
@Override
- public void setContentResolver(ContentResolver contentResolver) {
- mBurstFacade.setContentResolver(contentResolver);
- }
-
- @Override
public void onCameraAttached(OneCamera camera) {
mBurstFacade.onCameraAttached(camera);
}
@@ -88,9 +85,9 @@ public class ToastingBurstFacadeDecorator implements BurstFacade {
}
@Override
- public void startBurst() {
+ public void startBurst(CaptureSession captureSession, File tempSessionDirectory) {
mToaster.showToastBurstStarted();
- mBurstFacade.startBurst();
+ mBurstFacade.startBurst(captureSession, tempSessionDirectory);
}
@Override
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index 99561088f..2fb16fdf3 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -181,8 +181,28 @@ public abstract class LocalMediaData implements LocalData {
@Override
public boolean delete(Context context) {
- File f = new File(mPath);
- return f.delete();
+ File fileToDelete = new File(mPath);
+ boolean deletionSucceeded = fileToDelete.delete();
+ deleteSubDirIfEmpty(fileToDelete.getParentFile());
+ return deletionSucceeded;
+ }
+
+ private void deleteSubDirIfEmpty(File directory) {
+ // Make sure 'directory' refers to a valid existing empty directory.
+ if (!directory.exists() || !directory.isDirectory() || directory.list().length != 0) {
+ return;
+ }
+
+ // Check if this is a 'Camera' sub-directory.
+ String cameraPathStr = Storage.DIRECTORY_FILE.getAbsolutePath();
+ String fileParentPathStr = directory.getParentFile().getAbsolutePath();
+ Log.d(TAG, "CameraPathStr: " + cameraPathStr + " fileParentPathStr: " + fileParentPathStr);
+
+ // Delete the directory if it's an empty sub-directory of the Camera
+ // directory.
+ if (fileParentPathStr.equals(cameraPathStr)) {
+ directory.delete();
+ }
}
@Override
diff --git a/src/com/android/camera/session/CaptureSession.java b/src/com/android/camera/session/CaptureSession.java
index 7b3c7f0cf..25a6dcb79 100644
--- a/src/com/android/camera/session/CaptureSession.java
+++ b/src/com/android/camera/session/CaptureSession.java
@@ -121,6 +121,13 @@ public interface CaptureSession {
ExifInterface exif, OnMediaSavedListener listener);
/**
+ * Will create and return a {@link StackSaver} for saving out a number of
+ * media items to a stack. The name of the stack will be the title of this
+ * capture session.
+ */
+ public StackSaver getStackSaver();
+
+ /**
* Finishes the session.
*/
public void finish();
@@ -138,16 +145,15 @@ public interface CaptureSession {
public String getTempOutputPath();
/**
- * Returns the URI to the final output of this session. This is only available
- * after startSession has been called.
+ * Returns the URI to the final output of this session. This is only
+ * available after startSession has been called.
*/
public Uri getUri();
/**
* Returns the Content URI to the final output of this session. This is only
- * available if the session has been finished.
- *
- * Returns null if it has not been finished.
+ * available if the session has been finished. Returns null if it has not
+ * been finished.
*/
public Uri getContentUri();
diff --git a/src/com/android/camera/session/CaptureSessionManager.java b/src/com/android/camera/session/CaptureSessionManager.java
index d76046fbc..de9cf0946 100644
--- a/src/com/android/camera/session/CaptureSessionManager.java
+++ b/src/com/android/camera/session/CaptureSessionManager.java
@@ -19,9 +19,6 @@ package com.android.camera.session;
import android.location.Location;
import android.net.Uri;
-import com.android.camera.app.MediaSaver.OnMediaSavedListener;
-import com.android.camera.exif.ExifInterface;
-
import java.io.File;
import java.io.IOException;
@@ -89,22 +86,6 @@ public interface CaptureSessionManager {
CaptureSession getSession(Uri sessionUri);
/**
- * Save an image without creating a session that includes progress.
- *
- * @param data the image data to be saved.
- * @param title the title of the media item.
- * @param date the timestamp of the capture.
- * @param loc the capture location.
- * @param width the width of the captured image.
- * @param height the height of the captured image.
- * @param orientation the orientation of the captured image.
- * @param exif the EXIF data of the captured image.
- * @param listener called when saving is complete.
- */
- void saveImage(byte[] data, String title, long date, Location loc, int width, int height,
- int orientation, ExifInterface exif, OnMediaSavedListener listener);
-
- /**
* Add a listener to be informed about capture session updates.
* <p>
* Note: It is guaranteed that the callbacks will happen on the main thread,
diff --git a/src/com/android/camera/session/CaptureSessionManagerImpl.java b/src/com/android/camera/session/CaptureSessionManagerImpl.java
index 84bd693d4..d6849940e 100644
--- a/src/com/android/camera/session/CaptureSessionManagerImpl.java
+++ b/src/com/android/camera/session/CaptureSessionManagerImpl.java
@@ -71,6 +71,9 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
*/
private final String mTempOutputPath;
+ /** Saver that is used to store a stack of images. */
+ private final StackSaver mStackSaver;
+
/**
* Creates a new {@link CaptureSession}.
*
@@ -79,11 +82,13 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
* (since epoch).
* @param location the location of this session, used for media store.
*/
- private CaptureSessionImpl(String title, long sessionStartMillis, Location location) {
+ private CaptureSessionImpl(String title, long sessionStartMillis, Location location,
+ StackSaver stackSaver) {
mTitle = title;
mSessionStartMillis = sessionStartMillis;
mLocation = location;
mTempOutputPath = createTempOutputPath(mTitle);
+ mStackSaver = stackSaver;
}
@Override
@@ -187,6 +192,11 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
}
@Override
+ public StackSaver getStackSaver() {
+ return mStackSaver;
+ }
+
+ @Override
public void finish() {
if (mPlaceHolderSession == null) {
throw new IllegalStateException(
@@ -350,9 +360,10 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
}
private final MediaSaver mMediaSaver;
+ private final ContentResolver mContentResolver;
private final PlaceholderManager mPlaceholderManager;
private final SessionStorageManager mSessionStorageManager;
- private final ContentResolver mContentResolver;
+ private final StackSaverFactory mStackSaverFactory;
/** Failed session messages. Uri -> message. */
private final HashMap<Uri, CharSequence> mFailedSessionMessages =
@@ -380,22 +391,25 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
* temporary session data
*/
public CaptureSessionManagerImpl(MediaSaver mediaSaver, ContentResolver contentResolver,
- PlaceholderManager placeholderManager, SessionStorageManager sessionStorageManager) {
+ PlaceholderManager placeholderManager, SessionStorageManager sessionStorageManager,
+ StackSaverFactory stackSaverProvider) {
mSessions = new HashMap<String, CaptureSession>();
mMediaSaver = mediaSaver;
mContentResolver = contentResolver;
mPlaceholderManager = placeholderManager;
mSessionStorageManager = sessionStorageManager;
+ mStackSaverFactory = stackSaverProvider;
}
@Override
public CaptureSession createNewSession(String title, long sessionStartTime, Location location) {
- return new CaptureSessionImpl(title, sessionStartTime, location);
+ return new CaptureSessionImpl(title, sessionStartTime, location, mStackSaverFactory.create(
+ title, location));
}
@Override
public CaptureSession createSession() {
- return new CaptureSessionImpl(null, System.currentTimeMillis(), null);
+ return new CaptureSessionImpl(null, System.currentTimeMillis(), null, null);
}
@Override
@@ -413,14 +427,6 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
}
@Override
- public void saveImage(byte[] data, String title, long date, Location loc,
- int width, int height, int orientation, ExifInterface exif,
- OnMediaSavedListener listener) {
- mMediaSaver.addImage(data, title, date, loc, width, height, orientation, exif,
- listener, mContentResolver);
- }
-
- @Override
public void addSessionListener(SessionListener listener) {
synchronized (mTaskListeners) {
mTaskListeners.add(listener);
diff --git a/src/com/android/camera/session/StackSaver.java b/src/com/android/camera/session/StackSaver.java
new file mode 100644
index 000000000..a65e9c9db
--- /dev/null
+++ b/src/com/android/camera/session/StackSaver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.session;
+
+import android.net.Uri;
+
+import com.android.camera.exif.ExifInterface;
+
+/**
+ * Used to store images that belong to the same stack.
+ */
+public interface StackSaver {
+
+ /**
+ * Save a single image from a stack/burst.
+ *
+ * @param data the bytes to write to disk
+ * @param title the title of this image, without the file extension
+ * @param width the width of the image in pixels
+ * @param height the height of the image in pixels
+ * @param imageOrientation the image orientation in degrees
+ * @param exif the exif data for the image
+ * @param captureTimeEpoch the capture time in millis since epoch
+ * @param mimeType the mime type of the image
+ * @return The Uri of the saved image, or null, of the image could not be
+ * saved.
+ */
+ public Uri saveStackedImage(byte[] data, String title, int width, int height,
+ int imageOrientation, ExifInterface exif, long captureTimeEpoch, String mimeType);
+}
diff --git a/src/com/android/camera/session/StackSaverFactory.java b/src/com/android/camera/session/StackSaverFactory.java
new file mode 100644
index 000000000..272efecd1
--- /dev/null
+++ b/src/com/android/camera/session/StackSaverFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.session;
+
+import android.content.ContentResolver;
+import android.location.Location;
+
+import java.io.File;
+
+/**
+ * Creates {@link StackSaver} instances.
+ */
+public class StackSaverFactory {
+ private final String mCameraDirectory;
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Create a new stack saver factory.
+ *
+ * @param cameraDirectory the directory in which the camera stores images.
+ * @param contentResolver the Android content resolver used to include
+ * images into the media store.
+ */
+ public StackSaverFactory(String cameraDirectory,
+ ContentResolver contentResolver) {
+ mCameraDirectory = cameraDirectory;
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Creates a new StackSaver.
+ *
+ * @param mTitle the title of this stack session.
+ * @param location the GPS location that the media in this session was
+ * created at.
+ * @return A StackSaver that is set up to save images in a stacked location.
+ */
+ public StackSaver create(String mTitle, Location location) {
+ return new StackSaverImpl(new File(mCameraDirectory, mTitle), location, mContentResolver);
+ }
+}
diff --git a/src/com/android/camera/session/StackSaverImpl.java b/src/com/android/camera/session/StackSaverImpl.java
new file mode 100644
index 000000000..c7534edca
--- /dev/null
+++ b/src/com/android/camera/session/StackSaverImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.session;
+
+import android.content.ContentResolver;
+import android.location.Location;
+import android.net.Uri;
+
+import com.android.camera.Storage;
+import com.android.camera.debug.Log;
+import com.android.camera.exif.ExifInterface;
+
+import java.io.File;
+
+/**
+ * Default implementation of the {@link StackSaver} interface. It creates a
+ * directory for each stack and stores images and if needed also metadata inside
+ * that directory.
+ * <p>
+ * TODO: Add placeholder support for stack writing.
+ * </p>
+ */
+public class StackSaverImpl implements StackSaver {
+ private static final Log.Tag TAG = new Log.Tag("StackSaverImpl");
+ /** The stacked images are stored in this directory. */
+ private final File mStackDirectory;
+ private final ContentResolver mContentResolver;
+ private final Location mGpsLocation;
+
+ /**
+ * Instantiate a new stack saver implementation.
+ *
+ * @param stackDirectory the directory, which either exists already or can
+ * be created, into which images belonging to this stack are
+ * belonging.
+ * @param gpsLocation the GPS location to attach to all stacked images.
+ * @param contentResolver content resolver for storing the data in media
+ * store. TODO: Replace with a media storage storer that can be
+ * mocked out in tests.
+ */
+ public StackSaverImpl(File stackDirectory, Location gpsLocation, ContentResolver contentResolver) {
+ mStackDirectory = stackDirectory;
+ mGpsLocation = gpsLocation;
+ mContentResolver = contentResolver;
+ }
+
+ @Override
+ public Uri saveStackedImage(byte[] data, String title, int width, int height,
+ int imageOrientation, ExifInterface exif, long captureTimeEpoch, String mimeType) {
+ if (exif != null) {
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, imageOrientation));
+ }
+
+ String filePath =
+ Storage.generateFilepath(mStackDirectory.getAbsolutePath(), title, mimeType);
+ Log.d(TAG, "Saving using stack image saver: " + filePath);
+
+ long fileLength = Storage.writeFile(filePath, data, exif);
+ if (fileLength >= 0) {
+ return Storage.addImageToMediaStore(mContentResolver, title, captureTimeEpoch,
+ mGpsLocation, imageOrientation, fileLength, filePath, width, height, mimeType);
+ }
+ return null;
+ }
+}