diff --git a/build.gradle b/build.gradle
index aa754d7d10..2137d0d309 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,9 +29,6 @@ repositories {
     maven { // textdrawable
         url 'https://dl.bintray.com/amulyakhare/maven'
     }
-    maven { // cwac-camera
-        url 'https://repo.commonsware.com.s3.amazonaws.com'
-    }
     jcenter()
     mavenLocal()
 }
@@ -72,7 +69,6 @@ dependencies {
         exclude group: 'com.android.support', module: 'support-v4'
     }
     compile 'com.madgag.spongycastle:prov:1.51.0.0'
-    compile 'com.commonsware.cwac:camera:0.6.12'
     provided 'com.squareup.dagger:dagger-compiler:1.2.2'
 
     compile 'org.whispersystems:jobmanager:1.0.2'
@@ -126,7 +122,6 @@ dependencyVerification {
             'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
             'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
             'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
-            'com.commonsware.cwac:camera:dcc93ddbb2f0393114fa1f31a13fe9e6edfcf5dbe96b22bc4b66c7b15e179054',
             'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
             'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
             'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
diff --git a/res/layout/quick_attachment_drawer.xml b/res/layout/quick_attachment_drawer.xml
index 1bb3907592..62ec6213ac 100644
--- a/res/layout/quick_attachment_drawer.xml
+++ b/res/layout/quick_attachment_drawer.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <org.thoughtcrime.securesms.components.camera.QuickCamera
+    <org.thoughtcrime.securesms.components.camera.CameraView
         android:id="@+id/quick_camera"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/res/layout/quick_camera_controls.xml b/res/layout/quick_camera_controls.xml
index 8d152887e0..345578c937 100644
--- a/res/layout/quick_camera_controls.xml
+++ b/res/layout/quick_camera_controls.xml
@@ -31,7 +31,7 @@
                  android:layout_alignParentBottom="true"
                  android:layout_alignParentLeft="true"
                  android:background="#00000000"
-                 android:src="@drawable/quick_camera_front"
+                 android:src="@drawable/quick_camera_rear"
                  android:padding="20dp"
                  android:visibility="invisible"
                  tools:visibility="visible" />
diff --git a/res/layout/quick_camera_controls_land.xml b/res/layout/quick_camera_controls_land.xml
index 2fc926f310..b00711eb4d 100644
--- a/res/layout/quick_camera_controls_land.xml
+++ b/res/layout/quick_camera_controls_land.xml
@@ -31,7 +31,7 @@
                  android:layout_alignParentTop="true"
                  android:layout_alignParentRight="true"
                  android:background="#00000000"
-                 android:src="@drawable/quick_camera_front"
+                 android:src="@drawable/quick_camera_rear"
                  android:padding="20dp"
                  android:visibility="invisible"
                  tools:visibility="visible"/>
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 77d2e3deb2..98687697c7 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -55,7 +55,6 @@ import android.widget.TextView;
 import android.widget.Toast;
 
 import com.afollestad.materialdialogs.AlertDialogWrapper;
-import com.commonsware.cwac.camera.CameraHost.FailureReason;
 import com.google.protobuf.ByteString;
 
 import org.thoughtcrime.redphone.RedPhone;
@@ -1344,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   }
 
   @Override
-  public void onCameraFail(FailureReason reason) {
+  public void onCameraFail() {
     Toast.makeText(this, R.string.ConversationActivity_quick_camera_unavailable, Toast.LENGTH_SHORT).show();
     quickAttachmentDrawer.hide(false);
     quickAttachmentToggle.disable();
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java b/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java
new file mode 100644
index 0000000000..7a991eae5c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java
@@ -0,0 +1,33 @@
+package org.thoughtcrime.securesms.components.camera;
+
+import android.content.Context;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+  private boolean ready;
+
+  @SuppressWarnings("deprecation")
+  public CameraSurfaceView(Context context) {
+    super(context);
+    getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+    getHolder().addCallback(this);
+  }
+
+  public boolean isReady() {
+    return ready;
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder holder) {
+    ready = true;
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder holder) {
+    ready = false;
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java
new file mode 100644
index 0000000000..9ee2b3d218
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java
@@ -0,0 +1,70 @@
+package org.thoughtcrime.securesms.components.camera;
+
+import android.annotation.TargetApi;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.Build.VERSION;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+@SuppressWarnings("deprecation")
+public class CameraUtils {
+  @TargetApi(11)
+  public static @Nullable Size getPreferredPreviewSize(int orientation, int width, int height, @NonNull Camera camera) {
+    final Parameters parameters    = camera.getParameters();
+    final Size       preferredSize = VERSION.SDK_INT > 11
+                                   ? parameters.getPreferredPreviewSizeForVideo()
+                                   : null;
+
+    return preferredSize == null ? getBestAspectPreviewSize(orientation, width, height, parameters)
+                                 : preferredSize;
+  }
+
+  /*
+   * modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
+   */
+  public static @Nullable Size getBestAspectPreviewSize(int displayOrientation,
+                                                        int width,
+                                                        int height,
+                                                        Parameters parameters) {
+    double targetRatio = (double)width / height;
+    Size   optimalSize = null;
+    double minDiff     = Double.MAX_VALUE;
+
+    if (displayOrientation == 90 || displayOrientation == 270) {
+      targetRatio = (double)height / width;
+    }
+
+    List<Size> sizes = parameters.getSupportedPreviewSizes();
+
+    Collections.sort(sizes, Collections.reverseOrder(new SizeComparator()));
+
+    for (Size size : sizes) {
+      double ratio = (double)size.width / size.height;
+
+      if (Math.abs(ratio - targetRatio) < minDiff) {
+        optimalSize = size;
+        minDiff     = Math.abs(ratio - targetRatio);
+      }
+    }
+
+    return optimalSize;
+  }
+
+  private static class SizeComparator implements Comparator<Size> {
+    @Override
+    public int compare(Size lhs, Size rhs) {
+      int left  = lhs.width * lhs.height;
+      int right = rhs.width * rhs.height;
+
+      if (left < right) return -1;
+      if (left > right) return 1;
+      else              return 0;
+    }
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
index 6be20ebb82..8c3ff03857 100644
--- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
@@ -20,44 +20,47 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.hardware.Camera;
-import android.hardware.Camera.PreviewCallback;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Build.VERSION;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.OrientationEventListener;
 import android.view.Surface;
-import android.view.View;
 import android.widget.FrameLayout;
 
 import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-
-import com.commonsware.cwac.camera.CameraHost;
-import com.commonsware.cwac.camera.CameraHost.FailureReason;
+import java.util.List;
 
 import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.util.BitmapUtil;
 import org.thoughtcrime.securesms.util.Util;
 import org.whispersystems.jobqueue.Job;
 import org.whispersystems.jobqueue.JobParameters;
+import org.whispersystems.libaxolotl.util.guava.Optional;
 
 @SuppressWarnings("deprecation")
 public class CameraView extends FrameLayout {
   private static final String TAG = CameraView.class.getSimpleName();
 
-  private          PreviewStrategy     previewStrategy        = null;
-  private          Camera.Size         previewSize            = null;
-  private volatile Camera              camera                 = null;
-  private          boolean             inPreview              = false;
-  private          boolean             cameraReady            = false;
-  private          CameraHost          host                   = null;
-  private          OnOrientationChange onOrientationChange    = null;
-  private          int                 displayOrientation     = -1;
-  private          int                 outputOrientation      = -1;
-  private          int                 cameraId               = -1;
-  private          int                 lastPictureOrientation = -1;
+  private final CameraSurfaceView   surface;
+  private final OnOrientationChange onOrientationChange;
+
+  private @NonNull volatile Optional<Camera> camera   = Optional.absent();
+  private          volatile int              cameraId = CameraInfo.CAMERA_FACING_BACK;
+
+  private           boolean            started;
+  private @Nullable CameraViewListener listener;
+  private           int                displayOrientation     = -1;
+  private           int                outputOrientation      = -1;
 
   public CameraView(Context context) {
     this(context, null);
@@ -71,51 +74,41 @@ public class CameraView extends FrameLayout {
     super(context, attrs, defStyle);
     setBackgroundColor(Color.BLACK);
 
-    onOrientationChange = new OnOrientationChange(context.getApplicationContext());
-  }
-
-  public CameraHost getHost() {
-    return host;
-  }
-
-  public void setHost(CameraHost host) {
-    this.host = host;
+    if (isMultiCamera()) cameraId = CameraInfo.CAMERA_FACING_FRONT;
 
-    if (host.getDeviceProfile().useTextureView()) {
-      previewStrategy = new TexturePreviewStrategy(this);
-    } else {
-      previewStrategy = new SurfacePreviewStrategy(this);
-    }
-    addView(previewStrategy.getWidget());
+    surface             = new CameraSurfaceView(getContext());
+    onOrientationChange = new OnOrientationChange(context.getApplicationContext());
+    addView(surface);
   }
 
   @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
   public void onResume() {
+    if (started) return;
+    started = true;
     Log.w(TAG, "onResume() queued");
-    final CameraHost host = getHost();
-    submitTask(new SerializedAsyncTask<FailureReason>() {
-      @Override protected FailureReason onRunBackground() {
+    enqueueTask(new SerialAsyncTask<Camera>() {
+      @Override
+      protected @Nullable Camera onRunBackground() {
         try {
-          cameraId = host.getCameraId();
           if (cameraId >= 0) {
-            camera = Camera.open(cameraId);
+            return Camera.open(cameraId);
           } else {
-            return FailureReason.NO_CAMERAS_REPORTED;
+            return null;
           }
         } catch (Exception e) {
-          return FailureReason.UNKNOWN;
+          return null;
         }
-
-        return null;
       }
 
-      @Override protected void onPostMain(FailureReason result) {
-        if (result != null) {
-          host.onCameraFail(result);
+      @Override
+      protected void onPostMain(@Nullable Camera camera) {
+        if (camera == null) {
+          if (listener != null) listener.onCameraFail();
           return;
         }
+
+        CameraView.this.camera = Optional.of(camera);
         try {
-          cameraReady = true;
           if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
             onOrientationChange.enable();
           }
@@ -123,227 +116,182 @@ public class CameraView extends FrameLayout {
           synchronized (CameraView.this) {
             CameraView.this.notifyAll();
           }
-          previewCreated();
-          initPreview();
+          onCameraReady();
           requestLayout();
           invalidate();
           Log.w(TAG, "onResume() completed");
-        } catch (RuntimeException re) {
-          Log.w(TAG, "exception when starting camera preview", re);
-          try {
-            previewDestroyed();
-          } catch (RuntimeException re2) {
-            Log.w(TAG, "also failed to release camera", re2);
-          }
+        } catch (RuntimeException e) {
+          Log.w(TAG, "exception when starting camera preview", e);
+          onPause();
         }
       }
     });
   }
 
   public void onPause() {
+    if (!started) return;
+    started = false;
     Log.w(TAG, "onPause() queued");
-    submitTask(new SerializedAsyncTask<Void>() {
+    final Optional<Camera> cameraToDestroy = camera;
+
+    enqueueTask(new SerialAsyncTask<Void>() {
       @Override protected void onPreMain() {
-        cameraReady = false;
+        camera = Optional.absent();
       }
 
       @Override protected Void onRunBackground() {
-        previewDestroyed();
+        if (cameraToDestroy.isPresent()) {
+          stopPreview();
+          cameraToDestroy.get().release();
+        }
         return null;
       }
 
       @Override protected void onPostMain(Void avoid) {
         onOrientationChange.disable();
-        previewSize = null;
         displayOrientation = -1;
         outputOrientation = -1;
-        cameraId = -1;
-        lastPictureOrientation = -1;
         Log.w(TAG, "onPause() completed");
       }
     });
   }
 
-  // based on CameraPreview.java from ApiDemos
+  public boolean isStarted() {
+    return started;
+  }
 
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-    if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) {
-      Camera.Size newSize = null;
-
-      try {
-        if (getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY) {
-          newSize = getHost().getPreferredPreviewSizeForVideo(getDisplayOrientation(),
-                                                              getMeasuredWidth(),
-                                                              getMeasuredHeight(),
-                                                              camera.getParameters(),
-                                                              null);
-        }
-        if (newSize == null || newSize.width * newSize.height < 65536) {
-          newSize = getHost().getPreviewSize(getDisplayOrientation(),
-                                             getMeasuredWidth(),
-                                             getMeasuredHeight(),
-                                             camera.getParameters());
-        }
-      } catch (Exception e) {
-        Log.e(TAG, "Could not work with camera parameters?", e);
-        // TODO get this out to library clients
-      }
-
-      if (newSize != null) {
-        if (previewSize == null) {
-          previewSize = newSize;
-          synchronized (this) { notifyAll(); }
-        } else if (previewSize.width != newSize.width || previewSize.height != newSize.height) {
-          if (inPreview) {
-            stopPreview();
-          }
-
-          previewSize = newSize;
-          synchronized (this) { notifyAll(); }
-          initPreview();
-        }
+    if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera.isPresent()) {
+      final Size preferredPreviewSize = CameraUtils.getPreferredPreviewSize(displayOrientation,
+                                                                            getMeasuredWidth(),
+                                                                            getMeasuredHeight(),
+                                                                            camera.get());
+      final Parameters parameters = camera.get().getParameters();
+      if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
+        stopPreview();
+        parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
+        camera.get().setParameters(parameters);
+        requestLayout();
+        startPreview();
       }
     }
-    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   }
 
-  // based on CameraPreview.java from ApiDemos
-
-  @SuppressWarnings("SuspiciousNameCombination") @Override
+  @SuppressWarnings("SuspiciousNameCombination")
+  @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
-    if (getChildCount() > 0) {
-      final View child         = getChildAt(0);
-      final int  width         = r - l;
-      final int  height        = b - t;
-      final int  previewWidth;
-      final int  previewHeight;
-
-      // handle orientation
-
-      if (previewSize != null && (getDisplayOrientation() == 90 || getDisplayOrientation() == 270)) {
+    final int  width         = r - l;
+    final int  height        = b - t;
+    final int  previewWidth;
+    final int  previewHeight;
+
+    if (camera.isPresent()) {
+      final Size previewSize = camera.get().getParameters().getPreviewSize();
+      if (displayOrientation == 90 || displayOrientation == 270) {
         previewWidth  = previewSize.height;
         previewHeight = previewSize.width;
-      } else if (previewSize != null) {
+      } else {
         previewWidth  = previewSize.width;
         previewHeight = previewSize.height;
-      } else {
-        previewWidth  = width;
-        previewHeight = height;
-      }
-
-      if (previewHeight == 0 || previewWidth == 0) {
-        Log.w(TAG, "skipping layout due to zero-width/height preview size");
-        return;
       }
+    } else {
+      previewWidth  = width;
+      previewHeight = height;
+    }
 
-      boolean useFirstStrategy = (width * previewHeight > height * previewWidth);
-      boolean useFullBleed     = getHost().useFullBleedPreview();
+    if (previewHeight == 0 || previewWidth == 0) {
+      Log.w(TAG, "skipping layout due to zero-width/height preview size");
+      return;
+    }
+    Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight);
 
-      if ((useFirstStrategy && !useFullBleed) || (!useFirstStrategy && useFullBleed)) {
-        final int scaledChildWidth = previewWidth * height / previewHeight;
-        child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
-      } else {
-        final int scaledChildHeight = previewHeight * width / previewWidth;
-        child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
-      }
+    if (width * previewHeight > height * previewWidth) {
+      final int scaledChildHeight = previewHeight * width / previewWidth;
+      surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
+    } else {
+      final int scaledChildWidth = previewWidth * height / previewHeight;
+      surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
     }
   }
 
-  public int getDisplayOrientation() {
-    return displayOrientation;
+  public void setListener(@Nullable CameraViewListener listener) {
+    this.listener = listener;
   }
 
-  public void setOneShotPreviewCallback(PreviewCallback callback) {
-    if (camera != null) camera.setOneShotPreviewCallback(callback);
+  public boolean isMultiCamera() {
+    return Camera.getNumberOfCameras() > 1;
   }
 
-  public @Nullable Camera.Parameters getCameraParameters() {
-    return camera == null || !cameraReady ? null : camera.getParameters();
+  public boolean isRearCamera() {
+    return cameraId == CameraInfo.CAMERA_FACING_BACK;
   }
 
-  void previewCreated() {
-    Log.w(TAG, "previewCreated() queued");
-    final CameraHost host = getHost();
-    submitTask(new PostInitializationTask<Void>() {
-      @Override protected void onPostMain(Void avoid) {
-        try {
-          if (camera != null) {
-            previewStrategy.attach(camera);
-          }
-        } catch (IOException e) {
-          host.handleException(e);
-        }
-        Log.w(TAG, "previewCreated() completed");
-      }
-    });
-  }
-
-  void previewDestroyed() {
-    try {
-      if (camera != null) {
-        previewStopped();
-        camera.release();
-      }
-    } finally {
-      camera = null;
+  public void flipCamera() {
+    if (Camera.getNumberOfCameras() > 1) {
+      cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
+                 ? CameraInfo.CAMERA_FACING_FRONT
+                 : CameraInfo.CAMERA_FACING_BACK;
+      onPause();
+      onResume();
     }
   }
 
-  private void previewStopped() {
-    if (inPreview) {
-      stopPreview();
-    }
-  }
 
-  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-  public void initPreview() {
-    Log.w(TAG, "initPreview() queued");
-    submitTask(new PostInitializationTask<Void>() {
-      @Override protected void onPostMain(Void avoid) {
-        if (camera != null && cameraReady) {
-          Camera.Parameters parameters = camera.getParameters();
+  @TargetApi(14)
+  private void onCameraReady() {
+    if (!camera.isPresent()) return;
 
-          parameters.setPreviewSize(previewSize.width, previewSize.height);
+    final Parameters   parameters = camera.get().getParameters();
+    final List<String> focusModes = parameters.getSupportedFocusModes();
 
-          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            parameters.setRecordingHint(getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY);
-          }
+    if (VERSION.SDK_INT >= 14) parameters.setRecordingHint(true);
 
-          camera.setParameters(getHost().adjustPreviewParameters(parameters));
-          startPreview();
-          requestLayout();
-          invalidate();
-          Log.w(TAG, "initPreview() completed");
+    if (VERSION.SDK_INT >= 14 && focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+      parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+    } else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
+      parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+    }
+
+    camera.get().setParameters(parameters);
+
+    enqueueTask(new PostInitializationTask<Void>() {
+      @Override protected void onPostMain(Void avoid) {
+        if (camera.isPresent()) {
+          try {
+            camera.get().setPreviewDisplay(surface.getHolder());
+            startPreview();
+          } catch (IOException e) {
+            Log.w(TAG, e);
+          }
         }
       }
     });
   }
 
   private void startPreview() {
-    camera.startPreview();
-    inPreview = true;
-    getHost().autoFocusAvailable();
+    if (camera.isPresent()) {
+      camera.get().startPreview();
+    }
   }
 
   private void stopPreview() {
-    camera.startPreview();
-    inPreview = false;
-    getHost().autoFocusUnavailable();
-    camera.stopPreview();
+    if (camera.isPresent()) {
+      camera.get().stopPreview();
+    }
   }
 
   // based on
   // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
   // and http://stackoverflow.com/a/10383164/115145
   private void setCameraDisplayOrientation() {
-    Camera.CameraInfo info     = new Camera.CameraInfo();
+    Camera.CameraInfo info     = getCameraInfo();
     int               rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
     int               degrees  = 0;
     DisplayMetrics    dm       = new DisplayMetrics();
 
-    Camera.getCameraInfo(cameraId, info);
     getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
 
     switch (rotation) {
@@ -361,64 +309,51 @@ public class CameraView extends FrameLayout {
       displayOrientation = (info.orientation - degrees + 360) % 360;
     }
 
-    boolean wasInPreview = inPreview;
-
-    if (inPreview) {
-      stopPreview();
-    }
-
-    camera.setDisplayOrientation(displayOrientation);
-
-    if (wasInPreview) {
-      startPreview();
-    }
+    stopPreview();
+    camera.get().setDisplayOrientation(displayOrientation);
+    startPreview();
   }
 
   public int getCameraPictureOrientation() {
-    Camera.CameraInfo info = new Camera.CameraInfo();
-
-    Camera.getCameraInfo(cameraId, info);
-
     if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
       outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
                                                                 .getDefaultDisplay()
                                                                 .getOrientation());
-    } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+    } else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
       outputOrientation = (360 - displayOrientation) % 360;
     } else {
       outputOrientation = displayOrientation;
     }
 
-    if (lastPictureOrientation != outputOrientation) {
-      lastPictureOrientation = outputOrientation;
-    }
     return outputOrientation;
   }
 
-  // based on:
-  // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setRotation(int)
+  private @NonNull CameraInfo getCameraInfo() {
+    final CameraInfo info = new Camera.CameraInfo();
+    Camera.getCameraInfo(cameraId, info);
+    return info;
+  }
+
+  // XXX this sucks
+  private Activity getActivity() {
+    return (Activity)getContext();
+  }
 
   public int getCameraPictureRotation(int orientation) {
-    Camera.CameraInfo info = new Camera.CameraInfo();
-    Camera.getCameraInfo(cameraId, info);
-    int rotation;
+    final CameraInfo info = getCameraInfo();
+    final int        rotation;
 
     orientation = (orientation + 45) / 90 * 90;
 
     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
       rotation = (info.orientation - orientation + 360) % 360;
-    }
-    else { // back-facing camera
+    } else {
       rotation = (info.orientation + orientation) % 360;
     }
 
     return rotation;
   }
 
-  Activity getActivity() {
-    return (Activity)getContext();
-  }
-
   private class OnOrientationChange extends OrientationEventListener {
     public OnOrientationChange(Context context) {
       super(context);
@@ -427,19 +362,18 @@ public class CameraView extends FrameLayout {
 
     @Override
     public void onOrientationChanged(int orientation) {
-      if (camera != null && orientation != ORIENTATION_UNKNOWN) {
+      if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) {
         int newOutputOrientation = getCameraPictureRotation(orientation);
 
         if (newOutputOrientation != outputOrientation) {
           outputOrientation = newOutputOrientation;
 
-          Camera.Parameters params = camera.getParameters();
+          Camera.Parameters params = camera.get().getParameters();
 
           params.setRotation(outputOrientation);
 
           try {
-            camera.setParameters(params);
-            lastPictureOrientation = outputOrientation;
+            camera.get().setParameters(params);
           }
           catch (Exception e) {
             Log.e(TAG, "Exception updating camera parameters in orientation change", e);
@@ -449,13 +383,66 @@ public class CameraView extends FrameLayout {
     }
   }
 
-  private void submitTask(SerializedAsyncTask job) {
+  public void takePicture(final Rect previewRect) {
+    if (!camera.isPresent() || camera.get().getParameters() == null) {
+      Log.w(TAG, "camera not in capture-ready state");
+      return;
+    }
+
+    camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() {
+      @Override
+      public void onPreviewFrame(byte[] data, final Camera camera) {
+        final int  rotation     = getCameraPictureOrientation();
+        final Size previewSize  = camera.getParameters().getPreviewSize();
+        final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
+
+        Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
+        Log.w(TAG, "data bytes: " + data.length);
+        Log.w(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat());
+        Log.w(TAG, "croppingRect: " + croppingRect.toString());
+        Log.w(TAG, "rotation: " + rotation);
+        new RotatePreviewAsyncTask(previewSize, rotation, croppingRect).execute(data);
+      }
+    });
+  }
+
+  private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
+    final int previewWidth  = cameraPreviewSize.width;
+    final int previewHeight = cameraPreviewSize.height;
+
+    if (rotation % 180 > 0) rotateRect(visibleRect);
+
+    float scale = (float) previewWidth / visibleRect.width();
+    if (visibleRect.height() * scale > previewHeight) {
+      scale = (float) previewHeight / visibleRect.height();
+    }
+    final float newWidth  = visibleRect.width()  * scale;
+    final float newHeight = visibleRect.height() * scale;
+    final float centerX   = (VERSION.SDK_INT < 14) ? previewWidth - newWidth / 2 : previewWidth / 2;
+    final float centerY   = previewHeight / 2;
+
+    visibleRect.set((int) (centerX - newWidth  / 2),
+                    (int) (centerY - newHeight / 2),
+                    (int) (centerX + newWidth  / 2),
+                    (int) (centerY + newHeight / 2));
+
+    if (rotation % 180 > 0) rotateRect(visibleRect);
+    return visibleRect;
+  }
+
+
+  @SuppressWarnings("SuspiciousNameCombination")
+  private void rotateRect(Rect rect) {
+    rect.set(rect.top, rect.left, rect.bottom, rect.right);
+  }
+
+  private void enqueueTask(SerialAsyncTask job) {
     ApplicationContext.getInstance(getContext()).getJobManager().add(job);
   }
 
-  private static abstract class SerializedAsyncTask<Result> extends Job {
+  private static abstract class SerialAsyncTask<Result> extends Job {
 
-    public SerializedAsyncTask() {
+    public SerialAsyncTask() {
       super(JobParameters.newBuilder().withGroupId(CameraView.class.getSimpleName()).create());
     }
 
@@ -464,7 +451,7 @@ public class CameraView extends FrameLayout {
     @Override public final void onRun() {
       try {
         onWait();
-        runOnMainSync(new Runnable() {
+        Util.runOnMainSync(new Runnable() {
           @Override public void run() {
             onPreMain();
           }
@@ -472,7 +459,7 @@ public class CameraView extends FrameLayout {
 
         final Result result = onRunBackground();
 
-        runOnMainSync(new Runnable() {
+        Util.runOnMainSync(new Runnable() {
           @Override public void run() {
             onPostMain(result);
           }
@@ -488,44 +475,61 @@ public class CameraView extends FrameLayout {
 
     @Override public void onCanceled() { }
 
-    private void runOnMainSync(final Runnable runnable) {
-      final CountDownLatch sync = new CountDownLatch(1);
-      Util.runOnMain(new Runnable() {
-        @Override public void run() {
-          try {
-            runnable.run();
-          } finally {
-            sync.countDown();
-          }
-        }
-      });
-      try {
-        sync.await();
-      } catch (InterruptedException ie) {
-        throw new AssertionError(ie);
-      }
-    }
-
     protected void onWait() throws PreconditionsNotMetException {}
     protected void onPreMain() {}
     protected Result onRunBackground() { return null; }
     protected void onPostMain(Result result) {}
   }
 
-  private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> {
+  private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
     @Override protected void onWait() throws PreconditionsNotMetException {
       synchronized (CameraView.this) {
-        if (!cameraReady) {
+        if (!camera.isPresent()) {
           throw new PreconditionsNotMetException();
         }
-        while (camera == null || previewSize == null || !previewStrategy.isReady()) {
-          Log.w(TAG, String.format("waiting. camera? %s previewSize? %s prevewStrategy? %s",
-                                   camera != null, previewSize != null, previewStrategy.isReady()));
+        while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
+          Log.w(TAG, String.format("waiting. surface ready? %s", surface.isReady()));
           Util.wait(CameraView.this, 0);
         }
       }
     }
   }
 
+  private class RotatePreviewAsyncTask extends AsyncTask<byte[], Void, byte[]> {
+    private final Size previewSize;
+    private final int  rotation;
+    private final Rect croppingRect;
+
+    public RotatePreviewAsyncTask(Size previewSize, int rotation, Rect croppingRect) {
+      this.previewSize  = previewSize;
+      this.rotation     = rotation;
+      this.croppingRect = croppingRect;
+    }
+
+    @Override
+    protected byte[] doInBackground(byte[]... params) {
+      final byte[] data = params[0];
+      try {
+        return BitmapUtil.createFromNV21(data,
+                                         previewSize.width,
+                                         previewSize.height,
+                                         rotation,
+                                         croppingRect);
+      } catch (IOException e) {
+        return null;
+      }
+    }
+
+    @Override
+    protected void onPostExecute(byte[] imageBytes) {
+      if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes);
+    }
+  }
+
   private static class PreconditionsNotMetException extends Exception {}
+
+  public interface CameraViewListener {
+    void onImageCapture(@NonNull final byte[] imageBytes);
+    void onCameraFail();
+  }
 }
diff --git a/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java
deleted file mode 100644
index 14ce75f486..0000000000
--- a/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.thoughtcrime.securesms.components.camera;
-
-import android.hardware.Camera;
-import android.media.MediaRecorder;
-import android.view.View;
-
-import java.io.IOException;
-
-@SuppressWarnings("deprecation")
-public interface PreviewStrategy extends com.commonsware.cwac.camera.PreviewStrategy {
-  boolean isReady();
-}
diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
index d62e272d70..d20dbf65b4 100644
--- a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
+++ b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
@@ -26,7 +26,7 @@ import com.nineoldandroids.animation.ObjectAnimator;
 import org.thoughtcrime.securesms.R;
 import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
 import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
-import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener;
+import org.thoughtcrime.securesms.components.camera.CameraView.CameraViewListener;
 import org.thoughtcrime.securesms.util.ServiceUtil;
 import org.thoughtcrime.securesms.util.Util;
 import org.thoughtcrime.securesms.util.ViewUtil;
@@ -36,7 +36,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
 
   private final ViewDragHelper dragHelper;
 
-  private QuickCamera               quickCamera;
+  private CameraView                cameraView;
   private int                       coverViewPosition;
   private KeyboardAwareLinearLayout container;
   private View                      coverView;
@@ -74,12 +74,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
 
   private void initializeView() {
     inflate(getContext(), R.layout.quick_attachment_drawer, this);
-    quickCamera = (QuickCamera) findViewById(R.id.quick_camera);
+    cameraView = ViewUtil.findById(this, R.id.quick_camera);
     updateControlsView();
 
     coverViewPosition = getChildCount();
     controls.setVisibility(GONE);
-    quickCamera.setVisibility(GONE);
+    cameraView.setVisibility(GONE);
   }
 
   public static boolean isDeviceSupported(Context context) {
@@ -108,7 +108,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
     this.rotation = rotation;
     if (rotationChanged) {
       if (isShowing()) {
-        quickCamera.onPause();
+        cameraView.onPause();
       }
       updateControlsView();
       setDrawerStateAndUpdate(drawerState, true);
@@ -123,13 +123,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
     shutterButton    = (ImageButton) controls.findViewById(R.id.shutter_button);
     swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
     fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
-    if (quickCamera.isMultipleCameras()) {
+    if (cameraView.isMultiCamera()) {
       swapCameraButton.setVisibility(View.VISIBLE);
       swapCameraButton.setOnClickListener(new CameraFlipClickListener());
     }
     shutterButton.setOnClickListener(new ShutterClickListener());
     fullScreenButton.setOnClickListener(new FullscreenClickListener());
-    ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(quickCamera) + 1);
+    ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(cameraView) + 1);
     this.controls = controls;
   }
 
@@ -170,11 +170,11 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
       int childLeft = paddingLeft;
       int childBottom;
 
-      if (child == quickCamera) {
+      if (child == cameraView) {
         childTop    = computeCameraTopPosition(slideOffset);
         childBottom = childTop + childHeight;
-        if (quickCamera.getMeasuredWidth() < getMeasuredWidth())
-          childLeft = (getMeasuredWidth() - quickCamera.getMeasuredWidth()) / 2 + paddingLeft;
+        if (cameraView.getMeasuredWidth() < getMeasuredWidth())
+          childLeft = (getMeasuredWidth() - cameraView.getMeasuredWidth()) / 2 + paddingLeft;
       } else if (child == controls) {
         childBottom = getMeasuredHeight();
       } else {
@@ -271,14 +271,14 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
       ViewCompat.postInvalidateOnAnimation(this);
     }
 
-    if (slideOffset == 0 && quickCamera.isStarted()) {
-      quickCamera.onPause();
+    if (slideOffset == 0 && cameraView.isStarted()) {
+      cameraView.onPause();
       controls.setVisibility(GONE);
-      quickCamera.setVisibility(GONE);
-    } else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) {
+      cameraView.setVisibility(GONE);
+    } else if (slideOffset != 0 && !cameraView.isStarted() & !paused) {
       controls.setVisibility(VISIBLE);
-      quickCamera.setVisibility(VISIBLE);
-      quickCamera.onResume();
+      cameraView.setVisibility(VISIBLE);
+      cameraView.onResume();
     }
   }
 
@@ -335,10 +335,10 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
 
   public void setListener(AttachmentDrawerListener listener) {
     this.listener = listener;
-    if (quickCamera != null) quickCamera.setQuickCameraListener(listener);
+    if (cameraView != null) cameraView.setListener(listener);
   }
 
-  public interface AttachmentDrawerListener extends QuickCameraListener {
+  public interface AttachmentDrawerListener extends CameraViewListener {
     void onAttachmentDrawerStateChanged(DrawerState drawerState);
   }
 
@@ -391,8 +391,8 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
         int slideOffset = getTargetSlideOffset();
         dragHelper.captureChildView(coverView, 0);
         dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
-        dragHelper.captureChildView(quickCamera, 0);
-        dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
+        dragHelper.captureChildView(cameraView, 0);
+        dragHelper.settleCapturedViewAt(cameraView.getLeft(), computeCameraTopPosition(slideOffset));
         ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
       }
     }
@@ -455,13 +455,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
   @SuppressWarnings("ResourceType")
   private boolean isDragViewUnder(int x, int y) {
     int[] viewLocation = new int[2];
-    quickCamera.getLocationOnScreen(viewLocation);
+    cameraView.getLocationOnScreen(viewLocation);
     int[] parentLocation = new int[2];
     this.getLocationOnScreen(parentLocation);
     int screenX = parentLocation[0] + x;
     int screenY = parentLocation[1] + y;
-    return screenX >= viewLocation[0] && screenX < viewLocation[0] + quickCamera.getWidth() &&
-           screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight();
+    return screenX >= viewLocation[0] && screenX < viewLocation[0] + cameraView.getWidth() &&
+           screenY >= viewLocation[1] && screenY < viewLocation[1] + cameraView.getHeight();
   }
 
   private int computeCameraTopPosition(int slideOffset) {
@@ -469,7 +469,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
       return getPaddingTop();
     }
 
-    final int   baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2;
+    final int   baseCameraTop = (cameraView.getMeasuredHeight() - halfExpandedHeight) / 2;
     final int   baseOffset    = getMeasuredHeight() - slideOffset - baseCameraTop;
     final float slop          = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
                                            0f,
@@ -502,12 +502,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
 
   public void onPause() {
     paused = true;
-    quickCamera.onPause();
+    cameraView.onPause();
   }
 
   public void onResume() {
     paused = false;
-    if (drawerState.isVisible()) quickCamera.onResume();
+    if (drawerState.isVisible()) cameraView.onResume();
   }
 
   public enum DrawerState {
@@ -522,18 +522,18 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
     @Override
     public void onClick(View v) {
       boolean crop        = drawerState != DrawerState.FULL_EXPANDED;
-      int     imageHeight = crop ? getContainer().getKeyboardHeight() : quickCamera.getMeasuredHeight();
-      Rect    previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight);
-      quickCamera.takePicture(previewRect);
+      int     imageHeight = crop ? getContainer().getKeyboardHeight() : cameraView.getMeasuredHeight();
+      Rect    previewRect = new Rect(0, 0, cameraView.getMeasuredWidth(), imageHeight);
+      cameraView.takePicture(previewRect);
     }
   }
 
   private class CameraFlipClickListener implements OnClickListener {
     @Override
     public void onClick(View v) {
-      quickCamera.swapCamera();
-      swapCameraButton.setImageResource(quickCamera.isRearCamera() ? R.drawable.quick_camera_front
-                                                                   : R.drawable.quick_camera_rear);
+      cameraView.flipCamera();
+      swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front
+                                                                  : R.drawable.quick_camera_rear);
     }
   }
 
diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java b/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java
deleted file mode 100644
index 4f4dde6470..0000000000
--- a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java
+++ /dev/null
@@ -1,207 +0,0 @@
-package org.thoughtcrime.securesms.components.camera;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
-import android.os.AsyncTask;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.support.annotation.NonNull;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import com.commonsware.cwac.camera.CameraHost.FailureReason;
-import com.commonsware.cwac.camera.SimpleCameraHost;
-
-import org.thoughtcrime.securesms.util.BitmapUtil;
-
-import java.io.IOException;
-import java.util.List;
-
-@SuppressWarnings("deprecation") public class QuickCamera extends CameraView {
-  private static final String TAG = QuickCamera.class.getSimpleName();
-
-  private QuickCameraListener listener;
-  private boolean             capturing;
-  private boolean             started;
-  private QuickCameraHost     cameraHost;
-
-  public QuickCamera(Context context) {
-    this(context, null);
-  }
-
-  public QuickCamera(Context context, AttributeSet attrs) {
-    this(context, attrs, 0);
-  }
-
-  public QuickCamera(Context context, AttributeSet attrs, int defStyle) {
-    super(context, attrs, defStyle);
-    cameraHost = new QuickCameraHost(context);
-    setHost(cameraHost);
-  }
-
-  @Override
-  public void onResume() {
-    if (started) return;
-    super.onResume();
-    started = true;
-  }
-
-  @Override
-  public void onPause() {
-    if (!started) return;
-    super.onPause();
-    started = false;
-  }
-
-  public boolean isStarted() {
-    return started;
-  }
-
-  public void takePicture(final Rect previewRect) {
-    if (capturing) {
-      Log.w(TAG, "takePicture() called while previous capture pending.");
-      return;
-    }
-
-    final Parameters cameraParameters = getCameraParameters();
-    if (cameraParameters == null) {
-      Log.w(TAG, "camera not in capture-ready state");
-      return;
-    }
-
-    setOneShotPreviewCallback(new Camera.PreviewCallback() {
-      @Override
-      public void onPreviewFrame(byte[] data, final Camera camera) {
-        final int  rotation     = getCameraPictureOrientation();
-        final Size previewSize  = cameraParameters.getPreviewSize();
-        final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
-
-        Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
-        Log.w(TAG, "previewFormat: " + cameraParameters.getPreviewFormat());
-        Log.w(TAG, "croppingRect: " + croppingRect.toString());
-        Log.w(TAG, "rotation: " + rotation);
-        new AsyncTask<byte[], Void, byte[]>() {
-          @Override
-          protected byte[] doInBackground(byte[]... params) {
-            byte[] data = params[0];
-            try {
-
-              return BitmapUtil.createFromNV21(data,
-                                               previewSize.width,
-                                               previewSize.height,
-                                               rotation,
-                                               croppingRect);
-            } catch (IOException e) {
-              return null;
-            }
-          }
-
-          @Override
-          protected void onPostExecute(byte[] imageBytes) {
-            capturing = false;
-            if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes);
-          }
-        }.execute(data);
-      }
-    });
-  }
-
-  private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
-    final int previewWidth  = cameraPreviewSize.width;
-    final int previewHeight = cameraPreviewSize.height;
-
-    if (rotation % 180 > 0) rotateRect(visibleRect);
-
-    float scale = (float) previewWidth / visibleRect.width();
-    if (visibleRect.height() * scale > previewHeight) {
-      scale = (float) previewHeight / visibleRect.height();
-    }
-    final float newWidth  = visibleRect.width()  * scale;
-    final float newHeight = visibleRect.height() * scale;
-    final float centerX;
-    final float centerY   = previewHeight        / 2;
-    if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
-      centerX = previewWidth - newWidth / 2;
-    } else {
-      centerX = previewWidth / 2;
-    }
-
-    visibleRect.set((int) (centerX - newWidth  / 2),
-                    (int) (centerY - newHeight / 2),
-                    (int) (centerX + newWidth  / 2),
-                    (int) (centerY + newHeight / 2));
-
-    if (rotation % 180 > 0) rotateRect(visibleRect);
-    return visibleRect;
-  }
-
-  @SuppressWarnings("SuspiciousNameCombination")
-  private void rotateRect(Rect rect) {
-    rect.set(rect.top, rect.left, rect.bottom, rect.right);
-  }
-
-  public void setQuickCameraListener(QuickCameraListener listener) {
-    this.listener = listener;
-  }
-
-  public boolean isMultipleCameras() {
-    return Camera.getNumberOfCameras() > 1;
-  }
-
-  public boolean isRearCamera() {
-    return cameraHost.getCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK;
-  }
-
-  public void swapCamera() {
-    cameraHost.swapCameraId();
-    onPause();
-    onResume();
-  }
-
-  public interface QuickCameraListener {
-    void onImageCapture(@NonNull final byte[] imageBytes);
-    void onCameraFail(FailureReason reason);
-  }
-
-  private class QuickCameraHost extends SimpleCameraHost {
-    int cameraId = CameraInfo.CAMERA_FACING_BACK;
-
-    public QuickCameraHost(Context context) {
-      super(context);
-    }
-
-    @TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) @Override
-    public Parameters adjustPreviewParameters(Parameters parameters) {
-      List<String> focusModes = parameters.getSupportedFocusModes();
-      if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
-        parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
-      } else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
-        parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
-      }
-      return parameters;
-    }
-
-    @Override
-    public int getCameraId() {
-      return cameraId;
-    }
-
-    public void swapCameraId() {
-      if (isMultipleCameras()) {
-        if (cameraId == CameraInfo.CAMERA_FACING_BACK) cameraId = CameraInfo.CAMERA_FACING_FRONT;
-        else                                           cameraId = CameraInfo.CAMERA_FACING_BACK;
-      }
-    }
-
-    @Override
-    public void onCameraFail(FailureReason reason) {
-      super.onCameraFail(reason);
-      if (listener != null) listener.onCameraFail(reason);
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java
deleted file mode 100644
index 78e8a2769c..0000000000
--- a/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/***
- Copyright (c) 2013 CommonsWare, LLC
-
- 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 org.thoughtcrime.securesms.components.camera;
-
-import android.hardware.Camera;
-import android.media.MediaRecorder;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-
-import java.io.IOException;
-
-class SurfacePreviewStrategy implements PreviewStrategy,
-    SurfaceHolder.Callback {
-  private final static String TAG = SurfacePreviewStrategy.class.getSimpleName();
-  private final CameraView cameraView;
-  private SurfaceView preview=null;
-  private SurfaceHolder previewHolder=null;
-  private boolean ready = false;
-
-  @SuppressWarnings("deprecation")
-  SurfacePreviewStrategy(CameraView cameraView) {
-    this.cameraView=cameraView;
-    preview=new SurfaceView(cameraView.getContext());
-    previewHolder=preview.getHolder();
-    previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
-    previewHolder.addCallback(this);
-  }
-
-  @Override
-  public void surfaceCreated(SurfaceHolder holder) {
-    Log.w(TAG, "surfaceCreated()");
-    ready = true;
-    synchronized (cameraView) { cameraView.notifyAll(); }
-  }
-
-  @Override
-  public void surfaceChanged(SurfaceHolder holder, int format,
-                             int width, int height) {
-    Log.w(TAG, "surfaceChanged()");
-  }
-
-  @Override
-  public void surfaceDestroyed(SurfaceHolder holder) {
-    Log.w(TAG, "surfaceDestroyed()");
-    cameraView.onPause();
-  }
-
-  @Override
-  public void attach(Camera camera) throws IOException {
-    Log.w(TAG, "attach(Camera)");
-    camera.setPreviewDisplay(previewHolder);
-  }
-
-  @Override
-  public void attach(MediaRecorder recorder) {
-    recorder.setPreviewDisplay(previewHolder.getSurface());
-  }
-
-  @Override
-  public View getWidget() {
-    return(preview);
-  }
-
-  @Override
-  public boolean isReady() {
-    return ready;
-  }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java
deleted file mode 100644
index 4b7049eeb2..0000000000
--- a/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.thoughtcrime.securesms.components.camera;
-/***
- Copyright (c) 2013 CommonsWare, LLC
-
- 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.
- */
-
-import android.annotation.TargetApi;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.media.MediaRecorder;
-import android.os.Build;
-import android.util.Log;
-import android.view.TextureView;
-import android.view.View;
-
-import java.io.IOException;
-
-@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-class TexturePreviewStrategy implements PreviewStrategy,
-    TextureView.SurfaceTextureListener {
-  private final static String TAG = TexturePreviewStrategy.class.getSimpleName();
-  private final CameraView cameraView;
-  private TextureView widget=null;
-  private SurfaceTexture surface=null;
-
-  TexturePreviewStrategy(CameraView cameraView) {
-    this.cameraView=cameraView;
-    widget=new TextureView(cameraView.getContext());
-    widget.setSurfaceTextureListener(this);
-  }
-
-  @Override
-  public void onSurfaceTextureAvailable(SurfaceTexture surface,
-                                        int width, int height) {
-    Log.w(TAG, "onSurfaceTextureAvailable()");
-    this.surface=surface;
-    synchronized (cameraView) { cameraView.notifyAll(); }
-  }
-
-  @Override
-  public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
-                                          int width, int height) {
-    Log.w(TAG, "onSurfaceTextureChanged()");
-  }
-
-  @Override
-  public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-    Log.w(TAG, "onSurfaceTextureDestroyed()");
-    cameraView.onPause();
-
-    return(true);
-  }
-
-  @Override
-  public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-    // no-op
-  }
-
-  @Override
-  public void attach(Camera camera) throws IOException {
-    camera.setPreviewTexture(surface);
-  }
-
-  @Override
-  public void attach(MediaRecorder recorder) {
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-      // no-op
-    }
-    else {
-      throw new IllegalStateException(
-          "Cannot use TextureView with MediaRecorder");
-    }
-  }
-
-  @Override
-  public boolean isReady() {
-    return widget.isAvailable();
-  }
-
-  @Override
-  public View getWidget() {
-    return(widget);
-  }
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
index 962cf65f6e..7fa5d2ed96 100644
--- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java
+++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
@@ -166,14 +166,24 @@ public class BitmapUtil {
     return bytes;
   }
 
+  /*
+   * NV21 a.k.a. YUV420sp
+   * YUV 4:2:0 planar image, with 8 bit Y samples, followed by interleaved V/U plane with 8bit 2x2
+   * subsampled chroma samples.
+   *
+   * http://www.fourcc.org/yuv.php#NV21
+   */
   public static byte[] rotateNV21(@NonNull final byte[] yuv,
                                   final int width,
                                   final int height,
                                   final int rotation)
+      throws IOException
   {
     if (rotation == 0) return yuv;
     if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
       throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
+    } else if ((width * height * 3) / 2 != yuv.length) {
+      throw new IOException("provided width and height don't jive with the data length");
     }
 
     final byte[]  output    = new byte[yuv.length];
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index 6d5377888e..e1d3bc08e0 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -55,6 +55,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -330,11 +331,33 @@ public class Util {
     }
   }
 
-  public static void runOnMain(Runnable runnable) {
+  public static void runOnMain(final @NonNull Runnable runnable) {
     if (isMainThread()) runnable.run();
     else                handler.post(runnable);
   }
 
+  public static void runOnMainSync(final @NonNull Runnable runnable) {
+    if (isMainThread()) {
+      runnable.run();
+    } else {
+      final CountDownLatch sync = new CountDownLatch(1);
+      runOnMain(new Runnable() {
+        @Override public void run() {
+          try {
+            runnable.run();
+          } finally {
+            sync.countDown();
+          }
+        }
+      });
+      try {
+        sync.await();
+      } catch (InterruptedException ie) {
+        throw new AssertionError(ie);
+      }
+    }
+  }
+
   public static boolean equals(@Nullable Object a, @Nullable Object b) {
     return a == b || (a != null && a.equals(b));
   }