diff options
author | Sascha Haeberling <haeberling@google.com> | 2014-12-17 01:43:21 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-12-17 01:43:22 +0000 |
commit | b79279f49e365cc6da75c1bc44b4c786036d0aa7 (patch) | |
tree | 58df3ae5fc952cd87bfa14ac7bd14dc4cebe13c5 /src | |
parent | b530f7d637117e7c014d72d7692579d9dda6caa9 (diff) | |
parent | 24069e7a9cc9b4f908f18a71301285ccf5e164f6 (diff) | |
download | android_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')
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; + } +} |