Merge "Interface ImageTaskManager for Image-based Tasks" into ub-camera-haleakala
diff --git a/src/com/android/camera/async/HandlerFactory.java b/src/com/android/camera/async/HandlerFactory.java
new file mode 100644
index 0000000..79be101
--- /dev/null
+++ b/src/com/android/camera/async/HandlerFactory.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.async;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Creates new handlers backed by threads with a specified lifetime.
+ */
+public class HandlerFactory {
+    /**
+     * @param lifetime The lifetime of the associated handler's thread.
+     * @param threadName The name to assign to the created thread.
+     * @return A handler backed by a new thread.
+     */
+    public Handler create(Lifetime lifetime, String threadName) {
+        final HandlerThread thread = new HandlerThread(threadName);
+        thread.start();
+
+        lifetime.add(new SafeCloseable() {
+            @Override
+            public void close() {
+                thread.quitSafely();
+            }
+        });
+
+        return new Handler(thread.getLooper());
+    }
+}
diff --git a/src/com/android/camera/async/MainThreadExecutor.java b/src/com/android/camera/async/MainThreadExecutor.java
new file mode 100644
index 0000000..51c3899
--- /dev/null
+++ b/src/com/android/camera/async/MainThreadExecutor.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.async;
+
+import android.os.Handler;
+import android.os.Looper;
+
+public class MainThreadExecutor extends HandlerExecutor {
+    public MainThreadExecutor(Handler handler) {
+        super(handler);
+    }
+
+    public static MainThreadExecutor create() {
+        return new MainThreadExecutor(new Handler(Looper.getMainLooper()));
+    }
+}
diff --git a/src/com/android/camera/async/Observable.java b/src/com/android/camera/async/Observable.java
index 496c8ef..e9b3df0 100644
--- a/src/com/android/camera/async/Observable.java
+++ b/src/com/android/camera/async/Observable.java
@@ -17,7 +17,6 @@
 package com.android.camera.async;
 
 import com.android.camera.util.Callback;
-import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 
 import java.util.concurrent.Executor;
@@ -35,5 +34,5 @@
      * @return A {@link SafeCloseable} handle to be closed when the callback
      *         must be removed.
      */
-    public SafeCloseable addCallback(Callback callback, Executor executor);
+    public SafeCloseable addCallback(Callback<T> callback, Executor executor);
 }
diff --git a/src/com/android/camera/one/OneCamera.java b/src/com/android/camera/one/OneCamera.java
index e8e3b84..c24c3e7 100644
--- a/src/com/android/camera/one/OneCamera.java
+++ b/src/com/android/camera/one/OneCamera.java
@@ -399,17 +399,6 @@
     public void close();
 
     /**
-     * @return A list of all supported preview resolutions.
-     */
-    public Size[] getSupportedPreviewSizes();
-
-    /**
-     * @return The aspect ratio of the full size capture (usually the native
-     *         resolution of the camera).
-     */
-    public float getFullSizeAspectRatio();
-
-    /**
      * @return The direction of the camera.
      */
     public Facing getDirection();
diff --git a/src/com/android/camera/one/OneCameraCharacteristics.java b/src/com/android/camera/one/OneCameraCharacteristics.java
index 9e806b8..c2b4418 100644
--- a/src/com/android/camera/one/OneCameraCharacteristics.java
+++ b/src/com/android/camera/one/OneCameraCharacteristics.java
@@ -16,6 +16,10 @@
 
 package com.android.camera.one;
 
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+
 import com.android.camera.util.Size;
 
 import java.util.List;
@@ -23,16 +27,38 @@
 /**
  * The properties describing a OneCamera device. These properties are fixed for
  * a given OneCamera device.
- *
- * TODO: Complete this interface to expose all camera
- * properties.
  */
 public interface OneCameraCharacteristics {
     /**
      * Gets the supported picture sizes for the given image format.
      *
      * @param imageFormat The specific image format listed on
-     *                    {@link ImageFormat}.
+     *            {@link ImageFormat}.
      */
     public List<Size> getSupportedPictureSizes(int imageFormat);
+
+    /**
+     * Gets the supported preview sizes.
+     */
+    public List<Size> getSupportedPreviewSizes();
+
+    /**
+     * @See {@link CameraCharacteristics#SENSOR_ORIENTATION}
+     */
+    public int getSensorOrientation();
+
+    /**
+     * @Return The direction of the camera
+     */
+    public OneCamera.Facing getCameraDirection();
+
+    /**
+     * @See {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}
+     */
+    public Rect getSensorInfoActiveArraySize();
+
+    /**
+     * @See {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM}
+     */
+    public float getAvailableMaxDigitalZoom();
 }
diff --git a/src/com/android/camera/one/OneCameraManager.java b/src/com/android/camera/one/OneCameraManager.java
index dde1ca2..4cd6552 100644
--- a/src/com/android/camera/one/OneCameraManager.java
+++ b/src/com/android/camera/one/OneCameraManager.java
@@ -107,7 +107,7 @@
         int maxMemoryMB = activity.getServices().getMemoryManager()
                 .getMaxAllowedNativeMemoryAllocation();
         return new com.android.camera.one.v2.OneCameraManagerImpl(
-                activity.getApplicationContext(), cameraManager, maxMemoryMB,
+                activity, cameraManager, maxMemoryMB,
                 displayMetrics, activity.getSoundPlayer());
     }
 
diff --git a/src/com/android/camera/one/v2/OneCameraCharacteristicsImpl.java b/src/com/android/camera/one/v2/OneCameraCharacteristicsImpl.java
index 77278e5..2994717 100644
--- a/src/com/android/camera/one/v2/OneCameraCharacteristicsImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraCharacteristicsImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.camera.one.v2;
 
-import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.params.StreamConfigurationMap;
+
+import com.android.camera.one.OneCamera;
 import com.android.camera.one.OneCameraCharacteristics;
 import com.android.camera.util.Size;
 
@@ -26,8 +29,9 @@
 import java.util.List;
 
 /**
- * Describes a OneCamera device which is on top of camera2 API. This is essential a wrapper
- * for #{link android.hardware.camera2.CameraCharacteristics}.
+ * Describes a OneCamera device which is on top of camera2 API. This is
+ * essential a wrapper for #{link
+ * android.hardware.camera2.CameraCharacteristics}.
  */
 public class OneCameraCharacteristicsImpl implements OneCameraCharacteristics {
     private final CameraCharacteristics mCameraCharacteristics;
@@ -46,4 +50,40 @@
         }
         return supportedPictureSizes;
     }
+
+    @Override
+    public List<Size> getSupportedPreviewSizes() {
+        StreamConfigurationMap configMap =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        ArrayList<Size> supportedPictureSizes = new ArrayList<>();
+        for (android.util.Size androidSize : configMap.getOutputSizes(SurfaceTexture.class)) {
+            supportedPictureSizes.add(new Size(androidSize));
+        }
+        return supportedPictureSizes;
+    }
+
+    @Override
+    public int getSensorOrientation() {
+        return mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+    }
+
+    @Override
+    public OneCamera.Facing getCameraDirection() {
+        int direction = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
+        if (direction == CameraCharacteristics.LENS_FACING_BACK) {
+            return OneCamera.Facing.BACK;
+        } else {
+            return OneCamera.Facing.FRONT;
+        }
+    }
+
+    @Override
+    public Rect getSensorInfoActiveArraySize() {
+        return mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+    }
+
+    @Override
+    public float getAvailableMaxDigitalZoom() {
+        return mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+    }
 }
diff --git a/src/com/android/camera/one/v2/OneCameraFactory.java b/src/com/android/camera/one/v2/OneCameraFactory.java
new file mode 100644
index 0000000..aab328d
--- /dev/null
+++ b/src/com/android/camera/one/v2/OneCameraFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.one.v2;
+
+import android.os.Handler;
+
+import com.android.camera.async.MainThreadExecutor;
+import com.android.camera.async.Observable;
+import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCameraCharacteristics;
+import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
+import com.android.camera.util.Size;
+
+import java.util.concurrent.Executor;
+
+public interface OneCameraFactory {
+    OneCamera createOneCamera(CameraDeviceProxy cameraDevice,
+            OneCameraCharacteristics characteristics,
+            MainThreadExecutor mainThreadExecutor,
+            Size pictureSize,
+            Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting);
+}
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index 15a5411..26c860a 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -436,14 +436,12 @@
         mDevice.close();
     }
 
-    @Override
     public Size[] getSupportedPreviewSizes() {
         StreamConfigurationMap config = mCharacteristics
                 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
         return Size.convert(config.getOutputSizes(SurfaceTexture.class));
     }
 
-    @Override
     public float getFullSizeAspectRatio() {
         return mFullSizeAspectRatio;
     }
diff --git a/src/com/android/camera/one/v2/OneCameraManagerImpl.java b/src/com/android/camera/one/v2/OneCameraManagerImpl.java
index 584cb4e..704b8fe 100644
--- a/src/com/android/camera/one/v2/OneCameraManagerImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraManagerImpl.java
@@ -25,6 +25,7 @@
 import android.util.DisplayMetrics;
 
 import com.android.camera.SoundPlayer;
+import com.android.camera.app.AppController;
 import com.android.camera.debug.Log;
 import com.android.camera.debug.Log.Tag;
 import com.android.camera.one.OneCamera;
@@ -41,7 +42,7 @@
 public class OneCameraManagerImpl extends OneCameraManager {
     private static final Tag TAG = new Tag("OneCameraMgrImpl2");
 
-    private final Context mContext;
+    private final AppController mAppController;
     private final CameraManager mCameraManager;
     private final int mMaxMemoryMB;
     private final DisplayMetrics mDisplayMetrics;
@@ -54,9 +55,9 @@
      * @param maxMemoryMB maximum amount of memory opened cameras should consume
      *            during capture and processing, in megabytes.
      */
-    public OneCameraManagerImpl(Context context, CameraManager cameraManager, int maxMemoryMB,
-            DisplayMetrics displayMetrics, SoundPlayer soundPlayer) {
-        mContext = context;
+    public OneCameraManagerImpl(AppController appController, CameraManager cameraManager, int
+            maxMemoryMB, DisplayMetrics displayMetrics, SoundPlayer soundPlayer) {
+        mAppController = appController;
         mCameraManager = cameraManager;
         mMaxMemoryMB = maxMemoryMB;
         mDisplayMetrics = displayMetrics;
@@ -113,8 +114,9 @@
                             CameraCharacteristics characteristics = mCameraManager
                                     .getCameraCharacteristics(device.getId());
                             // TODO: Set boolean based on whether HDR+ is enabled.
-                            OneCamera oneCamera = OneCameraCreator.create(mContext, useHdr, device,
-                                    characteristics, pictureSize, mMaxMemoryMB, mDisplayMetrics,
+                            OneCamera oneCamera = OneCameraCreator.create(mAppController, useHdr,
+                                    device, characteristics, pictureSize, mMaxMemoryMB,
+                                    mDisplayMetrics,
                                     mSoundPlayer);
                             openCallback.onCameraOpened(oneCamera);
                         } catch (CameraAccessException e) {
diff --git a/src/com/android/camera/one/v2/OneCameraZslImpl.java b/src/com/android/camera/one/v2/OneCameraZslImpl.java
index 033e832..cfd314f 100644
--- a/src/com/android/camera/one/v2/OneCameraZslImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraZslImpl.java
@@ -597,14 +597,12 @@
         mCaptureImageReader.close();
     }
 
-    @Override
     public Size[] getSupportedPreviewSizes() {
         StreamConfigurationMap config =
                 mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
         return Size.convert(config.getOutputSizes(sCaptureImageFormat));
     }
 
-    @Override
     public float getFullSizeAspectRatio() {
         return mFullSizeAspectRatio;
     }
diff --git a/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java b/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java
deleted file mode 100644
index 367ce11..0000000
--- a/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * 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.one.v2;
-
-import android.annotation.TargetApi;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.media.ImageReader;
-import android.os.Build;
-import android.os.Handler;
-import android.view.Surface;
-
-import com.android.camera.app.OrientationManager;
-import com.android.camera.async.CallbackRunnable;
-import com.android.camera.async.ConcurrentState;
-import com.android.camera.async.HandlerExecutor;
-import com.android.camera.async.Lifetime;
-import com.android.camera.async.Updatable;
-import com.android.camera.one.OneCamera;
-import com.android.camera.one.v2.autofocus.ManualAutoFocus;
-import com.android.camera.one.v2.autofocus.ManualAutoFocusFactory;
-import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
-import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
-import com.android.camera.one.v2.camera2proxy.CameraDeviceRequestBuilderFactory;
-import com.android.camera.one.v2.camera2proxy.ImageProxy;
-import com.android.camera.one.v2.commands.CameraCommandExecutor;
-import com.android.camera.one.v2.commands.PreviewCommand;
-import com.android.camera.one.v2.commands.RunnableCameraCommand;
-import com.android.camera.one.v2.common.SimpleCaptureStream;
-import com.android.camera.one.v2.common.TimestampResponseListener;
-import com.android.camera.one.v2.common.TotalCaptureResultResponseListener;
-import com.android.camera.one.v2.common.ZoomedCropRegion;
-import com.android.camera.one.v2.core.CaptureStream;
-import com.android.camera.one.v2.core.RequestTemplate;
-import com.android.camera.one.v2.core.FrameServer;
-import com.android.camera.one.v2.core.FrameServerFactory;
-import com.android.camera.one.v2.initialization.CameraStarter;
-import com.android.camera.one.v2.initialization.InitializedOneCameraFactory;
-import com.android.camera.one.v2.photo.ImageSaver;
-import com.android.camera.one.v2.photo.PictureTaker;
-import com.android.camera.one.v2.photo.PictureTakerFactory;
-import com.android.camera.one.v2.sharedimagereader.ImageStreamFactory;
-import com.android.camera.one.v2.sharedimagereader.SharedImageReaderFactory;
-import com.android.camera.session.CaptureSession;
-import com.android.camera.util.Size;
-import com.google.common.base.Supplier;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Creates a camera which takes jpeg images using the hardware encoder with
- * baseline functionality.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class SimpleJpegOneCameraFactory {
-    /**
-     * Finishes constructing the camera when prerequisites, e.g. the preview
-     * stream and capture session, are ready.
-     */
-    private static class CameraStarterImpl implements CameraStarter {
-        private final CameraDeviceProxy mDevice;
-        private final CameraCharacteristics mCameraCharacteristics;
-        private final ImageReader mImageReader;
-        private final Handler mMainHandler;
-
-        private CameraStarterImpl(
-                CameraDeviceProxy device,
-                CameraCharacteristics cameraCharacteristics,
-                ImageReader imageReader, Handler mainHandler) {
-            mDevice = device;
-            mCameraCharacteristics = cameraCharacteristics;
-            mImageReader = imageReader;
-            mMainHandler = mainHandler;
-        }
-
-        @Override
-        public CameraControls startCamera(Lifetime cameraLifetime,
-                CameraCaptureSessionProxy cameraCaptureSession,
-                Surface previewSurface,
-                ConcurrentState<Float> zoomState,
-                Updatable<TotalCaptureResult> metadataCallback,
-                Updatable<Boolean> readyState) {
-            // Build the FrameServer from the CaptureSession
-            FrameServerFactory frameServerFactory =
-                    new FrameServerFactory(new Lifetime(cameraLifetime), cameraCaptureSession);
-            FrameServer frameServer = frameServerFactory.provideFrameServer();
-
-            // Build the shared image reader
-            SharedImageReaderFactory sharedImageReaderFactory = new SharedImageReaderFactory(new
-                    Lifetime(cameraLifetime), mImageReader);
-
-            Updatable<Long> globalTimestampCallback =
-                    sharedImageReaderFactory.provideGlobalTimestampQueue();
-            ImageStreamFactory imageStreamFactory =
-                    sharedImageReaderFactory.provideSharedImageReader();
-
-            // The request builder used by all camera operations.
-            // Streams, ResponseListeners, and Parameters added to
-            // this will be applied to *all* requests sent to the camera.
-            RequestTemplate rootBuilder = new RequestTemplate
-                    (new CameraDeviceRequestBuilderFactory(mDevice));
-            // The shared image reader must be wired to receive every timestamp
-            // for every image (including the preview).
-            rootBuilder.addResponseListener(
-                    new TimestampResponseListener(globalTimestampCallback));
-
-            rootBuilder.addResponseListener(new TotalCaptureResultResponseListener
-                    (metadataCallback));
-
-            ZoomedCropRegion cropRegion = new ZoomedCropRegion(mCameraCharacteristics,
-                    zoomState);
-            rootBuilder.setParam(CaptureRequest.SCALER_CROP_REGION, cropRegion);
-
-            CaptureStream previewStream = new SimpleCaptureStream(previewSurface);
-            rootBuilder.addStream(previewStream);
-
-            int templateType = CameraDevice.TEMPLATE_PREVIEW;
-
-            ScheduledExecutorService miscThreadPool = Executors.newScheduledThreadPool(1);
-
-            CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(miscThreadPool);
-            PreviewCommand previewCommand = new PreviewCommand(frameServer, rootBuilder,
-                    templateType);
-            Runnable previewRunner = new RunnableCameraCommand(cameraCommandExecutor,
-                    previewCommand);
-
-            // Restart the preview when the zoom changes.
-            zoomState.addCallback(new CallbackRunnable(previewRunner), miscThreadPool);
-
-            OrientationManager.DeviceOrientation sensorOrientation = getSensorOrientation();
-
-            ManualAutoFocusFactory manualAutoFocusFactory = new ManualAutoFocusFactory(new
-                    Lifetime(cameraLifetime), frameServer, miscThreadPool, cropRegion,
-                    sensorOrientation, previewRunner, rootBuilder, templateType);
-            ManualAutoFocus autoFocus = manualAutoFocusFactory.provideManualAutoFocus();
-            Supplier<MeteringRectangle[]> aeRegions =
-                    manualAutoFocusFactory.provideAEMeteringRegion();
-            Supplier<MeteringRectangle[]> afRegions =
-                    manualAutoFocusFactory.provideAFMeteringRegion();
-
-            rootBuilder.setParam(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
-            rootBuilder.setParam(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
-
-            HandlerExecutor mainExecutor = new HandlerExecutor(mMainHandler);
-
-            // FIXME TODO Replace stub with real implementation
-            ImageSaver.Builder imageSaverBuilder = new ImageSaver.Builder() {
-                @Override
-                public ImageSaver build(OrientationManager.DeviceOrientation orientation,
-                        CaptureSession session) {
-                    return new ImageSaver() {
-                        @Override
-                        public void saveAndCloseImage(ImageProxy imageProxy) {
-                            imageProxy.close();
-                        }
-                    };
-                }
-            };
-
-            PictureTakerFactory pictureTakerFactory = new PictureTakerFactory(mainExecutor,
-                    cameraCommandExecutor, imageSaverBuilder, frameServer, rootBuilder,
-                    imageStreamFactory);
-            PictureTaker pictureTaker = pictureTakerFactory.providePictureTaker();
-
-            previewRunner.run();
-
-            return new CameraControls(pictureTaker, autoFocus);
-        }
-
-        private OrientationManager.DeviceOrientation getSensorOrientation() {
-            Integer degrees = mCameraCharacteristics.get(CameraCharacteristics
-                    .SENSOR_ORIENTATION);
-
-            switch (degrees) {
-                case 0:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-                case 90:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_90;
-                case 180:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_180;
-                case 270:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_270;
-                default:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-            }
-        }
-    }
-
-    private final InitializedOneCameraFactory mInitializedOneCameraFactory;
-
-    public SimpleJpegOneCameraFactory(CameraDevice cameraDevice,
-            CameraCharacteristics characteristics, Handler mainHandler, Size pictureSize) {
-        CameraDeviceProxy device = new CameraDeviceProxy(cameraDevice);
-
-        int imageFormat = ImageFormat.JPEG;
-        // TODO This is totally arbitrary, and could probably be increased.
-        int maxImageCount = 10;
-
-        ImageReader imageReader = ImageReader.newInstance(pictureSize.getWidth(),
-                pictureSize.getHeight(), imageFormat, 10);
-
-        // FIXME TODO Close the ImageReader when all images have been freed!
-
-        List<Surface> outputSurfaces = new ArrayList<>();
-        outputSurfaces.add(imageReader.getSurface());
-
-        CameraStarter cameraStarter = new CameraStarterImpl(device, characteristics, imageReader,
-                mainHandler);
-
-        mInitializedOneCameraFactory =
-                new InitializedOneCameraFactory(cameraStarter, device, characteristics,
-                        outputSurfaces, imageFormat, mainHandler);
-    }
-
-    public OneCamera provideOneCamera() {
-        return mInitializedOneCameraFactory.provideOneCamera();
-    }
-}
diff --git a/src/com/android/camera/one/v2/SimpleOneCameraFactory.java b/src/com/android/camera/one/v2/SimpleOneCameraFactory.java
new file mode 100644
index 0000000..bf58849
--- /dev/null
+++ b/src/com/android/camera/one/v2/SimpleOneCameraFactory.java
@@ -0,0 +1,173 @@
+/*
+ * 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.one.v2;
+
+import android.annotation.TargetApi;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.ImageReader;
+import android.os.Build;
+import android.view.Surface;
+
+import com.android.camera.async.HandlerFactory;
+import com.android.camera.async.Lifetime;
+import com.android.camera.async.MainThreadExecutor;
+import com.android.camera.async.Observable;
+import com.android.camera.async.Updatable;
+import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCameraCharacteristics;
+import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
+import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
+import com.android.camera.one.v2.camera2proxy.CameraDeviceRequestBuilderFactory;
+import com.android.camera.one.v2.commands.CameraCommandExecutor;
+import com.android.camera.one.v2.common.BasicCameraFactory;
+import com.android.camera.one.v2.common.SimpleCaptureStream;
+import com.android.camera.one.v2.common.TimestampResponseListener;
+import com.android.camera.one.v2.core.FrameServer;
+import com.android.camera.one.v2.core.FrameServerFactory;
+import com.android.camera.one.v2.core.RequestBuilder;
+import com.android.camera.one.v2.core.RequestTemplate;
+import com.android.camera.one.v2.initialization.CameraStarter;
+import com.android.camera.one.v2.initialization.InitializedOneCameraFactory;
+import com.android.camera.one.v2.photo.ImageRotationCalculator;
+import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
+import com.android.camera.one.v2.photo.ImageSaver;
+import com.android.camera.one.v2.photo.PictureTakerFactory;
+import com.android.camera.one.v2.photo.YuvImageBackendImageSaver;
+import com.android.camera.one.v2.sharedimagereader.ImageStreamFactory;
+import com.android.camera.one.v2.sharedimagereader.SharedImageReaderFactory;
+import com.android.camera.util.Size;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Creates a camera which takes jpeg images using the hardware encoder with
+ * baseline functionality.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class SimpleOneCameraFactory implements OneCameraFactory {
+    private final int mImageFormat;
+    private final int mMaxImageCount;
+
+    /**
+     * @param imageFormat The {@link ImageFormat} to use for full-size images to
+     *            be saved.
+     * @param maxImageCount The size of the image reader to use for full-size
+     *            images.
+     */
+    public SimpleOneCameraFactory(int imageFormat, int maxImageCount) {
+        mImageFormat = imageFormat;
+        mMaxImageCount = maxImageCount;
+    }
+
+    @Override
+    public OneCamera createOneCamera(final CameraDeviceProxy device,
+            final OneCameraCharacteristics characteristics, final MainThreadExecutor mainExecutor,
+            Size pictureSize,
+            final Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting) {
+
+        final ImageReader imageReader = ImageReader.newInstance(pictureSize.getWidth(),
+                pictureSize.getHeight(), mImageFormat, mMaxImageCount);
+        // FIXME TODO Close the ImageReader when all images have been freed!
+
+        List<Surface> outputSurfaces = new ArrayList<>();
+        outputSurfaces.add(imageReader.getSurface());
+
+        /**
+         * Finishes constructing the camera when prerequisites, e.g. the preview
+         * stream and capture session, are ready.
+         */
+        CameraStarter cameraStarter = new CameraStarter() {
+            @Override
+            public CameraStarter.CameraControls startCamera(Lifetime cameraLifetime,
+                    CameraCaptureSessionProxy cameraCaptureSession,
+                    Surface previewSurface,
+                    Observable<Float> zoomState,
+                    Updatable<TotalCaptureResult> metadataCallback,
+                    Updatable<Boolean> readyState) {
+                // Create the FrameServer from the CaptureSession.
+                FrameServer frameServer = new FrameServerFactory(
+                        new Lifetime(cameraLifetime), cameraCaptureSession, new HandlerFactory())
+                        .provideFrameServer();
+
+                // Create a thread pool on which to execute camera operations.
+                ScheduledExecutorService miscThreadPool = Executors.newScheduledThreadPool(1);
+
+                // Create the shared image reader.
+                SharedImageReaderFactory sharedImageReaderFactory =
+                        new SharedImageReaderFactory(new Lifetime(cameraLifetime), imageReader);
+                Updatable<Long> globalTimestampCallback =
+                        sharedImageReaderFactory.provideGlobalTimestampQueue();
+                ImageStreamFactory imageStreamFactory =
+                        sharedImageReaderFactory.provideSharedImageReader();
+
+                // Create the request builder used by all camera operations.
+                // Streams, ResponseListeners, and Parameters added to
+                // this will be applied to *all* requests sent to the camera.
+                RequestTemplate rootBuilder = new RequestTemplate
+                        (new CameraDeviceRequestBuilderFactory(device));
+                // The shared image reader must be wired to receive every
+                // timestamp for every image (including the preview).
+                rootBuilder.addResponseListener(
+                        new TimestampResponseListener(globalTimestampCallback));
+                rootBuilder.addStream(new SimpleCaptureStream(previewSurface));
+
+                // Create basic functionality (zoom, AE, AF).
+                BasicCameraFactory basicCameraFactory = new BasicCameraFactory(new Lifetime
+                        (cameraLifetime), characteristics, frameServer, rootBuilder,
+                        miscThreadPool, flashSetting, zoomState, CameraDevice
+                        .TEMPLATE_ZERO_SHUTTER_LAG);
+
+                RequestBuilder.Factory meteredZooomedRequestBuilder =
+                        basicCameraFactory.provideMeteredZoomedRequestBuilder();
+
+                // Create the image saver.
+                // Used to rotate images the right way based on the sensor used
+                // for taking the image.
+                ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
+                        .from(characteristics);
+                // FIXME Create based on mImageFormat
+                ImageSaver.Builder imageSaverBuilder = new YuvImageBackendImageSaver(mainExecutor,
+                        imageRotationCalculator);
+
+                // Create the picture-taker.
+                CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(
+                        miscThreadPool);
+
+                PictureTakerFactory pictureTakerFactory = new PictureTakerFactory(mainExecutor,
+                        cameraCommandExecutor, imageSaverBuilder, frameServer,
+                        meteredZooomedRequestBuilder, imageStreamFactory);
+
+                basicCameraFactory.providePreviewStarter().run();
+
+                return new CameraStarter.CameraControls(
+                        pictureTakerFactory.providePictureTaker(),
+                        basicCameraFactory.provideManualAutoFocus());
+            }
+        };
+
+        float maxZoom = characteristics.getAvailableMaxDigitalZoom();
+        List<Size> supportedPreviewSizes = characteristics.getSupportedPreviewSizes();
+        OneCamera.Facing direction = characteristics.getCameraDirection();
+
+        return new InitializedOneCameraFactory(cameraStarter, device, outputSurfaces, mainExecutor,
+                new HandlerFactory(), maxZoom, supportedPreviewSizes, direction).provideOneCamera();
+    }
+}
diff --git a/src/com/android/camera/one/v2/SimpleYuvOneCameraFactory.java b/src/com/android/camera/one/v2/SimpleYuvOneCameraFactory.java
deleted file mode 100644
index e5ccead..0000000
--- a/src/com/android/camera/one/v2/SimpleYuvOneCameraFactory.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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.one.v2;
-
-import android.annotation.TargetApi;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.media.ImageReader;
-import android.os.Build;
-import android.os.Handler;
-import android.view.Surface;
-
-import com.android.camera.app.OrientationManager;
-import com.android.camera.async.CallbackRunnable;
-import com.android.camera.async.ConcurrentState;
-import com.android.camera.async.HandlerExecutor;
-import com.android.camera.async.Lifetime;
-import com.android.camera.async.Updatable;
-import com.android.camera.one.OneCamera;
-import com.android.camera.one.v2.autofocus.ManualAutoFocus;
-import com.android.camera.one.v2.autofocus.ManualAutoFocusFactory;
-import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
-import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
-import com.android.camera.one.v2.camera2proxy.CameraDeviceRequestBuilderFactory;
-import com.android.camera.one.v2.commands.CameraCommandExecutor;
-import com.android.camera.one.v2.commands.PreviewCommand;
-import com.android.camera.one.v2.commands.RunnableCameraCommand;
-import com.android.camera.one.v2.common.SimpleCaptureStream;
-import com.android.camera.one.v2.common.TimestampResponseListener;
-import com.android.camera.one.v2.common.TotalCaptureResultResponseListener;
-import com.android.camera.one.v2.common.ZoomedCropRegion;
-import com.android.camera.one.v2.core.CaptureStream;
-import com.android.camera.one.v2.core.RequestTemplate;
-import com.android.camera.one.v2.core.FrameServer;
-import com.android.camera.one.v2.core.FrameServerFactory;
-import com.android.camera.one.v2.initialization.CameraStarter;
-import com.android.camera.one.v2.initialization.InitializedOneCameraFactory;
-import com.android.camera.one.v2.photo.ImageRotationCalculator;
-import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
-import com.android.camera.one.v2.photo.ImageSaver;
-import com.android.camera.one.v2.photo.PictureTaker;
-import com.android.camera.one.v2.photo.PictureTakerFactory;
-import com.android.camera.one.v2.photo.YuvImageBackendImageSaver;
-import com.android.camera.one.v2.sharedimagereader.ImageStreamFactory;
-import com.android.camera.one.v2.sharedimagereader.SharedImageReaderFactory;
-import com.android.camera.util.Size;
-import com.google.common.base.Optional;
-import com.google.common.base.Supplier;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Creates a camera which takes jpeg images using the hardware encoder with
- * baseline functionality.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class SimpleYuvOneCameraFactory {
-    /**
-     * Finishes constructing the camera when prerequisites, e.g. the preview
-     * stream and capture session, are ready.
-     */
-    private static class CameraStarterImpl implements CameraStarter {
-        private final CameraDeviceProxy mDevice;
-        private final CameraCharacteristics mCameraCharacteristics;
-        private final ImageReader mImageReader;
-        private final Handler mMainHandler;
-
-        private CameraStarterImpl(
-                CameraDeviceProxy device,
-                CameraCharacteristics cameraCharacteristics,
-                ImageReader imageReader, Handler mainHandler) {
-            mDevice = device;
-            mCameraCharacteristics = cameraCharacteristics;
-            mImageReader = imageReader;
-            mMainHandler = mainHandler;
-        }
-
-        @Override
-        public CameraControls startCamera(Lifetime cameraLifetime,
-                CameraCaptureSessionProxy cameraCaptureSession,
-                Surface previewSurface,
-                ConcurrentState<Float> zoomState,
-                Updatable<TotalCaptureResult> metadataCallback,
-                Updatable<Boolean> readyState) {
-            // Build the FrameServer from the CaptureSession
-            FrameServerFactory frameServerFactory =
-                    new FrameServerFactory(new Lifetime(cameraLifetime), cameraCaptureSession);
-            FrameServer frameServer = frameServerFactory.provideFrameServer();
-
-            // Build the shared image reader
-            SharedImageReaderFactory sharedImageReaderFactory = new SharedImageReaderFactory(new
-                    Lifetime(cameraLifetime), mImageReader);
-
-            Updatable<Long> globalTimestampCallback =
-                    sharedImageReaderFactory.provideGlobalTimestampQueue();
-            ImageStreamFactory imageStreamFactory =
-                    sharedImageReaderFactory.provideSharedImageReader();
-
-            // The request builder used by all camera operations.
-            // Streams, ResponseListeners, and Parameters added to
-            // this will be applied to *all* requests sent to the camera.
-            RequestTemplate rootBuilder = new RequestTemplate
-                    (new CameraDeviceRequestBuilderFactory(mDevice));
-            // The shared image reader must be wired to receive every timestamp
-            // for every image (including the preview).
-            rootBuilder.addResponseListener(
-                    new TimestampResponseListener(globalTimestampCallback));
-
-            rootBuilder.addResponseListener(new TotalCaptureResultResponseListener
-                    (metadataCallback));
-
-            ZoomedCropRegion cropRegion = new ZoomedCropRegion(mCameraCharacteristics,
-                    zoomState);
-            rootBuilder.setParam(CaptureRequest.SCALER_CROP_REGION, cropRegion);
-
-            CaptureStream previewStream = new SimpleCaptureStream(previewSurface);
-            rootBuilder.addStream(previewStream);
-
-            int templateType = CameraDevice.TEMPLATE_PREVIEW;
-
-            ScheduledExecutorService miscThreadPool = Executors.newScheduledThreadPool(1);
-
-            CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(miscThreadPool);
-            PreviewCommand previewCommand = new PreviewCommand(frameServer, rootBuilder,
-                    templateType);
-            Runnable previewRunner = new RunnableCameraCommand(cameraCommandExecutor,
-                    previewCommand);
-
-            // Restart the preview when the zoom changes.
-            zoomState.addCallback(new CallbackRunnable(previewRunner), miscThreadPool);
-
-            OrientationManager.DeviceOrientation sensorOrientation = getSensorOrientation();
-
-            ManualAutoFocusFactory manualAutoFocusFactory = new ManualAutoFocusFactory(new
-                    Lifetime(cameraLifetime), frameServer, miscThreadPool, cropRegion,
-                    sensorOrientation, previewRunner, rootBuilder, templateType);
-            ManualAutoFocus autoFocus = manualAutoFocusFactory.provideManualAutoFocus();
-            Supplier<MeteringRectangle[]> aeRegions =
-                    manualAutoFocusFactory.provideAEMeteringRegion();
-            Supplier<MeteringRectangle[]> afRegions =
-                    manualAutoFocusFactory.provideAFMeteringRegion();
-
-            rootBuilder.setParam(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
-            rootBuilder.setParam(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
-
-            HandlerExecutor mainExecutor = new HandlerExecutor(mMainHandler);
-
-            // Used to rotate images the right way based on the sensor used for taking the image.
-            ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
-                    .from(mCameraCharacteristics);
-
-            ImageSaver.Builder imageSaverBuilder = new YuvImageBackendImageSaver(mainExecutor,
-                    imageRotationCalculator);
-
-            PictureTakerFactory pictureTakerFactory = new PictureTakerFactory(mainExecutor,
-                    cameraCommandExecutor, imageSaverBuilder, frameServer, rootBuilder,
-                    imageStreamFactory);
-            PictureTaker pictureTaker = pictureTakerFactory.providePictureTaker();
-
-            previewRunner.run();
-
-            return new CameraControls(pictureTaker, autoFocus);
-        }
-
-        private OrientationManager.DeviceOrientation getSensorOrientation() {
-            Integer degrees = mCameraCharacteristics.get(CameraCharacteristics
-                    .SENSOR_ORIENTATION);
-
-            switch (degrees) {
-                case 0:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-                case 90:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_90;
-                case 180:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_180;
-                case 270:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_270;
-                default:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-            }
-        }
-    }
-
-    private final InitializedOneCameraFactory mInitializedOneCameraFactory;
-
-    public SimpleYuvOneCameraFactory(CameraDevice cameraDevice,
-            CameraCharacteristics characteristics, Handler mainHandler, Size pictureSize) {
-        CameraDeviceProxy device = new CameraDeviceProxy(cameraDevice);
-
-        int imageFormat = ImageFormat.YUV_420_888;
-        // TODO This is totally arbitrary, and could probably be increased.
-        int maxImageCount = 10;
-
-        ImageReader imageReader = ImageReader.newInstance(pictureSize.getWidth(),
-                pictureSize.getHeight(), imageFormat, maxImageCount);
-
-        // FIXME TODO Close the ImageReader when all images have been freed!
-
-        List<Surface> outputSurfaces = new ArrayList<>();
-        outputSurfaces.add(imageReader.getSurface());
-
-        CameraStarter cameraStarter = new CameraStarterImpl(device, characteristics, imageReader,
-                mainHandler);
-
-        mInitializedOneCameraFactory =
-                new InitializedOneCameraFactory(cameraStarter, device, characteristics,
-                        outputSurfaces, imageFormat, mainHandler);
-    }
-
-    public OneCamera provideOneCamera() {
-        return mInitializedOneCameraFactory.provideOneCamera();
-    }
-}
diff --git a/src/com/android/camera/one/v2/ZslOneCameraFactory.java b/src/com/android/camera/one/v2/ZslOneCameraFactory.java
index 3ceecc0..32ef1bd 100644
--- a/src/com/android/camera/one/v2/ZslOneCameraFactory.java
+++ b/src/com/android/camera/one/v2/ZslOneCameraFactory.java
@@ -16,230 +16,149 @@
 
 package com.android.camera.one.v2;
 
-import android.annotation.TargetApi;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.MeteringRectangle;
 import android.media.ImageReader;
-import android.os.Build;
-import android.os.Handler;
 import android.view.Surface;
 
-import com.android.camera.app.OrientationManager;
-import com.android.camera.async.CallbackRunnable;
-import com.android.camera.async.ConcurrentState;
-import com.android.camera.async.HandlerExecutor;
+import com.android.camera.async.HandlerFactory;
 import com.android.camera.async.Lifetime;
+import com.android.camera.async.MainThreadExecutor;
+import com.android.camera.async.Observable;
 import com.android.camera.async.Updatable;
 import com.android.camera.one.OneCamera;
-import com.android.camera.one.v2.autofocus.ManualAutoFocus;
-import com.android.camera.one.v2.autofocus.ManualAutoFocusFactory;
+import com.android.camera.one.OneCameraCharacteristics;
 import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
 import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
 import com.android.camera.one.v2.camera2proxy.CameraDeviceRequestBuilderFactory;
 import com.android.camera.one.v2.commands.CameraCommandExecutor;
-import com.android.camera.one.v2.commands.PreviewCommand;
-import com.android.camera.one.v2.commands.RunnableCameraCommand;
+import com.android.camera.one.v2.common.BasicCameraFactory;
 import com.android.camera.one.v2.common.SimpleCaptureStream;
 import com.android.camera.one.v2.common.TimestampResponseListener;
-import com.android.camera.one.v2.common.TotalCaptureResultResponseListener;
-import com.android.camera.one.v2.common.ZoomedCropRegion;
-import com.android.camera.one.v2.core.CaptureStream;
 import com.android.camera.one.v2.core.FrameServer;
 import com.android.camera.one.v2.core.FrameServerFactory;
+import com.android.camera.one.v2.core.RequestBuilder;
 import com.android.camera.one.v2.core.RequestTemplate;
 import com.android.camera.one.v2.initialization.CameraStarter;
 import com.android.camera.one.v2.initialization.InitializedOneCameraFactory;
 import com.android.camera.one.v2.photo.ImageRotationCalculator;
 import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
 import com.android.camera.one.v2.photo.ImageSaver;
-import com.android.camera.one.v2.photo.PictureTaker;
 import com.android.camera.one.v2.photo.YuvImageBackendImageSaver;
 import com.android.camera.one.v2.photo.ZslPictureTakerFactory;
 import com.android.camera.one.v2.sharedimagereader.ImageStreamFactory;
 import com.android.camera.one.v2.sharedimagereader.ZslSharedImageReaderFactory;
 import com.android.camera.one.v2.sharedimagereader.imagedistributor.ImageStream;
-import com.android.camera.one.v2.stats.PreviewFpsListener;
 import com.android.camera.util.Size;
-import com.google.common.base.Supplier;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
-/**
- * Creates a camera which takes YUV images with zero shutter lag.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class ZslOneCameraFactory {
-    /**
-     * Finishes constructing the camera when prerequisites, e.g. the preview
-     * stream and capture session, are ready.
-     */
-    private static class CameraStarterImpl implements CameraStarter {
-        private final CameraDeviceProxy mDevice;
-        private final CameraCharacteristics mCameraCharacteristics;
-        private final ImageReader mImageReader;
-        private final Handler mMainHandler;
+public class ZslOneCameraFactory implements OneCameraFactory {
+    private final int mImageFormat;
+    private final int mMaxImageCount;
 
-        private CameraStarterImpl(
-                CameraDeviceProxy device,
-                CameraCharacteristics cameraCharacteristics,
-                ImageReader imageReader, Handler mainHandler) {
-            mDevice = device;
-            mCameraCharacteristics = cameraCharacteristics;
-            mImageReader = imageReader;
-            mMainHandler = mainHandler;
-        }
-
-        @Override
-        public CameraControls startCamera(Lifetime cameraLifetime,
-                CameraCaptureSessionProxy cameraCaptureSession,
-                Surface previewSurface,
-                ConcurrentState<Float> zoomState,
-                Updatable<TotalCaptureResult> metadataCallback,
-                Updatable<Boolean> readyState) {
-            // Build the FrameServer from the CaptureSession
-            FrameServerFactory frameServerFactory =
-                    new FrameServerFactory(new Lifetime(cameraLifetime), cameraCaptureSession);
-            FrameServer frameServer = frameServerFactory.provideFrameServer();
-
-            // Build the shared image reader
-            ZslSharedImageReaderFactory sharedImageReaderFactory =
-                    new ZslSharedImageReaderFactory(new Lifetime(cameraLifetime), mImageReader);
-
-            Updatable<Long> globalTimestampCallback =
-                    sharedImageReaderFactory.provideGlobalTimestampQueue();
-            ImageStreamFactory imageStreamFactory =
-                    sharedImageReaderFactory.provideSharedImageReader();
-            ImageStream zslImageStream = sharedImageReaderFactory.provideZSLCaptureStream();
-
-            // The request builder used by all camera operations.
-            // Streams, ResponseListeners, and Parameters added to
-            // this will be applied to *all* requests sent to the camera.
-            RequestTemplate rootBuilder = new RequestTemplate
-                    (new CameraDeviceRequestBuilderFactory(mDevice));
-            // The shared image reader must be wired to receive every timestamp
-            // for every image (including the preview).
-            rootBuilder.addResponseListener(
-                    new TimestampResponseListener(globalTimestampCallback));
-
-            rootBuilder.addResponseListener(new PreviewFpsListener());
-
-            // TODO Change AE mode depending on flash mode.
-            rootBuilder.setParam(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
-                    .CONTROL_AE_MODE_ON);
-
-            rootBuilder.addStream(zslImageStream);
-
-            rootBuilder.addResponseListener(new TotalCaptureResultResponseListener
-                    (metadataCallback));
-
-            ZoomedCropRegion cropRegion = new ZoomedCropRegion(mCameraCharacteristics,
-                    zoomState);
-            rootBuilder.setParam(CaptureRequest.SCALER_CROP_REGION, cropRegion);
-
-            CaptureStream previewStream = new SimpleCaptureStream(previewSurface);
-            rootBuilder.addStream(previewStream);
-
-            ScheduledExecutorService miscThreadPool = Executors.newScheduledThreadPool(1);
-
-            CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(miscThreadPool);
-            PreviewCommand previewCommand = new PreviewCommand(frameServer, rootBuilder,
-                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
-
-            Runnable previewRunner = new RunnableCameraCommand(cameraCommandExecutor,
-                    previewCommand);
-
-            // Resend the repeating preview request when the zoom changes to
-            // apply the new crop factor.
-            zoomState.addCallback(new CallbackRunnable(previewRunner), miscThreadPool);
-
-            OrientationManager.DeviceOrientation sensorOrientation = getSensorOrientation();
-
-            ManualAutoFocusFactory manualAutoFocusFactory = new ManualAutoFocusFactory(new
-                    Lifetime(cameraLifetime), frameServer, miscThreadPool, cropRegion,
-                    sensorOrientation, previewRunner, rootBuilder,
-                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
-            ManualAutoFocus autoFocus = manualAutoFocusFactory.provideManualAutoFocus();
-            Supplier<MeteringRectangle[]> aeRegions =
-                    manualAutoFocusFactory.provideAEMeteringRegion();
-            Supplier<MeteringRectangle[]> afRegions =
-                    manualAutoFocusFactory.provideAFMeteringRegion();
-
-            rootBuilder.setParam(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
-            rootBuilder.setParam(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
-
-            HandlerExecutor mainExecutor = new HandlerExecutor(mMainHandler);
-
-            // Used to rotate images the right way based on the sensor used for taking the image.
-            ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
-                    .from(mCameraCharacteristics);
-
-            ImageSaver.Builder imageSaverBuilder = new YuvImageBackendImageSaver(mainExecutor,
-                    imageRotationCalculator);
-
-            ZslPictureTakerFactory pictureTakerFactory = new ZslPictureTakerFactory(mainExecutor,
-                    cameraCommandExecutor, imageSaverBuilder, frameServer, rootBuilder,
-                    imageStreamFactory, zslImageStream);
-            PictureTaker pictureTaker = pictureTakerFactory.providePictureTaker();
-
-            previewRunner.run();
-
-            return new CameraControls(pictureTaker, autoFocus);
-        }
-
-        private OrientationManager.DeviceOrientation getSensorOrientation() {
-            Integer degrees = mCameraCharacteristics.get(CameraCharacteristics
-                    .SENSOR_ORIENTATION);
-
-            switch (degrees) {
-                case 0:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-                case 90:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_90;
-                case 180:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_180;
-                case 270:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_270;
-                default:
-                    return OrientationManager.DeviceOrientation.CLOCKWISE_0;
-            }
-        }
+    public ZslOneCameraFactory(int imageFormat, int maxImageCount) {
+        mImageFormat = imageFormat;
+        mMaxImageCount = maxImageCount;
     }
 
-    private final InitializedOneCameraFactory mInitializedOneCameraFactory;
+    @Override
+    public OneCamera createOneCamera(final CameraDeviceProxy device,
+            final OneCameraCharacteristics characteristics,
+            final MainThreadExecutor mainThreadExecutor,
+            Size pictureSize,
+            final Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting) {
 
-    public ZslOneCameraFactory(CameraDevice cameraDevice,
-            CameraCharacteristics characteristics, Handler mainHandler, Size pictureSize) {
-        CameraDeviceProxy device = new CameraDeviceProxy(cameraDevice);
-
-        int imageFormat = ImageFormat.YUV_420_888;
-        // TODO This is totally arbitrary, and could probably be increased.
-        int maxImageCount = 10;
-
-        ImageReader imageReader = ImageReader.newInstance(pictureSize.getWidth(),
-                pictureSize.getHeight(), imageFormat, maxImageCount);
-
+        final ImageReader imageReader = ImageReader.newInstance(pictureSize.getWidth(),
+                pictureSize.getHeight(), mImageFormat, mMaxImageCount);
         // FIXME TODO Close the ImageReader when all images have been freed!
 
         List<Surface> outputSurfaces = new ArrayList<>();
         outputSurfaces.add(imageReader.getSurface());
 
-        CameraStarter cameraStarter = new CameraStarterImpl(device, characteristics, imageReader,
-                mainHandler);
+        /**
+         * Finishes constructing the camera when prerequisites, e.g. the preview
+         * stream and capture session, are ready.
+         */
+        CameraStarter cameraStarter = new CameraStarter() {
+            @Override
+            public CameraControls startCamera(Lifetime cameraLifetime,
+                    CameraCaptureSessionProxy cameraCaptureSession,
+                    Surface previewSurface,
+                    Observable<Float> zoomState,
+                    Updatable<TotalCaptureResult> metadataCallback,
+                    Updatable<Boolean> readyState) {
+                // Create the FrameServer from the CaptureSession.
+                FrameServer frameServer = new FrameServerFactory(
+                        new Lifetime(cameraLifetime), cameraCaptureSession, new HandlerFactory())
+                        .provideFrameServer();
 
-        mInitializedOneCameraFactory =
-                new InitializedOneCameraFactory(cameraStarter, device, characteristics,
-                        outputSurfaces, imageFormat, mainHandler);
-    }
+                // Create a thread pool on which to execute camera operations.
+                ScheduledExecutorService miscThreadPool = Executors.newScheduledThreadPool(1);
 
-    public OneCamera provideOneCamera() {
-        return mInitializedOneCameraFactory.provideOneCamera();
+                // Create the shared image reader.
+                ZslSharedImageReaderFactory sharedImageReaderFactory =
+                        new ZslSharedImageReaderFactory(new Lifetime(cameraLifetime), imageReader);
+                Updatable<Long> globalTimestampCallback =
+                        sharedImageReaderFactory.provideGlobalTimestampQueue();
+                ImageStreamFactory imageStreamFactory =
+                        sharedImageReaderFactory.provideSharedImageReader();
+                ImageStream zslStream = sharedImageReaderFactory.provideZSLCaptureStream();
+
+                // Create the request builder used by all camera operations.
+                // Streams, ResponseListeners, and Parameters added to
+                // this will be applied to *all* requests sent to the camera.
+                RequestTemplate rootBuilder = new RequestTemplate
+                        (new CameraDeviceRequestBuilderFactory(device));
+                // The shared image reader must be wired to receive every
+                // timestamp for every image (including the preview).
+                rootBuilder.addResponseListener(
+                        new TimestampResponseListener(globalTimestampCallback));
+                rootBuilder.addStream(new SimpleCaptureStream(previewSurface));
+                rootBuilder.addStream(zslStream);
+
+                // Create basic functionality (zoom, AE, AF).
+                BasicCameraFactory basicCameraFactory = new BasicCameraFactory(new Lifetime
+                        (cameraLifetime), characteristics, frameServer, rootBuilder,
+                        miscThreadPool, flashSetting, zoomState, CameraDevice
+                        .TEMPLATE_ZERO_SHUTTER_LAG);
+
+                RequestBuilder.Factory meteredZooomedRequestBuilder =
+                        basicCameraFactory.provideMeteredZoomedRequestBuilder();
+
+                // Create the image saver.
+                // Used to rotate images the right way based on the sensor used
+                // for taking the image.
+                ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
+                        .from(characteristics);
+                ImageSaver.Builder imageSaverBuilder = new YuvImageBackendImageSaver(
+                        mainThreadExecutor, imageRotationCalculator);
+
+                // Create the picture-taker.
+                CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(
+                        miscThreadPool);
+
+                ZslPictureTakerFactory pictureTakerFactory = new ZslPictureTakerFactory(
+                        mainThreadExecutor, cameraCommandExecutor, imageSaverBuilder, frameServer,
+                        meteredZooomedRequestBuilder, imageStreamFactory, zslStream);
+
+                basicCameraFactory.providePreviewStarter().run();
+
+                return new CameraControls(
+                        pictureTakerFactory.providePictureTaker(),
+                        basicCameraFactory.provideManualAutoFocus());
+            }
+        };
+
+        float maxZoom = characteristics.getAvailableMaxDigitalZoom();
+        List<Size> supportedPreviewSizes = characteristics.getSupportedPreviewSizes();
+        OneCamera.Facing direction = characteristics.getCameraDirection();
+        return new InitializedOneCameraFactory(cameraStarter, device,
+                outputSurfaces, mainThreadExecutor, new HandlerFactory(), maxZoom,
+                supportedPreviewSizes, direction).provideOneCamera();
     }
 }
diff --git a/src/com/android/camera/one/v2/autofocus/AEMeteringRegion.java b/src/com/android/camera/one/v2/autofocus/AEMeteringRegion.java
index 70b16ed..f0547ac 100644
--- a/src/com/android/camera/one/v2/autofocus/AEMeteringRegion.java
+++ b/src/com/android/camera/one/v2/autofocus/AEMeteringRegion.java
@@ -31,11 +31,11 @@
 class AEMeteringRegion implements Supplier<MeteringRectangle[]> {
     private final Supplier<MeteringParameters> mMeteringParameters;
     private final Supplier<Rect> mCropRegion;
-    private final OrientationManager.DeviceOrientation mSensorOrientation;
+    private final int mSensorOrientation;
 
     public AEMeteringRegion(Supplier<MeteringParameters> meteringParameters,
                             Supplier<Rect> cropRegion,
-                            OrientationManager.DeviceOrientation sensorOrientation) {
+                            int sensorOrientation) {
         mMeteringParameters = meteringParameters;
         mCropRegion = cropRegion;
         mSensorOrientation = sensorOrientation;
@@ -48,7 +48,7 @@
             Rect cropRegion = mCropRegion.get();
             PointF point = parameters.getAEPoint();
             return AutoFocusHelper.aeRegionsForNormalizedCoord(point.x, point.y, cropRegion,
-                    mSensorOrientation.getDegrees());
+                    mSensorOrientation);
         } else {
             return AutoFocusHelper.getZeroWeightRegion();
         }
diff --git a/src/com/android/camera/one/v2/autofocus/AFMeteringRegion.java b/src/com/android/camera/one/v2/autofocus/AFMeteringRegion.java
index 6f6b74a..bbb1843 100644
--- a/src/com/android/camera/one/v2/autofocus/AFMeteringRegion.java
+++ b/src/com/android/camera/one/v2/autofocus/AFMeteringRegion.java
@@ -31,11 +31,11 @@
 class AFMeteringRegion implements Supplier<MeteringRectangle[]> {
     private final Supplier<MeteringParameters> mMeteringParameters;
     private final Supplier<Rect> mCropRegion;
-    private final OrientationManager.DeviceOrientation mSensorOrientation;
+    private final int mSensorOrientation;
 
     public AFMeteringRegion(Supplier<MeteringParameters> meteringParameters,
                             Supplier<Rect> cropRegion,
-                            OrientationManager.DeviceOrientation sensorOrientation) {
+                            int sensorOrientation) {
         mMeteringParameters = meteringParameters;
         mCropRegion = cropRegion;
         mSensorOrientation = sensorOrientation;
@@ -48,7 +48,7 @@
             Rect cropRegion = mCropRegion.get();
             PointF point = parameters.getAEPoint();
             return AutoFocusHelper.afRegionsForNormalizedCoord(point.x, point.y, cropRegion,
-                    mSensorOrientation.getDegrees());
+                    mSensorOrientation);
         } else {
             return AutoFocusHelper.getZeroWeightRegion();
         }
diff --git a/src/com/android/camera/one/v2/autofocus/ManualAutoFocusFactory.java b/src/com/android/camera/one/v2/autofocus/ManualAutoFocusFactory.java
index d1b4d19..319e00c 100644
--- a/src/com/android/camera/one/v2/autofocus/ManualAutoFocusFactory.java
+++ b/src/com/android/camera/one/v2/autofocus/ManualAutoFocusFactory.java
@@ -60,7 +60,7 @@
      */
     public ManualAutoFocusFactory(Lifetime lifetime, FrameServer frameServer,
             ScheduledExecutorService threadPool, Supplier<Rect> cropRegion,
-            OrientationManager.DeviceOrientation sensorOrientation,
+            int sensorOrientation,
             Runnable previewRunner, RequestBuilder.Factory rootBuilder,
             int templateType) {
         CameraCommandExecutor commandExecutor = new CameraCommandExecutor(threadPool);
diff --git a/src/com/android/camera/one/v2/common/BasicCameraFactory.java b/src/com/android/camera/one/v2/common/BasicCameraFactory.java
new file mode 100644
index 0000000..1f04719
--- /dev/null
+++ b/src/com/android/camera/one/v2/common/BasicCameraFactory.java
@@ -0,0 +1,136 @@
+/*
+ * 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.one.v2.common;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.MeteringRectangle;
+
+import com.android.camera.app.OrientationManager;
+import com.android.camera.async.CallbackRunnable;
+import com.android.camera.async.Lifetime;
+import com.android.camera.async.Observable;
+import com.android.camera.async.SafeCloseable;
+import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCameraCharacteristics;
+import com.android.camera.one.v2.autofocus.ManualAutoFocus;
+import com.android.camera.one.v2.autofocus.ManualAutoFocusFactory;
+import com.android.camera.one.v2.commands.CameraCommandExecutor;
+import com.android.camera.one.v2.commands.PreviewCommand;
+import com.android.camera.one.v2.commands.RunnableCameraCommand;
+import com.android.camera.one.v2.core.FrameServer;
+import com.android.camera.one.v2.core.RequestBuilder;
+import com.android.camera.one.v2.core.RequestTemplate;
+import com.google.common.base.Supplier;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Wires together functionality common to all cameras:
+ * <ul>
+ * <li>Tap-to-focus</li>
+ * <li>Auto exposure, based on the current flash-setting</li>
+ * <li>Metering regions</li>
+ * <li>Zoom</li>
+ * <li>Logging of OS/driver-level errors</li>
+ * </ul>
+ * <p>
+ * Note that this does not include functionality for taking pictures, since this
+ * varies depending on hardware capability.
+ * </p>
+ */
+public class BasicCameraFactory {
+    private final ManualAutoFocus mManualAutoFocus;
+    private final RequestBuilder.Factory mMeteredZoomedRequestBuilder;
+    private final Runnable mPreviewStarter;
+    private OrientationManager.DeviceOrientation mSensorOrientation;
+
+    /**
+     * @param lifetime The lifetime of all created objects and their associated
+     *            resources.
+     * @param cameraCharacteristics
+     * @param rootBuilder Provides preconfigured request builders to be used for
+     *            all requests to mFrameServer.
+     * @param threadPool A dynamically-sized thread pool on which to interact
+     * @param templateType
+     */
+    public BasicCameraFactory(Lifetime lifetime,
+            OneCameraCharacteristics cameraCharacteristics,
+            FrameServer frameServer,
+            RequestBuilder.Factory rootBuilder,
+            ScheduledExecutorService threadPool,
+            Observable<OneCamera.PhotoCaptureParameters.Flash> flash,
+            Observable<Float> zoom, int templateType) {
+        RequestTemplate previewBuilder = new RequestTemplate(rootBuilder);
+        previewBuilder.setParam(
+                CaptureRequest.CONTROL_AE_MODE, new FlashBasedAEMode(flash));
+
+        Supplier<Rect> cropRegion = new ZoomedCropRegion(
+                cameraCharacteristics.getSensorInfoActiveArraySize(), zoom);
+        previewBuilder.setParam(CaptureRequest.SCALER_CROP_REGION, cropRegion);
+
+        CameraCommandExecutor cameraCommandExecutor = new CameraCommandExecutor(threadPool);
+        lifetime.add(cameraCommandExecutor);
+        PreviewCommand previewCommand = new PreviewCommand(frameServer, previewBuilder,
+                templateType);
+
+        mPreviewStarter = new RunnableCameraCommand(cameraCommandExecutor,
+                previewCommand);
+
+        // Resend the repeating preview request when the zoom or flash state
+        // changes to apply the new setting.
+        // Also, de-register these callbacks when the camera is closed (to
+        // not leak memory).
+        SafeCloseable zoomCallback = zoom.addCallback(new CallbackRunnable(mPreviewStarter),
+                threadPool);
+        lifetime.add(zoomCallback);
+        SafeCloseable flashCallback = flash.addCallback(new CallbackRunnable(mPreviewStarter),
+                threadPool);
+        lifetime.add(flashCallback);
+
+        int sensorOrientation =
+                cameraCharacteristics.getSensorOrientation();
+
+        ManualAutoFocusFactory manualAutoFocusFactory = new ManualAutoFocusFactory(new
+                Lifetime(lifetime), frameServer, threadPool, cropRegion,
+                sensorOrientation, mPreviewStarter, rootBuilder,
+                CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+        mManualAutoFocus = manualAutoFocusFactory.provideManualAutoFocus();
+        Supplier<MeteringRectangle[]> aeRegions =
+                manualAutoFocusFactory.provideAEMeteringRegion();
+        Supplier<MeteringRectangle[]> afRegions =
+                manualAutoFocusFactory.provideAFMeteringRegion();
+
+        previewBuilder.setParam(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+        previewBuilder.setParam(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
+
+        mMeteredZoomedRequestBuilder = previewBuilder;
+    }
+
+    public RequestBuilder.Factory provideMeteredZoomedRequestBuilder() {
+        return mMeteredZoomedRequestBuilder;
+    }
+
+    public ManualAutoFocus provideManualAutoFocus() {
+        return mManualAutoFocus;
+    }
+
+    public Runnable providePreviewStarter() {
+        return mPreviewStarter;
+    }
+}
diff --git a/src/com/android/camera/one/v2/common/ZoomedCropRegion.java b/src/com/android/camera/one/v2/common/ZoomedCropRegion.java
index dd89088..19d6535 100644
--- a/src/com/android/camera/one/v2/common/ZoomedCropRegion.java
+++ b/src/com/android/camera/one/v2/common/ZoomedCropRegion.java
@@ -25,18 +25,18 @@
  * Computes the current crop region based on the current zoom.
  */
 public class ZoomedCropRegion implements Supplier<Rect> {
-    private final CameraCharacteristics mCharacteristics;
+    private final Rect mSensorArrayArea;
     private final Supplier<Float> mZoom;
 
-    public ZoomedCropRegion(CameraCharacteristics characteristics, Supplier<Float> zoom) {
-        mCharacteristics = characteristics;
+    public ZoomedCropRegion(Rect sensorArrayArea, Supplier<Float> zoom) {
+        mSensorArrayArea = sensorArrayArea;
         mZoom = zoom;
     }
 
     @Override
     public Rect get() {
         float zoom = mZoom.get();
-        Rect sensor = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        Rect sensor = mSensorArrayArea;
         int xCenter = sensor.width() / 2;
         int yCenter = sensor.height() / 2;
         int xDelta = (int) (0.5f * sensor.width() / zoom);
diff --git a/src/com/android/camera/one/v2/core/FrameServerFactory.java b/src/com/android/camera/one/v2/core/FrameServerFactory.java
index 3304b6c..6859434 100644
--- a/src/com/android/camera/one/v2/core/FrameServerFactory.java
+++ b/src/com/android/camera/one/v2/core/FrameServerFactory.java
@@ -16,21 +16,24 @@
 
 package com.android.camera.one.v2.core;
 
+import android.os.Handler;
+
 import com.android.camera.async.CloseableHandlerThread;
+import com.android.camera.async.HandlerFactory;
 import com.android.camera.async.Lifetime;
 import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
 
 public class FrameServerFactory {
     private FrameServer mFrameServer;
 
-    public FrameServerFactory(Lifetime lifetime, CameraCaptureSessionProxy cameraCaptureSession) {
-        CloseableHandlerThread cameraHandler = new CloseableHandlerThread("CameraMetadataHandler");
-        lifetime.add(cameraHandler);
+    public FrameServerFactory(Lifetime lifetime, CameraCaptureSessionProxy cameraCaptureSession,
+            HandlerFactory handlerFactory) {
+        Handler cameraHandler = handlerFactory.create(lifetime, "CameraMetadataHandler");
         // TODO Maybe enable closing the FrameServer along with the lifetime?
         // It would allow clean reuse of the cameraCaptureSession with
         // non-frameserver interaction
         mFrameServer = new FrameServerImpl(new TagDispatchCaptureSession(cameraCaptureSession,
-                cameraHandler.get()));
+                cameraHandler));
     }
 
     public FrameServer provideFrameServer() {
diff --git a/src/com/android/camera/one/v2/initialization/CameraStarter.java b/src/com/android/camera/one/v2/initialization/CameraStarter.java
index b6e4242..f92f63b 100644
--- a/src/com/android/camera/one/v2/initialization/CameraStarter.java
+++ b/src/com/android/camera/one/v2/initialization/CameraStarter.java
@@ -21,6 +21,7 @@
 
 import com.android.camera.async.ConcurrentState;
 import com.android.camera.async.Lifetime;
+import com.android.camera.async.Observable;
 import com.android.camera.async.Updatable;
 import com.android.camera.one.v2.autofocus.ManualAutoFocus;
 import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
@@ -64,7 +65,7 @@
             Lifetime cameraLifetime,
             CameraCaptureSessionProxy cameraCaptureSession,
             Surface previewSurface,
-            ConcurrentState<Float> zoomState,
+            Observable<Float> zoomState,
             Updatable<TotalCaptureResult> metadataCallback,
             Updatable<Boolean> readyStateCallback);
 }
diff --git a/src/com/android/camera/one/v2/initialization/GenericOneCameraImpl.java b/src/com/android/camera/one/v2/initialization/GenericOneCameraImpl.java
index 9c51060..e24c11f 100644
--- a/src/com/android/camera/one/v2/initialization/GenericOneCameraImpl.java
+++ b/src/com/android/camera/one/v2/initialization/GenericOneCameraImpl.java
@@ -58,27 +58,19 @@
     private final Listenable<Boolean> mReadyStateListenable;
     private final float mMaxZoom;
     private final Updatable<Float> mZoom;
-    private final Size[] mSupportedPreviewSizes;
-    private final float mFullSizeAspectRatio;
     private final Facing mDirection;
     private final PreviewSizeSelector mPreviewSizeSelector;
-    private final Listenable<Boolean> mPreviewStartSuccessListenable;
     private final PreviewStarter mPreviewStarter;
 
     public GenericOneCameraImpl(SafeCloseable closeListener, PictureTaker pictureTaker,
             ManualAutoFocus manualAutoFocus, Executor mainExecutor,
             Listenable<Integer> afStateProvider, Listenable<FocusState> focusStateProvider,
             Listenable<Boolean> readyStateListenable, float maxZoom, Updatable<Float> zoom,
-            Size[] supportedPreviewSizes, float fullSizeAspectRatio, Facing direction,
-            PreviewSizeSelector previewSizeSelector,
-            Listenable<Boolean> previewStartSuccessListenable,
+            Facing direction, PreviewSizeSelector previewSizeSelector,
             PreviewStarter previewStarter) {
-        mPreviewStartSuccessListenable = previewStartSuccessListenable;
         mCloseListener = closeListener;
         mMainExecutor = mainExecutor;
         mMaxZoom = maxZoom;
-        mSupportedPreviewSizes = supportedPreviewSizes;
-        mFullSizeAspectRatio = fullSizeAspectRatio;
         mDirection = direction;
         mPreviewSizeSelector = previewSizeSelector;
         mPictureTaker = pictureTaker;
@@ -166,16 +158,6 @@
     }
 
     @Override
-    public Size[] getSupportedPreviewSizes() {
-        return mSupportedPreviewSizes;
-    }
-
-    @Override
-    public float getFullSizeAspectRatio() {
-        return mFullSizeAspectRatio;
-    }
-
-    @Override
     public Facing getDirection() {
         return mDirection;
     }
diff --git a/src/com/android/camera/one/v2/initialization/InitializedOneCameraFactory.java b/src/com/android/camera/one/v2/initialization/InitializedOneCameraFactory.java
index 3467f7b..43de683 100644
--- a/src/com/android/camera/one/v2/initialization/InitializedOneCameraFactory.java
+++ b/src/com/android/camera/one/v2/initialization/InitializedOneCameraFactory.java
@@ -17,20 +17,14 @@
 package com.android.camera.one.v2.initialization;
 
 import android.annotation.TargetApi;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.params.StreamConfigurationMap;
 import android.os.Build;
 import android.os.Handler;
 import android.view.Surface;
 
-import com.android.camera.async.CloseableHandlerThread;
 import com.android.camera.async.ConcurrentState;
 import com.android.camera.async.FilteredUpdatable;
-import com.android.camera.async.HandlerExecutor;
+import com.android.camera.async.HandlerFactory;
 import com.android.camera.async.Lifetime;
 import com.android.camera.async.Listenable;
 import com.android.camera.async.ListenableConcurrentState;
@@ -65,53 +59,30 @@
  */
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class InitializedOneCameraFactory {
-    private final CameraStarter mCameraStarter;
-    private final CameraDeviceProxy mDevice;
-    private final CameraCharacteristics mCameraCharacteristics;
-    private final List<Surface> mOutputSurfaces;
-    private final int mImageFormat;
-    private final Handler mMainHandler;
+    private final GenericOneCameraImpl mOneCamera;
 
     /**
      * @param cameraStarter Starts the camera, after initialization of the
      *            preview stream and capture session is complete.
      * @param device
-     * @param cameraCharacteristics
      * @param outputSurfaces The set of output Surfaces (excluding the
      *            not-yet-available preview Surface) to use when configuring the
      *            capture session.
-     * @param imageFormat The image format of the Surface for full-size images
-     *            to be saved.
-     * @param mainHandler
      */
     public InitializedOneCameraFactory(
-            CameraStarter cameraStarter,
-            CameraDeviceProxy device,
-            CameraCharacteristics cameraCharacteristics,
-            List<Surface> outputSurfaces,
-            int imageFormat,
-            Handler mainHandler) {
-        mCameraStarter = cameraStarter;
-        mDevice = device;
-        mCameraCharacteristics = cameraCharacteristics;
-        mOutputSurfaces = outputSurfaces;
-        mImageFormat = imageFormat;
-        mMainHandler = mainHandler;
-    }
-
-    public OneCamera provideOneCamera() {
+            final CameraStarter cameraStarter, CameraDeviceProxy device,
+            List<Surface> outputSurfaces, Executor mainThreadExecutor,
+            HandlerFactory handlerFactory, float maxZoom, List<Size> supportedPreviewSizes,
+            OneCamera.Facing direction) {
         // Assembles and returns a OneCamera based on the CameraStarter.
 
         // All resources tied to the camera are contained (directly or
         // transitively) by this.
         final Lifetime cameraLifetime = new Lifetime();
-        cameraLifetime.add(mDevice);
+        cameraLifetime.add(device);
 
         // Create/wrap required threads.
-        Executor mainThreadExecutor = new HandlerExecutor(mMainHandler);
-
-        final CloseableHandlerThread cameraHandler = new CloseableHandlerThread("CameraHandler");
-        cameraLifetime.add(cameraHandler);
+        final Handler cameraHandler = handlerFactory.create(cameraLifetime, "CameraHandler");
 
         final ExecutorService miscThreadPool = Executors.newCachedThreadPool();
 
@@ -137,7 +108,6 @@
         final ConcurrentState<Integer> afMode = new ConcurrentState<>(CaptureResult
                 .CONTROL_AF_MODE_OFF);
         final ConcurrentState<Boolean> readyState = new ConcurrentState<>(false);
-        final ConcurrentState<Boolean> previewStartSuccessState = new ConcurrentState<>(null);
 
         // Wrap state to be able to register listeners which run on the main
         // thread.
@@ -147,8 +117,6 @@
                 focusState, mainThreadExecutor);
         Listenable<Boolean> readyStateListenable = new ListenableConcurrentState<>(readyState,
                 mainThreadExecutor);
-        Listenable<Boolean> previewStateListenable = new ListenableConcurrentState<>
-                (previewStartSuccessState, mainThreadExecutor);
 
         // Wrap each value in a filter to ensure that only differences pass
         // through.
@@ -168,16 +136,16 @@
 
         // Note that these must be created in reverse-order to when they are run
         // because each stage depends on the previous one.
-        final CaptureSessionCreator captureSessionCreator = new CaptureSessionCreator(mDevice,
-                cameraHandler.get());
+        final CaptureSessionCreator captureSessionCreator = new CaptureSessionCreator(device,
+                cameraHandler);
 
-        PreviewStarter mPreviewStarter = new PreviewStarter(mOutputSurfaces,
+        PreviewStarter mPreviewStarter = new PreviewStarter(outputSurfaces,
                 captureSessionCreator,
                 new PreviewStarter.CameraCaptureSessionCreatedListener() {
                     @Override
                     public void onCameraCaptureSessionCreated(CameraCaptureSessionProxy session,
                             Surface previewSurface) {
-                        CameraStarter.CameraControls controls = mCameraStarter.startCamera(
+                        CameraStarter.CameraControls controls = cameraStarter.startCamera(
                                 new Lifetime(cameraLifetime),
                                 session, previewSurface,
                                 zoomState, metadataCallback, readyState);
@@ -186,47 +154,14 @@
                     }
                 });
 
-        // Various constants/functionality which OneCamera implementations must
-        // provide which do not depend on anything other than the
-        // characteristics.
-        PreviewSizeSelector previewSizeSelector = new PreviewSizeSelector(mImageFormat,
-                getSupportedPreviewSizes());
-        float maxZoom = getMaxZoom();
-        Size[] supportedPreviewSizes = getSupportedPreviewSizes();
-        float fullSizeAspectRatio = getFullSizeAspectRatio();
-        OneCamera.Facing direction = getDirection();
+        PreviewSizeSelector previewSizeSelector = new PreviewSizeSelector(supportedPreviewSizes);
 
-        return new GenericOneCameraImpl(cameraLifetime, pictureTaker,
-                manualAutoFocus, mainThreadExecutor, afStateListenable, focusStateListenable,
-                readyStateListenable, maxZoom, zoomState, supportedPreviewSizes,
-                fullSizeAspectRatio, direction, previewSizeSelector, previewStateListenable,
-                mPreviewStarter);
-
+        mOneCamera = new GenericOneCameraImpl(cameraLifetime, pictureTaker, manualAutoFocus,
+                mainThreadExecutor, afStateListenable, focusStateListenable, readyStateListenable,
+                maxZoom, zoomState, direction, previewSizeSelector, mPreviewStarter);
     }
 
-    private OneCamera.Facing getDirection() {
-        switch (mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING)) {
-            case CameraMetadata.LENS_FACING_BACK:
-                return OneCamera.Facing.BACK;
-            case CameraMetadata.LENS_FACING_FRONT:
-                return OneCamera.Facing.FRONT;
-        }
-        return OneCamera.Facing.BACK;
-    }
-
-    private float getFullSizeAspectRatio() {
-        Rect activeArraySize = mCameraCharacteristics.get(
-                CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-        return ((float) (activeArraySize.width())) / activeArraySize.height();
-    }
-
-    private Size[] getSupportedPreviewSizes() {
-        StreamConfigurationMap config = mCameraCharacteristics
-                .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        return Size.convert(config.getOutputSizes(SurfaceTexture.class));
-    }
-
-    private float getMaxZoom() {
-        return mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+    public OneCamera provideOneCamera() {
+        return mOneCamera;
     }
 }
diff --git a/src/com/android/camera/one/v2/initialization/PreviewSizeSelector.java b/src/com/android/camera/one/v2/initialization/PreviewSizeSelector.java
index c36a5e2..a28c2d5 100644
--- a/src/com/android/camera/one/v2/initialization/PreviewSizeSelector.java
+++ b/src/com/android/camera/one/v2/initialization/PreviewSizeSelector.java
@@ -17,56 +17,51 @@
 package com.android.camera.one.v2.initialization;
 
 import android.content.Context;
-import android.graphics.ImageFormat;
 
 import com.android.camera.CaptureModuleUtil;
 import com.android.camera.util.Size;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
 /**
- * Picks a preview size.
+ * Picks a preview size. TODO Remove dependency on static CaptureModuleUtil
+ * function and write tests.
  */
 class PreviewSizeSelector {
-    private final int mImageFormat;
-    private final Size[] mSupportedPreviewSizes;
+    private final List<Size> mSupportedPreviewSizes;
 
-    public PreviewSizeSelector(int imageFormat, Size[] supportedPreviewSizes) {
-        mImageFormat = imageFormat;
-        mSupportedPreviewSizes = supportedPreviewSizes;
+    public PreviewSizeSelector(List<Size> supportedPreviewSizes) {
+        mSupportedPreviewSizes = new ArrayList<>(supportedPreviewSizes);
     }
 
     public Size pickPreviewSize(Size pictureSize, Context context) {
         if (pictureSize == null) {
             // TODO The default should be selected by the caller, and
             // pictureSize should never be null.
-            pictureSize = getDefaultPictureSize();
+            pictureSize = getLargestPictureSize();
         }
         float pictureAspectRatio = pictureSize.getWidth() / (float) pictureSize.getHeight();
 
-        // Since devices only have one raw resolution we need to be more
-        // flexible for selecting a matching preview resolution.
-        Double aspectRatioTolerance = mImageFormat == ImageFormat.RAW_SENSOR ? 10d : null;
-        Size size = CaptureModuleUtil.getOptimalPreviewSize(context, mSupportedPreviewSizes,
-                pictureAspectRatio, aspectRatioTolerance);
+        Size size = CaptureModuleUtil.getOptimalPreviewSize(context,
+                (Size[]) mSupportedPreviewSizes.toArray(new Size[mSupportedPreviewSizes.size()]),
+                pictureAspectRatio, null);
         return size;
     }
 
     /**
      * @return The largest supported picture size.
      */
-    private Size getDefaultPictureSize() {
-        Size[] supportedSizes = mSupportedPreviewSizes;
-
-        // Find the largest supported size.
-        Size largestSupportedSize = supportedSizes[0];
-        long largestSupportedSizePixels =
-                largestSupportedSize.getWidth() * largestSupportedSize.getHeight();
-        for (int i = 1; i < supportedSizes.length; i++) {
-            long numPixels = supportedSizes[i].getWidth() * supportedSizes[i].getHeight();
-            if (numPixels > largestSupportedSizePixels) {
-                largestSupportedSize = supportedSizes[i];
-                largestSupportedSizePixels = numPixels;
+    private Size getLargestPictureSize() {
+        return Collections.max(mSupportedPreviewSizes, new Comparator<Size>() {
+            @Override
+            public int compare(Size size1, Size size2) {
+                int area1 = size1.getWidth() * size1.getHeight();
+                int area2 = size2.getWidth() * size2.getHeight();
+                return Integer.compare(area1, area2);
             }
-        }
-        return new Size(largestSupportedSize.getWidth(), largestSupportedSize.getHeight());
+        });
     }
 }
diff --git a/src/com/android/camera/one/v2/photo/ImageRotationCalculatorImpl.java b/src/com/android/camera/one/v2/photo/ImageRotationCalculatorImpl.java
index b20cca7..c999a53 100644
--- a/src/com/android/camera/one/v2/photo/ImageRotationCalculatorImpl.java
+++ b/src/com/android/camera/one/v2/photo/ImageRotationCalculatorImpl.java
@@ -17,10 +17,11 @@
 package com.android.camera.one.v2.photo;
 
 import android.annotation.TargetApi;
-import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
 
 import com.android.camera.app.OrientationManager;
+import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCameraCharacteristics;
 import com.android.camera.util.CameraUtil;
 
 /**
@@ -48,11 +49,11 @@
     /**
      * Create a calculator based on Camera characteristics.
      */
-    public static ImageRotationCalculator from(CameraCharacteristics characteristics) {
-        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
-        int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
+    public static ImageRotationCalculator from(OneCameraCharacteristics characteristics) {
+        int sensorOrientation = characteristics.getSensorOrientation();
+        OneCamera.Facing lensDirection = characteristics.getCameraDirection();
         return new ImageRotationCalculatorImpl(sensorOrientation,
-                lensFacing == CameraCharacteristics.LENS_FACING_FRONT);
+                lensDirection == OneCamera.Facing.FRONT);
     }
 
     @Override
diff --git a/src/com/android/camera/one/v2/photo/PictureTakerFactory.java b/src/com/android/camera/one/v2/photo/PictureTakerFactory.java
index dbf0caf..0439b3f 100644
--- a/src/com/android/camera/one/v2/photo/PictureTakerFactory.java
+++ b/src/com/android/camera/one/v2/photo/PictureTakerFactory.java
@@ -18,6 +18,7 @@
 
 import java.util.concurrent.Executor;
 
+import com.android.camera.async.MainThreadExecutor;
 import com.android.camera.one.v2.commands.CameraCommandExecutor;
 import com.android.camera.one.v2.core.FrameServer;
 import com.android.camera.one.v2.core.RequestBuilder;
@@ -26,7 +27,8 @@
 public class PictureTakerFactory {
     private final PictureTakerImpl mPictureTaker;
 
-    public PictureTakerFactory(Executor mainExecutor, CameraCommandExecutor commandExecutor,
+    public PictureTakerFactory(MainThreadExecutor mainExecutor,
+                               CameraCommandExecutor commandExecutor,
                                ImageSaver.Builder imageSaverBuilder,
                                FrameServer frameServer,
                                RequestBuilder.Factory rootRequestBuilder,
diff --git a/src/com/android/camera/one/v2/photo/PictureTakerImpl.java b/src/com/android/camera/one/v2/photo/PictureTakerImpl.java
index c4a31a4..a738081 100644
--- a/src/com/android/camera/one/v2/photo/PictureTakerImpl.java
+++ b/src/com/android/camera/one/v2/photo/PictureTakerImpl.java
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraAccessException;
 
 import com.android.camera.app.OrientationManager;
+import com.android.camera.async.MainThreadExecutor;
 import com.android.camera.async.Updatable;
 import com.android.camera.one.OneCamera;
 import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionClosedException;
@@ -31,14 +32,14 @@
 import java.util.concurrent.Executor;
 
 class PictureTakerImpl implements PictureTaker {
-    private final Executor mMainExecutor;
+    private final MainThreadExecutor mMainExecutor;
     private final CameraCommandExecutor mCameraCommandExecutor;
     private final ImageSaver.Builder mImageSaverBuilder;
     private final ImageCaptureCommand mFlashOffCommand;
     private final ImageCaptureCommand mFlashOnCommand;
     private final ImageCaptureCommand mFlashAutoCommand;
 
-    public PictureTakerImpl(Executor mainExecutor,
+    public PictureTakerImpl(MainThreadExecutor mainExecutor,
             CameraCommandExecutor cameraCommandExecutor,
             ImageSaver.Builder imageSaverBuilder,
             ImageCaptureCommand flashOffCommand,
diff --git a/src/com/android/camera/one/v2/photo/YuvImageBackendImageSaver.java b/src/com/android/camera/one/v2/photo/YuvImageBackendImageSaver.java
index 446b9f6..93120d8 100644
--- a/src/com/android/camera/one/v2/photo/YuvImageBackendImageSaver.java
+++ b/src/com/android/camera/one/v2/photo/YuvImageBackendImageSaver.java
@@ -18,6 +18,7 @@
 import android.net.Uri;
 
 import com.android.camera.app.OrientationManager;
+import com.android.camera.async.MainThreadExecutor;
 import com.android.camera.one.v2.camera2proxy.ImageProxy;
 import com.android.camera.processing.ProcessingServiceManager;
 import com.android.camera.processing.imagebackend.ImageBackend;
@@ -46,7 +47,7 @@
      * @param imageRotationCalculator the image rotation calculator to determine
      *            the final image rotation with
      */
-    public YuvImageBackendImageSaver(Executor executor,
+    public YuvImageBackendImageSaver(MainThreadExecutor executor,
             ImageRotationCalculator imageRotationCalculator) {
         mExecutor = executor;
         mImageRotationCalculator = imageRotationCalculator;
diff --git a/src/com/android/camera/one/v2/photo/ZslPictureTakerFactory.java b/src/com/android/camera/one/v2/photo/ZslPictureTakerFactory.java
index 4d27785..95c114b 100644
--- a/src/com/android/camera/one/v2/photo/ZslPictureTakerFactory.java
+++ b/src/com/android/camera/one/v2/photo/ZslPictureTakerFactory.java
@@ -35,6 +35,7 @@
 import java.util.concurrent.Executor;
 
 import com.android.camera.async.BufferQueue;
+import com.android.camera.async.MainThreadExecutor;
 import com.android.camera.one.v2.camera2proxy.ImageProxy;
 import com.android.camera.one.v2.commands.CameraCommandExecutor;
 import com.android.camera.one.v2.core.FrameServer;
@@ -44,7 +45,7 @@
 public class ZslPictureTakerFactory {
     private final PictureTakerImpl mPictureTaker;
 
-    public ZslPictureTakerFactory(Executor mainExecutor,
+    public ZslPictureTakerFactory(MainThreadExecutor mainExecutor,
                                   CameraCommandExecutor commandExecutor,
                                   ImageSaver.Builder imageSaverBuilder,
                                   FrameServer frameServer,
diff --git a/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributor.java b/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributor.java
index c1b2380..906c767 100644
--- a/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributor.java
+++ b/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributor.java
@@ -20,6 +20,9 @@
 import com.android.camera.async.BufferQueueController;
 import com.android.camera.one.v2.camera2proxy.ImageProxy;
 
+import javax.annotation.ParametersAreNonnullByDefault;
+
+@ParametersAreNonnullByDefault
 public interface ImageDistributor {
     /**
      * Begins routing new images with timestamps matching those found in
diff --git a/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributorImpl.java b/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributorImpl.java
index 69e426e..1f378de 100644
--- a/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributorImpl.java
+++ b/src/com/android/camera/one/v2/sharedimagereader/imagedistributor/ImageDistributorImpl.java
@@ -21,15 +21,18 @@
 import com.android.camera.one.v2.camera2proxy.ImageProxy;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import javax.annotation.ParametersAreNonnullByDefault;
+import javax.annotation.concurrent.GuardedBy;
+
 /**
  * Distributes incoming images to output {@link BufferQueueController}s
  * according to their timestamp.
  */
+@ParametersAreNonnullByDefault
 class ImageDistributorImpl implements ImageDistributor {
     /**
      * An input timestamp stream and an output image stream to receive images
@@ -51,6 +54,7 @@
      * the {@link BufferQueueController} to receive images with those
      * timestamps.
      */
+    @GuardedBy("mDispatchTable")
     private final Set<DispatchRecord> mDispatchTable;
 
     /**
@@ -68,7 +72,7 @@
      */
     public ImageDistributorImpl(BufferQueue<Long> globalTimestampBufferQueue) {
         mGlobalTimestampBufferQueue = globalTimestampBufferQueue;
-        mDispatchTable = Collections.synchronizedSet(new HashSet<DispatchRecord>());
+        mDispatchTable = new HashSet<>();
     }
 
     /**
@@ -85,8 +89,6 @@
      * @param image The image to distribute.
      */
     public void distributeImage(ImageProxy image) {
-        // TODO Profile GC impact, and pool all allocations if necessary &
-        // possible.
         final long timestamp = image.getTimestamp();
 
         // Wait until the global timestamp stream indicates that either the
@@ -98,7 +100,10 @@
         // stream associated with a {@link DispatchRecord} are updated on the
         // same thread in order.
         try {
-            while (mGlobalTimestampBufferQueue.getNext() <= timestamp) {
+            while (true) {
+                if (mGlobalTimestampBufferQueue.getNext() > timestamp) {
+                    break;
+                }
             }
         } catch (InterruptedException e) {
             image.close();
@@ -113,7 +118,10 @@
 
         // mDispatchTable may be modified in {@link #addRoute} while iterating,
         // so to avoid unnecessary locking, make a copy to iterate over.
-        Set<DispatchRecord> recordsToProcess = new HashSet<>(mDispatchTable);
+        Set<DispatchRecord> recordsToProcess;
+        synchronized (mDispatchTable) {
+            recordsToProcess = new HashSet<>(mDispatchTable);
+        }
         for (DispatchRecord dispatchRecord : recordsToProcess) {
             // If either the input timestampBufferQueue or the output
             // imageStream is closed, then the route can be removed.
@@ -125,14 +133,16 @@
             if (requestedImageTimestamp == null) {
                 continue;
             }
-            if (requestedImageTimestamp.longValue() == timestamp) {
+            if (requestedImageTimestamp == timestamp) {
                 // Discard the value we just looked at.
                 dispatchRecord.timestampBufferQueue.discardNext();
                 streamsToReceiveImage.add(dispatchRecord.imageStream);
             }
         }
 
-        mDispatchTable.removeAll(deadRecords);
+        synchronized (mDispatchTable) {
+            mDispatchTable.removeAll(deadRecords);
+        }
 
         RefCountedImageProxy sharedImage = new RefCountedImageProxy(image,
                 streamsToReceiveImage.size());
@@ -161,6 +171,8 @@
     @Override
     public void addRoute(BufferQueue<Long> inputTimestampBufferQueue,
             BufferQueueController<ImageProxy> outputStream) {
-        mDispatchTable.add(new DispatchRecord(inputTimestampBufferQueue, outputStream));
+        synchronized (mDispatchTable) {
+            mDispatchTable.add(new DispatchRecord(inputTimestampBufferQueue, outputStream));
+        }
     }
 }
diff --git a/src_pd/com/android/camera/one/v2/OneCameraCreator.java b/src_pd/com/android/camera/one/v2/OneCameraCreator.java
index afb96b3..8d6781d 100644
--- a/src_pd/com/android/camera/one/v2/OneCameraCreator.java
+++ b/src_pd/com/android/camera/one/v2/OneCameraCreator.java
@@ -21,12 +21,13 @@
 import android.hardware.camera2.CameraDevice;
 import android.util.DisplayMetrics;
 
+import com.android.camera.app.AppController;
 import com.android.camera.SoundPlayer;
 import com.android.camera.one.OneCamera;
 import com.android.camera.util.Size;
 
 public class OneCameraCreator {
-    public static OneCamera create(Context context, boolean useHdr, CameraDevice device,
+    public static OneCamera create(AppController context, boolean useHdr, CameraDevice device,
             CameraCharacteristics characteristics, Size pictureSize, int maxMemoryMB,
             DisplayMetrics displayMetrics, SoundPlayer soundPlayer) {
         // TODO: Might want to switch current camera to vendor HDR.