blob: 6c591334d241dba0e12cefd7130c028d81a18105 [file] [log] [blame]
/*
* 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;
import android.content.Context;
import android.location.Location;
import android.os.AsyncTask;
import android.text.TextUtils;
import com.android.camera.app.OrientationManager;
import com.android.camera.app.OrientationManager.DeviceOrientation;
import com.android.camera.app.OrientationManager.OnOrientationChangeListener;
import com.android.camera.data.LocalData;
import com.android.camera.debug.Log;
import com.android.camera.debug.Log.Tag;
import com.android.camera.exif.ExifInterface;
import com.android.camera.gl.FrameDistributor.FrameConsumer;
import com.android.camera.one.OneCamera;
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;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
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}.
* TODO: Hook UI to the listener.
*/
class BurstFacadeImpl implements BurstFacade {
/**
* The state of the burst module.
*/
private static enum BurstModuleState {
IDLE,
RUNNING,
STOPPING
}
private static final Tag TAG = new Tag("BurstFacadeImpl");
/**
* The format string of burst media item file name (without extension).
* <p/>
* An media item file name has the following format: "Burst_" + artifact
* type + "_" + index of artifact + "_" + index of media item + "_" +
* timestamp
*/
private static final String MEDIA_ITEM_FILENAME_FORMAT_STRING = "Burst_%s_%d_%d_%d";
private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
private final AtomicReference<BurstModuleState> mBurstModuleState =
new AtomicReference<BurstModuleState>(BurstModuleState.IDLE);
/** Lock to protect starting and stopping of the burst. */
private final Object mStartStopBurstLock = new Object();
private final BurstController mBurstController;
/**
* 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);
}
};
/** 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
* UI.
*/
private final BurstResultsListener mBurstResultsListener =
new BurstResultsListener() {
@Override
public void onBurstStarted() {
}
@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.
mReadyStateListener.onBurstReadyStateChanged(true);
}
@Override
public void onBurstCompleted(BurstResult burstResult) {
saveBurstResultAndEnableShutterButton(burstResult, mActiveStackSaver);
}
@Override
public void onArtifactCountAvailable(
final Map<String, Integer> artifactTypeCount) {
logArtifactCount(artifactTypeCount);
}
};
private final OrientationManager.OnOrientationChangeListener mOrientationChangeListener =
new OnOrientationChangeListener() {
@Override
public void onOrientationChanged(OrientationManager orientationManager,
DeviceOrientation orientation) {
mBurstController.onOrientationChanged(orientation.getDegrees(),
mCamera.getDirection() == Facing.FRONT);
}
};
/** Camera instance for starting/stopping the burst. */
private OneCamera mCamera;
private final OrientationManager mOrientationManager;
private final BurstReadyStateChangeListener mReadyStateListener;
/**
* Create a new BurstManagerImpl instance.
*
* @param appContext the Android application context.
* @param orientationManager orientationManager
* @param readyStateListener gets called when the ready state of Burst
* changes.
*/
public BurstFacadeImpl(Context appContext, OrientationManager orientationManager,
BurstReadyStateChangeListener readyStateListener) {
mOrientationManager = orientationManager;
mBurstController = new BurstControllerImpl(appContext, mBurstResultsListener);
mReadyStateListener = readyStateListener;
}
@Override
public void onCameraAttached(OneCamera camera) {
synchronized (mStartStopBurstLock) {
mCamera = camera;
}
}
@Override
public void onCameraDetached() {
synchronized (mStartStopBurstLock) {
mCamera = null;
}
}
@Override
public FrameConsumer getPreviewFrameConsumer() {
return mBurstController.getPreviewFrameConsumer();
}
@Override
public void startBurst(CaptureSession captureSession, File tempSessionDirectory) {
synchronized (mStartStopBurstLock) {
if (mCamera != null &&
mBurstModuleState.compareAndSet(BurstModuleState.IDLE,
BurstModuleState.RUNNING)) {
Log.d(TAG, "Starting burst.");
Location location = captureSession.getLocation();
mOrientationManager.addOnOrientationChangeListener(mOrientationChangeListener);
int orientation = mOrientationManager.getDeviceOrientation().getDegrees();
mBurstController.onOrientationChanged(orientation,
mCamera.getDirection() == Facing.FRONT);
BurstConfiguration burstConfig = mBurstController.startBurst();
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.
mReadyStateListener.onBurstReadyStateChanged(false);
// Start burst.
mCamera.startBurst(params, captureSession);
}
}
}
@Override
public boolean isReady() {
return mBurstModuleState.get() == BurstModuleState.IDLE;
}
@Override
public boolean stopBurst() {
synchronized (mStartStopBurstLock) {
boolean wasStopped = false;
if (mBurstModuleState.compareAndSet(BurstModuleState.RUNNING,
BurstModuleState.STOPPING)) {
mOrientationManager.removeOnOrientationChangeListener(mOrientationChangeListener);
if (mCamera != null) {
mCamera.stopBurst();
wasStopped = true;
}
}
return wasStopped;
}
}
/**
* Saves the burst result and on completion re-enables the shutter button.
*
* @param burstResult the result of the burst.
*/
private void saveBurstResultAndEnableShutterButton(final BurstResult burstResult,
final StackSaver stackSaver) {
Log.i(TAG, "Saving results of of the burst.");
AsyncTask<Void, String, Void> saveTask =
new AsyncTask<Void, String, Void>() {
@Override
protected Void doInBackground(Void... arg0) {
for (String artifactType : burstResult.getTypes()) {
publishProgress(artifactType);
saveArtifacts(stackSaver, burstResult, artifactType);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
mBurstModuleState.set(BurstModuleState.IDLE);
// Re-enable the shutter button.
mReadyStateListener.onBurstReadyStateChanged(true);
}
@Override
protected void onProgressUpdate(String... artifactTypes) {
logProgressUpdate(artifactTypes, burstResult);
}
};
saveTask.execute(null, null, null);
}
/**
* Save individual artifacts for bursts.
*/
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(stackSaver, mediaItems.get(index),
artifactType, artifactIndex + 1, index + 1);
}
}
}
private void saveBurstMediaItem(StackSaver stackSaver, BurstMediaItem mediaItem,
String artifactType, int artifactIndex, int index) {
long timestamp = mediaItem.getTimestamp();
String title = String.format(MEDIA_ITEM_FILENAME_FORMAT_STRING,
artifactType, artifactIndex, index, timestamp);
String mimeType = mediaItem.getMimeType();
ExifInterface exif = null;
if (LocalData.MIME_TYPE_JPEG.equals(mimeType)) {
exif = new ExifInterface();
exif.addDateTimeStampTag(
ExifInterface.TAG_DATE_TIME,
timestamp,
UTC_TIMEZONE);
}
stackSaver.saveStackedImage(mediaItem.getData(),
title,
mediaItem.getWidth(),
mediaItem.getHeight(),
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()) {
artifactDescription.add(entry.getValue() + " " + entry.getKey());
}
String message = prefix + TextUtils.join(" and ", artifactDescription) + ".";
Log.d(TAG, message);
}
private void logProgressUpdate(String[] artifactTypes, BurstResult burstResult) {
for (String artifactType : artifactTypes) {
List<BurstArtifact> artifacts =
burstResult.getArtifactsByType(artifactType);
if (!artifacts.isEmpty()) {
Log.d(TAG, "Saving " + artifacts.size()
+ " " + artifactType + "s.");
}
}
}
}