diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 71e173e18f..9f7889295f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
 
     <permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
diff --git a/build.gradle b/build.gradle
index 351d21faf2..50309b7083 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,9 @@ repositories {
     maven { // textdrawable
         url 'https://dl.bintray.com/amulyakhare/maven'
     }
+    maven {
+        url 'https://repo.commonsware.com.s3.amazonaws.com'
+    }
     jcenter()
     mavenLocal()
 }
@@ -69,6 +72,7 @@ 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.+'
     provided 'com.squareup.dagger:dagger-compiler:1.2.2'
 
     compile 'org.whispersystems:jobmanager:0.11.0'
diff --git a/res/drawable-hdpi/quick_camera_dark.png b/res/drawable-hdpi/quick_camera_dark.png
new file mode 100755
index 0000000000..aa3a3e5717
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_dark.png differ
diff --git a/res/drawable-hdpi/quick_camera_exit_fullscreen.png b/res/drawable-hdpi/quick_camera_exit_fullscreen.png
new file mode 100755
index 0000000000..061e737dab
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_exit_fullscreen.png differ
diff --git a/res/drawable-hdpi/quick_camera_front.png b/res/drawable-hdpi/quick_camera_front.png
new file mode 100755
index 0000000000..6fa7c4fa3d
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_front.png differ
diff --git a/res/drawable-hdpi/quick_camera_fullscreen.png b/res/drawable-hdpi/quick_camera_fullscreen.png
new file mode 100755
index 0000000000..bd69358cb5
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_fullscreen.png differ
diff --git a/res/drawable-hdpi/quick_camera_hide.png b/res/drawable-hdpi/quick_camera_hide.png
new file mode 100755
index 0000000000..724eb1f7ab
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_hide.png differ
diff --git a/res/drawable-hdpi/quick_camera_light.png b/res/drawable-hdpi/quick_camera_light.png
new file mode 100755
index 0000000000..c26a846e37
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_light.png differ
diff --git a/res/drawable-hdpi/quick_camera_rear.png b/res/drawable-hdpi/quick_camera_rear.png
new file mode 100755
index 0000000000..764e35ab0f
Binary files /dev/null and b/res/drawable-hdpi/quick_camera_rear.png differ
diff --git a/res/drawable-hdpi/quick_shutter_button.png b/res/drawable-hdpi/quick_shutter_button.png
new file mode 100755
index 0000000000..9817b7e7e2
Binary files /dev/null and b/res/drawable-hdpi/quick_shutter_button.png differ
diff --git a/res/drawable-mdpi/quick_camera_dark.png b/res/drawable-mdpi/quick_camera_dark.png
new file mode 100755
index 0000000000..4dbf6c77bc
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_dark.png differ
diff --git a/res/drawable-mdpi/quick_camera_exit_fullscreen.png b/res/drawable-mdpi/quick_camera_exit_fullscreen.png
new file mode 100755
index 0000000000..2e88a87737
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_exit_fullscreen.png differ
diff --git a/res/drawable-mdpi/quick_camera_front.png b/res/drawable-mdpi/quick_camera_front.png
new file mode 100755
index 0000000000..ee64e0f670
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_front.png differ
diff --git a/res/drawable-mdpi/quick_camera_fullscreen.png b/res/drawable-mdpi/quick_camera_fullscreen.png
new file mode 100755
index 0000000000..382fe09edc
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_fullscreen.png differ
diff --git a/res/drawable-mdpi/quick_camera_hide.png b/res/drawable-mdpi/quick_camera_hide.png
new file mode 100755
index 0000000000..6c24766600
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_hide.png differ
diff --git a/res/drawable-mdpi/quick_camera_light.png b/res/drawable-mdpi/quick_camera_light.png
new file mode 100755
index 0000000000..2a906080fd
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_light.png differ
diff --git a/res/drawable-mdpi/quick_camera_rear.png b/res/drawable-mdpi/quick_camera_rear.png
new file mode 100755
index 0000000000..764e35ab0f
Binary files /dev/null and b/res/drawable-mdpi/quick_camera_rear.png differ
diff --git a/res/drawable-mdpi/quick_shutter_button.png b/res/drawable-mdpi/quick_shutter_button.png
new file mode 100755
index 0000000000..90022770fe
Binary files /dev/null and b/res/drawable-mdpi/quick_shutter_button.png differ
diff --git a/res/drawable-xhdpi/quick_camera_dark.png b/res/drawable-xhdpi/quick_camera_dark.png
new file mode 100755
index 0000000000..753805275b
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_dark.png differ
diff --git a/res/drawable-xhdpi/quick_camera_exit_fullscreen.png b/res/drawable-xhdpi/quick_camera_exit_fullscreen.png
new file mode 100755
index 0000000000..fbb53e5c6e
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_exit_fullscreen.png differ
diff --git a/res/drawable-xhdpi/quick_camera_front.png b/res/drawable-xhdpi/quick_camera_front.png
new file mode 100755
index 0000000000..379bc460f8
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_front.png differ
diff --git a/res/drawable-xhdpi/quick_camera_fullscreen.png b/res/drawable-xhdpi/quick_camera_fullscreen.png
new file mode 100755
index 0000000000..5709dd8bed
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_fullscreen.png differ
diff --git a/res/drawable-xhdpi/quick_camera_hide.png b/res/drawable-xhdpi/quick_camera_hide.png
new file mode 100755
index 0000000000..f00aa8bd4e
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_hide.png differ
diff --git a/res/drawable-xhdpi/quick_camera_light.png b/res/drawable-xhdpi/quick_camera_light.png
new file mode 100755
index 0000000000..3be61d73f4
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_light.png differ
diff --git a/res/drawable-xhdpi/quick_camera_rear.png b/res/drawable-xhdpi/quick_camera_rear.png
new file mode 100755
index 0000000000..d27bf85e79
Binary files /dev/null and b/res/drawable-xhdpi/quick_camera_rear.png differ
diff --git a/res/drawable-xhdpi/quick_shutter_button.png b/res/drawable-xhdpi/quick_shutter_button.png
new file mode 100755
index 0000000000..9324ec8cb3
Binary files /dev/null and b/res/drawable-xhdpi/quick_shutter_button.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_dark.png b/res/drawable-xxhdpi/quick_camera_dark.png
new file mode 100755
index 0000000000..c1a3549bfc
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_dark.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_exit_fullscreen.png b/res/drawable-xxhdpi/quick_camera_exit_fullscreen.png
new file mode 100755
index 0000000000..d041734c13
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_exit_fullscreen.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_front.png b/res/drawable-xxhdpi/quick_camera_front.png
new file mode 100755
index 0000000000..9e35ad5c4a
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_front.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_fullscreen.png b/res/drawable-xxhdpi/quick_camera_fullscreen.png
new file mode 100755
index 0000000000..ce96d83e36
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_fullscreen.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_hide.png b/res/drawable-xxhdpi/quick_camera_hide.png
new file mode 100755
index 0000000000..16094f9bc4
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_hide.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_light.png b/res/drawable-xxhdpi/quick_camera_light.png
new file mode 100755
index 0000000000..62d83b253d
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_light.png differ
diff --git a/res/drawable-xxhdpi/quick_camera_rear.png b/res/drawable-xxhdpi/quick_camera_rear.png
new file mode 100755
index 0000000000..d9fd33dd37
Binary files /dev/null and b/res/drawable-xxhdpi/quick_camera_rear.png differ
diff --git a/res/drawable-xxhdpi/quick_shutter_button.png b/res/drawable-xxhdpi/quick_shutter_button.png
new file mode 100755
index 0000000000..4394349386
Binary files /dev/null and b/res/drawable-xxhdpi/quick_shutter_button.png differ
diff --git a/res/drawable/quick_camera_shutter_ring.xml b/res/drawable/quick_camera_shutter_ring.xml
new file mode 100644
index 0000000000..33690326ca
--- /dev/null
+++ b/res/drawable/quick_camera_shutter_ring.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape
+            android:innerRadiusRatio="3"
+            android:shape="ring"
+            android:thickness="2dp"
+            android:useLevel="false" >
+            <solid android:color="@android:color/white" />
+            <size
+                android:height="52dp"
+                android:width="52dp" />
+        </shape>
+    </item>
+    <item>
+        <shape
+            android:innerRadiusRatio="3"
+            android:shape="ring"
+            android:thickness="2dp"
+            android:useLevel="false" >
+            <solid android:color="#40ffffff" />
+            <size
+                android:height="52dp"
+                android:width="52dp" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/res/layout-land/quick_camera_controls.xml b/res/layout-land/quick_camera_controls.xml
new file mode 100644
index 0000000000..558d6b77f9
--- /dev/null
+++ b/res/layout-land/quick_camera_controls.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="bottom"
+    tools:background="@android:color/darker_gray">
+    <ImageButton
+        android:id="@+id/shutter_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:background="@drawable/quick_camera_shutter_ring"
+        android:src="@drawable/quick_shutter_button"
+        android:padding="20dp"/>
+    <ImageButton
+        android:id="@+id/fullscreen_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:background="#00000000"
+        android:src="@drawable/quick_camera_fullscreen"
+        android:padding="20dp"/>
+    <ImageButton
+        android:id="@+id/swap_camera_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:background="#00000000"
+        android:src="@drawable/quick_camera_front"
+        android:padding="20dp"
+        android:visibility="invisible"
+        tools:visibility="visible"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index a83d849113..c9421f4c62 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -10,6 +10,12 @@
         android:background="?conversation_background"
         android:orientation="vertical">
 
+<org.thoughtcrime.securesms.components.QuickAttachmentDrawer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/quick_attachment_drawer"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
     <RelativeLayout android:layout_width="fill_parent"
                     android:layout_height="fill_parent"
                     android:layout_weight="1"
@@ -95,6 +101,14 @@
                         tools:hint="Send TextSecure message" />
                 </LinearLayout>
 
+                <ImageButton android:id="@+id/quick_attachment_toggle"
+                             android:layout_width="wrap_content"
+                             android:layout_height="44dp"
+                             android:src="?quick_camera_icon"
+                             android:background="@drawable/touch_highlight_background"
+                             android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_description"
+                             android:padding="10dp"/>
+
                 <org.thoughtcrime.securesms.components.AnimatingToggle
                     android:id="@+id/button_toggle"
                     android:layout_width="50dp"
@@ -141,4 +155,6 @@
 
         </LinearLayout>
     </RelativeLayout>
+
+</org.thoughtcrime.securesms.components.QuickAttachmentDrawer>
 </org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout>
diff --git a/res/layout/quick_camera_controls.xml b/res/layout/quick_camera_controls.xml
new file mode 100644
index 0000000000..1e6a697589
--- /dev/null
+++ b/res/layout/quick_camera_controls.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="bottom"
+    tools:background="@android:color/darker_gray">
+    <ImageButton
+        android:id="@+id/shutter_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:background="@drawable/quick_camera_shutter_ring"
+        android:src="@drawable/quick_shutter_button"
+        android:padding="20dp"/>
+    <ImageButton
+        android:id="@+id/fullscreen_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:background="#00000000"
+        android:src="@drawable/quick_camera_fullscreen"
+        android:padding="20dp"/>
+    <ImageButton
+        android:id="@+id/swap_camera_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:background="#00000000"
+        android:src="@drawable/quick_camera_front"
+        android:padding="20dp"
+        android:visibility="invisible"
+        tools:visibility="visible"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6d25609aa4..9b8142d0c1 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -52,6 +52,7 @@
     <attr name="emoji_category_places" format="reference"/>
     <attr name="emoji_category_symbol" format="reference"/>
     <attr name="emoji_category_emoticons" format="reference"/>
+    <attr name="quick_camera_icon" format="reference"/>
 
     <attr name="conversation_item_background" format="reference"/>
     <attr name="conversation_item_bubble_background" format="reference|color"/>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 56b0af4c42..2fdaa5cf2e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -28,4 +28,5 @@
 
     <dimen name="color_grid_extra_padding">32dp</dimen>
     <dimen name="color_grid_item_size">48dp</dimen>
+    <dimen name="quick_media_drawer_default_height">250dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a28a9126dc..0df6e3941e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -521,6 +521,7 @@
     <string name="conversation_activity__compose_description">Message composition</string>
     <string name="conversation_activity__emoji_toggle_description">Toggle emoji keyboard</string>
     <string name="conversation_activity__attachment_thumbnail">Attachment Thumbnail</string>
+    <string name="conversation_activity__quick_attachment_drawer_toggle_description">Toggle attachment drawer</string>
 
     <!-- conversation_item -->
     <string name="conversation_item__mms_downloading_description">Media message downloading</string>
@@ -981,6 +982,9 @@
     <!-- transport_selection_list_item -->
     <string name="transport_selection_list_item__transport_icon">Transport icon</string>
 
+    <!-- quick_attachment_drawer -->
+    <string name="quick_camera_unavailable">Camera unavailable</string>
+
     <!-- EOF -->
 
 </resources>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 22cd2496b2..a460231e13 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -40,6 +40,7 @@
         <item name="ic_arrow_forward">@drawable/ic_arrow_forward_dark</item>
         <item name="lockscreen_watermark">@drawable/lockscreen_watermark_dark</item>
         <item name="android:windowBackground">@color/black</item>
+        <item name="conversation_background">@color/black</item>
     </style>
 
     <style name="PopupAnimation" parent="@android:style/Animation">
@@ -124,6 +125,9 @@
         <item name="conversation_item_sent_text_indicator_tab_color">#99000000</item>
         <item name="conversation_item_received_text_primary_color">@color/white</item>
         <item name="conversation_item_received_text_secondary_color">#BFffffff</item>
+
+        <item name="quick_camera_icon">@drawable/quick_camera_light</item>
+
         <item name="conversation_item_background">@drawable/conversation_item_background</item>
         <item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item>
 
@@ -250,6 +254,8 @@
         <item name="emoji_category_symbol">@drawable/emoji_category_symbol_dark</item>
         <item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_dark</item>
 
+        <item name="quick_camera_icon">@drawable/quick_camera_dark</item>
+
         <item name="menu_new_conversation_icon">@drawable/ic_add_white_24dp</item>
         <item name="menu_group_icon">@drawable/ic_group_white_24dp</item>
         <item name="menu_search_icon">@drawable/ic_search_white_24dp</item>
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 121af277b4..663c9b2d2d 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -34,6 +34,7 @@ import android.os.Build;
 import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.support.annotation.NonNull;
+import android.support.v4.view.WindowCompat;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextWatcher;
@@ -70,6 +71,9 @@ import org.thoughtcrime.securesms.components.emoji.EmojiPopup;
 import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
 import org.thoughtcrime.securesms.contacts.ContactAccessor;
 import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
+import org.thoughtcrime.securesms.components.QuickAttachmentDrawer;
+import org.thoughtcrime.securesms.components.QuickCamera;
+import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
 import org.thoughtcrime.securesms.crypto.MasterCipher;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.crypto.SecurityEvent;
@@ -114,6 +118,8 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
 import org.whispersystems.libaxolotl.InvalidMessageException;
 import org.whispersystems.libaxolotl.util.guava.Optional;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -172,6 +178,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   private BroadcastReceiver             groupUpdateReceiver;
   private Optional<EmojiPopup>          emojiPopup = Optional.absent();
   private EmojiToggle                   emojiToggle;
+  private ImageButton                   quickAttachmentToggle;
+  private QuickAttachmentDrawer         quickAttachmentDrawer;
 
   private Recipients recipients;
   private long       threadId;
@@ -193,6 +201,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
     this.masterSecret = masterSecret;
 
+    supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
     setContentView(R.layout.conversation_activity);
 
     fragment = initFragment(R.id.fragment_content, new ConversationFragment(),
@@ -229,6 +238,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     super.onResume();
     dynamicTheme.onResume(this);
     dynamicLanguage.onResume(this);
+    quickAttachmentDrawer.onResume();
 
     initializeSecurity();
     initializeEnabledCheck();
@@ -249,6 +259,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     super.onPause();
     MessageNotifier.setVisibleThread(-1L);
     if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
+    quickAttachmentDrawer.onPause();
   }
 
   @Override
@@ -366,6 +377,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   public void onBackPressed() {
     if (isEmojiDrawerOpen()) {
       hideEmojiPopup(false);
+    } else if (quickAttachmentDrawer.getDrawerState() != QuickAttachmentDrawer.COLLAPSED) {
+      quickAttachmentDrawer.setDrawerStateAndAnimate(QuickAttachmentDrawer.COLLAPSED);
     } else {
       super.onBackPressed();
     }
@@ -694,6 +707,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
             addAttachmentAudio(Uri.parse(draft.getValue()));
           } else if (draft.getType().equals(Draft.VIDEO)) {
             addAttachmentVideo(Uri.parse(draft.getValue()));
+          } else if (draft.getType().equals(Draft.ENCRYPTED_IMAGE)) {
+            addAttachmentEncryptedImage(Uri.parse(draft.getValue()));
           }
         }
 
@@ -766,6 +781,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
 
     container.addOnKeyboardShownListener(this);
 
+    buttonToggle          = (AnimatingToggle)       findViewById(R.id.button_toggle);
+    sendButton            = (SendButton)            findViewById(R.id.send_button);
+    attachButton          = (ImageButton)           findViewById(R.id.attach_button);
+    composeText           = (ComposeText)           findViewById(R.id.embedded_text_editor);
+    charactersLeft        = (TextView)              findViewById(R.id.space_left);
+    emojiToggle           = (EmojiToggle)           findViewById(R.id.emoji_toggle);
+    titleView             = (ConversationTitleView) getSupportActionBar().getCustomView();
+    unblockButton         = (Button)                findViewById(R.id.unblock_button);
+    composePanel          = findViewById(R.id.bottom_panel);
+    quickAttachmentDrawer = (QuickAttachmentDrawer) findViewById(R.id.quick_attachment_drawer);
+    quickAttachmentToggle = (ImageButton)           findViewById(R.id.quick_attachment_toggle);
+
     int[]      attributes   = new int[]{R.attr.conversation_item_bubble_background};
     TypedArray colors       = obtainStyledAttributes(attributes);
     int        defaultColor = colors.getColor(0, Color.WHITE);
@@ -814,6 +841,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     composeText.setOnClickListener(composeKeyPressedListener);
     composeText.setOnFocusChangeListener(composeKeyPressedListener);
     emojiToggle.setOnClickListener(new EmojiToggleListener());
+
+    if (quickAttachmentDrawer.hasCamera()) {
+      QuickAttachmentDrawerToggleListener listener = new QuickAttachmentDrawerToggleListener();
+      quickAttachmentDrawer.setQuickAttachmentDrawerListener(listener);
+      quickAttachmentDrawer.setQuickCameraListener(listener);
+      quickAttachmentToggle.setOnClickListener(listener);
+    } else {
+      quickAttachmentToggle.setVisibility(View.GONE);
+    }
   }
 
   protected void initializeActionBar() {
@@ -934,6 +970,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     }
   }
 
+  private void addAttachmentEncryptedImage(Uri uri) {
+    try {
+      attachmentManager.setEncryptedImage(uri, masterSecret);
+    } catch (IOException | BitmapDecodingException e) {
+      Log.w(TAG, e);
+      attachmentManager.clear();
+      Toast.makeText(this, R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment,
+          Toast.LENGTH_LONG).show();
+    }
+  }
+
   private void addAttachmentImage(Uri imageUri) {
     try {
       attachmentManager.setImage(imageUri);
@@ -1018,9 +1065,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     }
 
     for (Slide slide : attachmentManager.getSlideDeck().getSlides()) {
-      if      (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString()));
-      else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString()));
-      else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
+      String draftType = null;
+      if      (slide.hasAudio()) draftType = Draft.AUDIO;
+      else if (slide.hasVideo()) draftType = Draft.VIDEO;
+      else if (slide.hasImage()) draftType = slide.isEncrypted() ? Draft.ENCRYPTED_IMAGE : Draft.IMAGE;
+
+      if (draftType != null)
+        drafts.add(new Draft(draftType, slide.getUri().toString()));
     }
 
     return drafts;
@@ -1296,10 +1347,71 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
           }
         });
         input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
+        quickAttachmentDrawer.setDrawerStateAndAnimate(QuickAttachmentDrawer.COLLAPSED);
       }
     }
   }
 
+  private class QuickAttachmentDrawerToggleListener implements OnClickListener,
+          QuickAttachmentDrawer.QuickAttachmentDrawerListener,
+          QuickCamera.QuickCameraListener {
+    @QuickAttachmentDrawer.DrawerState int nextDrawerState = QuickAttachmentDrawer.HALF_EXPANDED;
+
+    @Override
+    public void onClick(View v) {
+      InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+      input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
+      composeText.clearFocus();
+      hideEmojiPopup(false);
+      quickAttachmentDrawer.setDrawerStateAndAnimate(nextDrawerState);
+    }
+
+    @Override
+    public void onCollapsed() {
+      getSupportActionBar().show();
+      nextDrawerState = QuickAttachmentDrawer.HALF_EXPANDED;
+    }
+
+    @Override
+    public void onExpanded() {
+      getSupportActionBar().hide();
+      nextDrawerState = QuickAttachmentDrawer.COLLAPSED;
+    }
+
+    @Override
+    public void onHalfExpanded() {
+      getSupportActionBar().hide();
+      nextDrawerState = QuickAttachmentDrawer.COLLAPSED;
+    }
+
+    @Override
+    public void onImageCapture(final byte[] data) {
+      quickAttachmentDrawer.setDrawerStateAndAnimate(QuickAttachmentDrawer.COLLAPSED);
+        new AsyncTask<Void, Void, Uri>() {
+          @Override
+          protected Uri doInBackground(Void... voids) {
+            try {
+              File tempDirectory = getDir("media", Context.MODE_PRIVATE);
+              File tempFile = File.createTempFile("image", ".jpg", tempDirectory);
+              FileOutputStream fileOutputStream = new EncryptingPartOutputStream(tempFile, masterSecret);
+              fileOutputStream.write(data);
+              fileOutputStream.close();
+              return Uri.fromFile(tempFile);
+            } catch (IOException e) {
+              e.printStackTrace();
+            }
+            return null;
+          }
+
+          @Override
+          protected void onPostExecute(Uri uri) {
+            if (uri != null)
+              addAttachmentEncryptedImage(uri);
+          }
+        }.execute();
+    }
+  }
+
   private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
     @Override
     public void onClick(View v) {
@@ -1370,8 +1482,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
 
     @Override
     public void onFocusChange(View v, boolean hasFocus) {
-      if (hasFocus) {
+      if (hasFocus && isEmojiDrawerOpen()) {
         hideEmojiPopup(true);
+      } else if (hasFocus && quickAttachmentDrawer.getDrawerState() != QuickAttachmentDrawer.COLLAPSED) {
+        quickAttachmentDrawer.setDrawerStateAndAnimate(QuickAttachmentDrawer.COLLAPSED);
       }
     }
   }
diff --git a/src/org/thoughtcrime/securesms/components/CameraView.java b/src/org/thoughtcrime/securesms/components/CameraView.java
new file mode 100644
index 0000000000..766e97f462
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/CameraView.java
@@ -0,0 +1,515 @@
+/***
+ Copyright (c) 2013-2014 CommonsWare, LLC
+ Portions Copyright (C) 2007 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 org.thoughtcrime.securesms.components;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.hardware.Camera;
+import android.hardware.Camera.AutoFocusCallback;
+import android.hardware.Camera.PreviewCallback;
+import android.os.Build;
+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.view.ViewGroup;
+import java.io.IOException;
+
+import com.commonsware.cwac.camera.CameraHost;
+import com.commonsware.cwac.camera.CameraHost.FailureReason;
+import com.commonsware.cwac.camera.CameraHostProvider;
+import com.commonsware.cwac.camera.PreviewStrategy;
+
+public class CameraView extends ViewGroup implements AutoFocusCallback {
+  static final String TAG = "CWAC-Camera";
+  private PreviewStrategy previewStrategy;
+  private Camera.Size     previewSize;
+  private Camera              camera                 = null;
+  private boolean             inPreview              = false;
+  private CameraHost          host                   = null;
+  private OnOrientationChange onOrientationChange    = null;
+  private int                 displayOrientation     = -1;
+  private int                 outputOrientation      = -1;
+  private int                 cameraId               = -1;
+  private boolean             isAutoFocusing         = false;
+  private int                 lastPictureOrientation = -1;
+
+  public CameraView(Context context) {
+    super(context);
+
+    onOrientationChange = new OnOrientationChange(context.getApplicationContext());
+  }
+
+  public CameraView(Context context, AttributeSet attrs) {
+    this(context, attrs, 0);
+  }
+
+  public CameraView(Context context, AttributeSet attrs, int defStyle) {
+    super(context, attrs, defStyle);
+
+    onOrientationChange = new OnOrientationChange(context.getApplicationContext());
+
+    if (context instanceof CameraHostProvider) {
+      setHost(((CameraHostProvider)context).getCameraHost());
+    } else {
+      throw new IllegalArgumentException("To use the two- or "
+          + "three-parameter constructors on CameraView, "
+          + "your activity needs to implement the "
+          + "CameraHostProvider interface");
+    }
+  }
+
+  public CameraHost getHost() {
+    return (host);
+  }
+
+  // must call this after constructor, before onResume()
+
+  public void setHost(CameraHost host) {
+    this.host = host;
+
+    if (host.getDeviceProfile().useTextureView()) {
+      previewStrategy = new TexturePreviewStrategy(this);
+    } else {
+      previewStrategy = new SurfacePreviewStrategy(this);
+    }
+  }
+
+  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+  public void onResume() {
+    addView(previewStrategy.getWidget());
+
+    if (camera == null) {
+      try {
+        cameraId = getHost().getCameraId();
+
+        if (cameraId >= 0) {
+          camera = Camera.open(cameraId);
+
+          if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+            onOrientationChange.enable();
+          }
+
+          setCameraDisplayOrientation();
+        }
+        else {
+          getHost().onCameraFail(FailureReason.NO_CAMERAS_REPORTED);
+        }
+      }
+      catch (Exception e) {
+        getHost().onCameraFail(FailureReason.UNKNOWN);
+      }
+    }
+  }
+
+  public void onPause() {
+    if (camera != null) {
+      previewDestroyed();
+    }
+
+    removeView(previewStrategy.getWidget());
+    onOrientationChange.disable();
+    lastPictureOrientation=-1;
+  }
+
+  // based on CameraPreview.java from ApiDemos
+
+  @Override
+  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    final int width=
+        resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
+    final int height=
+        resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
+    setMeasuredDimension(width, height);
+
+    if (width > 0 && height > 0) {
+      if (camera != null) {
+        Camera.Size newSize=null;
+
+        try {
+          if (getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY) {
+
+            newSize=
+                getHost().getPreferredPreviewSizeForVideo(getDisplayOrientation(),
+                    width,
+                    height,
+                    camera.getParameters(),
+                    null);
+
+          }
+
+          if (newSize == null || newSize.width * newSize.height < 65536) {
+            newSize=
+                getHost().getPreviewSize(getDisplayOrientation(),
+                    width, height,
+                    camera.getParameters());
+          }
+        }
+        catch (Exception e) {
+          android.util.Log.e(getClass().getSimpleName(),
+              "Could not work with camera parameters?",
+              e);
+          // TODO get this out to library clients
+        }
+
+        if (newSize != null) {
+          if (previewSize == null) {
+            previewSize=newSize;
+          }
+          else if (previewSize.width != newSize.width
+              || previewSize.height != newSize.height) {
+            if (inPreview) {
+              stopPreview();
+            }
+
+            previewSize=newSize;
+            initPreview(width, height, false);
+          }
+        }
+      }
+    }
+  }
+
+  // based on CameraPreview.java from ApiDemos
+
+  @Override
+  protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    if (changed && getChildCount() > 0) {
+      final View child=getChildAt(0);
+      final int width=r - l;
+      final int height=b - t;
+      int previewWidth=width;
+      int previewHeight=height;
+
+      // handle orientation
+
+      if (previewSize != null) {
+        if (getDisplayOrientation() == 90
+            || getDisplayOrientation() == 270) {
+          previewWidth=previewSize.height;
+          previewHeight=previewSize.width;
+        }
+        else {
+          previewWidth=previewSize.width;
+          previewHeight=previewSize.height;
+        }
+      }
+
+      boolean useFirstStrategy=
+          (width * previewHeight > height * previewWidth);
+      boolean useFullBleed=getHost().useFullBleedPreview();
+
+      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);
+      }
+    }
+  }
+
+  public int getDisplayOrientation() {
+    return(displayOrientation);
+  }
+
+  public void lockToLandscape(boolean enable) {
+    if (enable) {
+      getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+      onOrientationChange.enable();
+    }
+    else {
+      getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+      onOrientationChange.disable();
+    }
+  }
+
+  public void restartPreview() {
+    if (!inPreview) {
+      startPreview();
+    }
+  }
+
+  public void autoFocus() {
+    if (inPreview) {
+      camera.autoFocus(this);
+      isAutoFocusing=true;
+    }
+  }
+
+  public void cancelAutoFocus() {
+    camera.cancelAutoFocus();
+  }
+
+  public boolean isAutoFocusAvailable() {
+    return(inPreview);
+  }
+
+  @Override
+  public void onAutoFocus(boolean success, Camera camera) {
+    isAutoFocusing=false;
+
+    if (getHost() instanceof AutoFocusCallback) {
+      getHost().onAutoFocus(success, camera);
+    }
+  }
+
+  public String getFlashMode() {
+    return(camera.getParameters().getFlashMode());
+  }
+
+  public void setFlashMode(String mode) {
+    if (camera != null) {
+      Camera.Parameters params=camera.getParameters();
+
+      params.setFlashMode(mode);
+      camera.setParameters(params);
+    }
+  }
+
+  public void setOneShotPreviewCallback(PreviewCallback callback) {
+    if (camera != null)
+      camera.setOneShotPreviewCallback(callback);
+  }
+
+  public Camera.Parameters getCameraParameters() {
+    return camera.getParameters();
+  }
+
+  void previewCreated() {
+    if (camera != null) {
+      try {
+        previewStrategy.attach(camera);
+      }
+      catch (IOException e) {
+        getHost().handleException(e);
+      }
+    }
+  }
+
+  void previewDestroyed() {
+    if (camera != null) {
+      previewStopped();
+      camera.release();
+      camera=null;
+    }
+  }
+
+  void previewReset(int width, int height) {
+    previewStopped();
+    initPreview(width, height);
+  }
+
+  private void previewStopped() {
+    if (inPreview) {
+      stopPreview();
+    }
+  }
+
+  public void initPreview(int w, int h) {
+    initPreview(w, h, true);
+  }
+
+  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+  public void initPreview(int w, int h, boolean firstRun) {
+    if (camera != null) {
+      Camera.Parameters parameters=camera.getParameters();
+
+      parameters.setPreviewSize(previewSize.width, previewSize.height);
+
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        parameters.setRecordingHint(getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY);
+      }
+
+      requestLayout();
+
+      camera.setParameters(getHost().adjustPreviewParameters(parameters));
+      startPreview();
+    }
+  }
+
+  private void startPreview() {
+    camera.startPreview();
+    inPreview=true;
+    getHost().autoFocusAvailable();
+  }
+
+  private void stopPreview() {
+    inPreview=false;
+    getHost().autoFocusUnavailable();
+    camera.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();
+    int rotation=
+        getActivity().getWindowManager().getDefaultDisplay()
+            .getRotation();
+    int degrees=0;
+    DisplayMetrics dm=new DisplayMetrics();
+
+    Camera.getCameraInfo(cameraId, info);
+    getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+    switch (rotation) {
+      case Surface.ROTATION_0:
+        degrees=0;
+        break;
+      case Surface.ROTATION_90:
+        degrees=90;
+        break;
+      case Surface.ROTATION_180:
+        degrees=180;
+        break;
+      case Surface.ROTATION_270:
+        degrees=270;
+        break;
+    }
+
+    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+      displayOrientation=(info.orientation + degrees) % 360;
+      displayOrientation=(360 - displayOrientation) % 360;
+    }
+    else {
+      displayOrientation=(info.orientation - degrees + 360) % 360;
+    }
+
+    boolean wasInPreview=inPreview;
+
+    if (inPreview) {
+      stopPreview();
+    }
+
+    camera.setDisplayOrientation(displayOrientation);
+
+    if (wasInPreview) {
+      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) {
+      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)
+
+  public int getCameraPictureRotation(int orientation) {
+    Camera.CameraInfo info=new Camera.CameraInfo();
+    Camera.getCameraInfo(cameraId, info);
+    int rotation=0;
+
+    orientation=(orientation + 45) / 90 * 90;
+
+    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+      rotation=(info.orientation - orientation + 360) % 360;
+    }
+    else { // back-facing camera
+      rotation=(info.orientation + orientation) % 360;
+    }
+
+    return(rotation);
+  }
+
+  Activity getActivity() {
+    return((Activity)getContext());
+  }
+
+  private class OnOrientationChange extends OrientationEventListener {
+    private boolean isEnabled=false;
+
+    public OnOrientationChange(Context context) {
+      super(context);
+      disable();
+    }
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+      if (camera != null && orientation != ORIENTATION_UNKNOWN) {
+        int newOutputOrientation=getCameraPictureRotation(orientation);
+
+        if (newOutputOrientation != outputOrientation) {
+          outputOrientation=newOutputOrientation;
+
+          Camera.Parameters params=camera.getParameters();
+
+          params.setRotation(outputOrientation);
+
+          try {
+            camera.setParameters(params);
+            lastPictureOrientation=outputOrientation;
+          }
+          catch (Exception e) {
+            Log.e(getClass().getSimpleName(),
+                "Exception updating camera parameters in orientation change",
+                e);
+            // TODO: get this info out to hosting app
+          }
+        }
+      }
+    }
+
+    @Override
+    public void enable() {
+      isEnabled=true;
+      super.enable();
+    }
+
+    @Override
+    public void disable() {
+      isEnabled=false;
+      super.disable();
+    }
+
+    boolean isEnabled() {
+      return(isEnabled);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/QuickAttachmentDrawer.java
new file mode 100644
index 0000000000..2ab5d641f3
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/QuickAttachmentDrawer.java
@@ -0,0 +1,503 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+
+import com.commonsware.cwac.camera.SimpleCameraHost;
+
+import org.thoughtcrime.securesms.R;
+
+public class QuickAttachmentDrawer extends ViewGroup {
+  @IntDef({COLLAPSED, HALF_EXPANDED, FULL_EXPANDED})
+  public @interface DrawerState {}
+
+  public static final int COLLAPSED = 0;
+  public static final int HALF_EXPANDED = 1;
+  public static final int FULL_EXPANDED = 2;
+
+  private static final float FULL_EXPANDED_ANCHOR_POINT = 1.f;
+  private static final float COLLAPSED_ANCHOR_POINT = 0.f;
+
+  private final ViewDragHelper dragHelper;
+  private final QuickCamera quickCamera;
+  private final View controls;
+  private View coverView;
+  private ImageButton fullScreenButton;
+  private @DrawerState int drawerState;
+  private float slideOffset, initialMotionX, initialMotionY, halfExpandedAnchorPoint;
+  private boolean initialSetup, hasCamera, startCamera, stopCamera, landscape, belowICS;
+  private int slideRange, baseHalfHeight;
+  private Rect drawChildrenRect = new Rect();
+  private QuickAttachmentDrawerListener listener;
+
+  public QuickAttachmentDrawer(Context context) {
+    this(context, null);
+  }
+
+  public QuickAttachmentDrawer(Context context, AttributeSet attrs) {
+    this(context, attrs, 0);
+  }
+
+  public QuickAttachmentDrawer(Context context, AttributeSet attrs, int defStyle) {
+    super(context, attrs, defStyle);
+    initialSetup = true;
+    startCamera = false;
+    stopCamera = false;
+    drawerState = COLLAPSED;
+    baseHalfHeight = getResources().getDimensionPixelSize(R.dimen.quick_media_drawer_default_height);
+    halfExpandedAnchorPoint = COLLAPSED_ANCHOR_POINT;
+    int rotation = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+    landscape = rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
+    belowICS = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+    hasCamera = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) && Camera.getNumberOfCameras() > 0;
+    if (hasCamera) {
+      setBackgroundResource(android.R.color.black);
+      dragHelper = ViewDragHelper.create(this, 1.f, new ViewDragHelperCallback());
+      quickCamera = new QuickCamera(context);
+      controls = inflate(getContext(), R.layout.quick_camera_controls, null);
+      initializeControlsView();
+      addView(quickCamera);
+      addView(controls);
+    } else {
+      dragHelper = null;
+      quickCamera = null;
+      controls = null;
+    }
+  }
+
+  public boolean hasCamera() {
+    return hasCamera;
+  }
+
+  private void initializeHalfExpandedAnchorPoint() {
+    if (initialSetup) {
+      if (getChildCount() == 3)
+        coverView = getChildAt(2);
+      else
+        coverView = getChildAt(0);
+      slideRange = getMeasuredHeight();
+      int anchorHeight = slideRange - baseHalfHeight;
+      halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(anchorHeight);
+      initialSetup = false;
+    }
+  }
+
+  private void initializeControlsView() {
+    controls.findViewById(R.id.shutter_button).setOnClickListener(new OnClickListener() {
+      @Override
+      public void onClick(View v) {
+        boolean crop = drawerState != FULL_EXPANDED;
+        int imageHeight = crop ? baseHalfHeight : quickCamera.getMeasuredHeight();
+        Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight);
+        quickCamera.takePicture(crop, previewRect);
+      }
+    });
+
+    final ImageButton swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
+    if (quickCamera.isMultipleCameras()) {
+      swapCameraButton.setVisibility(View.VISIBLE);
+      swapCameraButton.setOnClickListener(new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+          quickCamera.swapCamera();
+          swapCameraButton.setImageResource(quickCamera.isRearCamera() ? R.drawable.quick_camera_front : R.drawable.quick_camera_rear);
+        }
+      });
+    }
+
+    fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
+    fullScreenButton.setOnClickListener(new OnClickListener() {
+      @Override
+      public void onClick(View v) {
+        if (drawerState == HALF_EXPANDED || drawerState == COLLAPSED)
+          setDrawerStateAndAnimate(FULL_EXPANDED);
+        else if (landscape || belowICS)
+          setDrawerStateAndAnimate(COLLAPSED);
+        else
+          setDrawerStateAndAnimate(HALF_EXPANDED);
+      }
+    });
+  }
+
+  @Override
+  protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    final int paddingLeft = getPaddingLeft();
+    final int paddingTop = getPaddingTop();
+
+    final int childCount = getChildCount();
+
+    for (int i = 0; i < childCount; i++) {
+      final View child = getChildAt(i);
+
+      final int childHeight = child.getMeasuredHeight();
+      int childTop = paddingTop;
+      int childBottom;
+      int childLeft = paddingLeft;
+
+      if (child == quickCamera) {
+        childTop = computeCameraTopPosition(slideOffset);
+        childBottom = childTop + childHeight;
+        if (quickCamera.getMeasuredWidth() < getMeasuredWidth())
+          childLeft = (getMeasuredWidth() - quickCamera.getMeasuredWidth()) / 2 + paddingLeft;
+      } else if (child == controls) {
+        childBottom = getMeasuredHeight();
+      } else {
+        childBottom = computeCoverBottomPosition(slideOffset);
+        childTop = childBottom - childHeight;
+      }
+      final int childRight = childLeft + child.getMeasuredWidth();
+
+      if (childHeight > 0)
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+  }
+
+  @Override
+  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+    if (widthMode != MeasureSpec.EXACTLY) {
+      throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
+    } else if (heightMode != MeasureSpec.EXACTLY) {
+      throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
+    }
+
+    final int childCount = getChildCount();
+    if ((hasCamera && childCount != 3) || (!hasCamera && childCount != 1))
+      throw new IllegalStateException("QuickAttachmentDrawer layouts may only have 1 child.");
+
+    int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
+
+    for (int i = 0; i < childCount; i++) {
+      final View child = getChildAt(i);
+      final LayoutParams lp = child.getLayoutParams();
+
+      if (child.getVisibility() == GONE && i == 0) {
+        continue;
+      }
+
+      int childWidthSpec;
+      switch (lp.width) {
+        case LayoutParams.WRAP_CONTENT:
+          childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+          break;
+        case LayoutParams.MATCH_PARENT:
+          childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+          break;
+        default:
+          childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+          break;
+      }
+
+      int childHeightSpec;
+      switch (lp.height) {
+        case LayoutParams.WRAP_CONTENT:
+          childHeightSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.AT_MOST);
+          break;
+        case LayoutParams.MATCH_PARENT:
+          childHeightSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.EXACTLY);
+          break;
+        default:
+          childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+          break;
+      }
+
+      child.measure(childWidthSpec, childHeightSpec);
+    }
+
+    setMeasuredDimension(widthSize, heightSize);
+    initializeHalfExpandedAnchorPoint();
+  }
+
+  @Override
+  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+    super.onSizeChanged(w, h, oldw, oldh);
+    if (h != oldh)
+      initialSetup = true;
+  }
+
+  @Override
+  protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
+    boolean result;
+    final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
+
+    canvas.getClipBounds(drawChildrenRect);
+    if (child == coverView)
+      drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom());
+    else if (coverView != null)
+      drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom());
+    canvas.clipRect(drawChildrenRect);
+    result = super.drawChild(canvas, child, drawingTime);
+    canvas.restoreToCount(save);
+    return result;
+  }
+
+  @Override
+  public void computeScroll() {
+    if (dragHelper != null && dragHelper.continueSettling(true)) {
+      ViewCompat.postInvalidateOnAnimation(this);
+    } else if (stopCamera) {
+      stopCamera = false;
+      quickCamera.onPause();
+    } else if (startCamera) {
+      startCamera = false;
+      quickCamera.onResume();
+    }
+  }
+
+  private void setDrawerState(@DrawerState int drawerState) {
+    if (hasCamera) {
+      switch (drawerState) {
+        case COLLAPSED:
+          quickCamera.previewCreated();
+          if (quickCamera.isStarted())
+            stopCamera = true;
+          slideOffset = COLLAPSED_ANCHOR_POINT;
+          startCamera = false;
+          fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
+          if (listener != null) listener.onCollapsed();
+          break;
+        case HALF_EXPANDED:
+          if (landscape || belowICS) {
+            setDrawerState(FULL_EXPANDED);
+            return;
+          }
+          if (!quickCamera.isStarted())
+            startCamera = true;
+          slideOffset = halfExpandedAnchorPoint;
+          stopCamera = false;
+          fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
+          if (listener != null) listener.onHalfExpanded();
+          break;
+        case FULL_EXPANDED:
+          if (!quickCamera.isStarted())
+            startCamera = true;
+          slideOffset = FULL_EXPANDED_ANCHOR_POINT;
+          stopCamera = false;
+          fullScreenButton.setImageResource(landscape || belowICS ? R.drawable.quick_camera_hide : R.drawable.quick_camera_exit_fullscreen);
+          if (listener != null) listener.onExpanded();
+          break;
+      }
+      this.drawerState = drawerState;
+    }
+  }
+
+  public
+  @DrawerState
+  int getDrawerState() {
+    return drawerState;
+  }
+
+  public void setDrawerStateAndAnimate(@DrawerState int drawerState) {
+    setDrawerState(drawerState);
+    slideTo(slideOffset);
+  }
+
+  public void setQuickAttachmentDrawerListener(QuickAttachmentDrawerListener listener) {
+    this.listener = listener;
+  }
+
+  public void setQuickCameraListener(QuickCamera.QuickCameraListener listener) {
+    if (quickCamera != null) quickCamera.setQuickCameraListener(listener);
+  }
+
+  public interface QuickAttachmentDrawerListener {
+    void onCollapsed();
+    void onExpanded();
+    void onHalfExpanded();
+  }
+
+  private class ViewDragHelperCallback extends ViewDragHelper.Callback {
+
+    @Override
+    public boolean tryCaptureView(View child, int pointerId) {
+      return child == controls && !belowICS;
+    }
+
+    @Override
+    public void onViewDragStateChanged(int state) {
+      if (dragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
+        setDrawerState(drawerState);
+        requestLayout();
+      }
+    }
+
+    @Override
+    public void onViewCaptured(View capturedChild, int activePointerId) {
+    }
+
+    @Override
+    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+      int newTop = coverView.getTop() + dy;
+      final int expandedTop = computeCoverBottomPosition(FULL_EXPANDED_ANCHOR_POINT) - coverView.getHeight();
+      final int collapsedTop = computeCoverBottomPosition(COLLAPSED_ANCHOR_POINT) - coverView.getHeight();
+      newTop = Math.min(Math.max(newTop, expandedTop), collapsedTop);
+      slideOffset = computeSlideOffsetFromCoverBottom(newTop + coverView.getHeight());
+      requestLayout();
+    }
+
+    @Override
+    public void onViewReleased(View releasedChild, float xvel, float yvel) {
+      if (releasedChild == controls) {
+        float direction = -yvel;
+        int drawerState = COLLAPSED;
+
+        if (direction > 1) {
+          drawerState = FULL_EXPANDED;
+        } else if (direction < -1) {
+          boolean halfExpand = (slideOffset > halfExpandedAnchorPoint && !landscape);
+          drawerState = halfExpand ? HALF_EXPANDED : COLLAPSED;
+        } else if (!landscape) {
+          if (halfExpandedAnchorPoint != 1 && slideOffset >= (1.f + halfExpandedAnchorPoint) / 2) {
+            drawerState = FULL_EXPANDED;
+          } else if (halfExpandedAnchorPoint == 1 && slideOffset >= 0.5f) {
+            drawerState = FULL_EXPANDED;
+          } else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint) {
+            drawerState = HALF_EXPANDED;
+          } else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint / 2) {
+            drawerState = HALF_EXPANDED;
+          }
+        }
+
+        setDrawerState(drawerState);
+        dragHelper.captureChildView(coverView, 0);
+        dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight());
+        dragHelper.captureChildView(quickCamera, 0);
+        dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
+        ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
+      }
+    }
+
+    @Override
+    public int getViewVerticalDragRange(View child) {
+      return slideRange;
+    }
+
+    @Override
+    public int clampViewPositionVertical(View child, int top, int dy) {
+      return top;
+    }
+  }
+
+  @Override
+  public boolean onInterceptTouchEvent(MotionEvent event) {
+    if (dragHelper != null) {
+      final int action = MotionEventCompat.getActionMasked(event);
+
+      if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+        dragHelper.cancel();
+        return false;
+      }
+
+      final float x = event.getX();
+      final float y = event.getY();
+
+      switch (action) {
+        case MotionEvent.ACTION_DOWN: {
+          initialMotionX = x;
+          initialMotionY = y;
+          break;
+        }
+
+        case MotionEvent.ACTION_MOVE: {
+          final float adx = Math.abs(x - initialMotionX);
+          final float ady = Math.abs(y - initialMotionY);
+          final int dragSlop = dragHelper.getTouchSlop();
+
+          if (adx > dragSlop && ady < dragSlop) {
+            return super.onInterceptTouchEvent(event);
+          }
+
+          if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) initialMotionX, (int) initialMotionY)) {
+            dragHelper.cancel();
+            return false;
+          }
+          break;
+        }
+      }
+      return dragHelper.shouldInterceptTouchEvent(event);
+    }
+    return super.onInterceptTouchEvent(event);
+  }
+
+
+  @Override
+  public boolean onTouchEvent(@NonNull MotionEvent event) {
+    if (dragHelper != null) {
+      dragHelper.processTouchEvent(event);
+      return true;
+    }
+    return super.onTouchEvent(event);
+  }
+
+  private boolean isDragViewUnder(int x, int y) {
+    int[] viewLocation = new int[2];
+    quickCamera.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();
+  }
+
+  private int computeCameraTopPosition(float slideOffset) {
+    float clampedOffset = slideOffset - halfExpandedAnchorPoint;
+    if (clampedOffset < COLLAPSED_ANCHOR_POINT)
+      clampedOffset = COLLAPSED_ANCHOR_POINT;
+    else
+      clampedOffset = clampedOffset / (FULL_EXPANDED_ANCHOR_POINT - halfExpandedAnchorPoint);
+    float slidePixelOffset = slideOffset * slideRange +
+        (quickCamera.getMeasuredHeight() - baseHalfHeight) / 2 * (FULL_EXPANDED_ANCHOR_POINT - clampedOffset);
+    float marginPixelOffset = (getMeasuredHeight() - quickCamera.getMeasuredHeight()) / 2 * clampedOffset;
+    return (int) (getMeasuredHeight() - slidePixelOffset + marginPixelOffset);
+  }
+
+  private int computeCoverBottomPosition(float slideOffset) {
+    int slidePixelOffset = (int) (slideOffset * slideRange);
+    return getMeasuredHeight() - getPaddingBottom() - slidePixelOffset;
+  }
+
+  private void slideTo(float slideOffset) {
+    if (dragHelper != null && !belowICS) {
+      dragHelper.smoothSlideViewTo(coverView, coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight());
+      dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
+      ViewCompat.postInvalidateOnAnimation(this);
+    } else {
+      invalidate();
+    }
+  }
+
+  private float computeSlideOffsetFromCoverBottom(int topPosition) {
+    final int topBoundCollapsed = computeCoverBottomPosition(0);
+    return (float) (topBoundCollapsed - topPosition) / slideRange;
+  }
+
+  public void onPause() {
+    quickCamera.onPause();
+  }
+
+  public void onResume() {
+    if (hasCamera && (drawerState == HALF_EXPANDED || drawerState == FULL_EXPANDED))
+      quickCamera.onResume();
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/components/QuickCamera.java b/src/org/thoughtcrime/securesms/components/QuickCamera.java
new file mode 100644
index 0000000000..88f50c0d4f
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/QuickCamera.java
@@ -0,0 +1,185 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.hardware.Camera;
+import android.os.AsyncTask;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.commonsware.cwac.camera.SimpleCameraHost;
+
+import org.thoughtcrime.securesms.R;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class QuickCamera extends CameraView {
+  private QuickCameraListener listener;
+  private boolean started, savingImage;
+  private int rotation;
+  private QuickCameraHost cameraHost;
+
+  public QuickCamera(Context context) {
+    super(context);
+    started = false;
+    savingImage = false;
+    setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+    cameraHost = new QuickCameraHost(context);
+    setHost(cameraHost);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    rotation = getCameraPictureOrientation();
+    started = true;
+  }
+
+  @Override
+  public void onPause() {
+    started = false;
+    super.onPause();
+  }
+
+  public boolean isStarted() {
+    return started;
+  }
+
+  public void takePicture(final boolean crop, final Rect previewRect) {
+    setOneShotPreviewCallback(new Camera.PreviewCallback() {
+      @Override
+      public void onPreviewFrame(byte[] data, Camera camera) {
+        new AsyncTask<byte[], Void, byte[]>() {
+          @Override
+          protected byte[] doInBackground(byte[]... params) {
+            byte[] data = params[0];
+            if (savingImage)
+              return null;
+            savingImage = true;
+            try {
+              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+              int previewWidth = getCameraParameters().getPreviewSize().width;
+              int previewHeight = getCameraParameters().getPreviewSize().height;
+              YuvImage previewImage = new YuvImage(data, ImageFormat.NV21, previewWidth, previewHeight, null);
+
+              if (crop) {
+                float newWidth, newHeight;
+                if (rotation == 90 || rotation == 270) {
+                  newWidth = previewRect.height();
+                  newHeight = previewRect.width();
+                } else {
+                  newWidth = previewRect.width();
+                  newHeight = previewRect.height();
+                }
+                float centerX = previewWidth / 2;
+                float centerY = previewHeight / 2;
+                previewRect.set((int) (centerX - newWidth / 2),
+                    (int) (centerY - newHeight / 2),
+                    (int) (centerX + newWidth / 2),
+                    (int) (centerY + newHeight / 2));
+              } else if (rotation == 90 || rotation == 270) {
+                previewRect.set(0, 0, previewRect.height(), previewRect.width());
+              }
+              previewImage.compressToJpeg(previewRect, 100, byteArrayOutputStream);
+              byte[] bytes = byteArrayOutputStream.toByteArray();
+              byteArrayOutputStream.close();
+              byteArrayOutputStream = new ByteArrayOutputStream();
+              Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+              if (rotation != 0)
+                bitmap = rotateBitmap(bitmap, rotation);
+              bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
+              byte[] finalImageByteArray = byteArrayOutputStream.toByteArray();
+              byteArrayOutputStream.close();
+              savingImage = false;
+              return finalImageByteArray;
+            } catch (IOException e) {
+              savingImage = false;
+              return null;
+            }
+          }
+
+          @Override
+          protected void onPostExecute(byte[] data) {
+            if (data != null && listener != null)
+              listener.onImageCapture(data);
+          }
+        }.execute(data);
+      }
+    });
+  }
+
+  private static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
+    Matrix matrix = new Matrix();
+    matrix.postRotate(angle);
+    Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+    if (rotated != bitmap) bitmap.recycle();
+    return rotated;
+  }
+
+  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(final byte[] data);
+  }
+
+  private class QuickCameraHost extends SimpleCameraHost {
+    int cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
+
+    public QuickCameraHost(Context context) {
+      super(context);
+    }
+
+    @Override
+    public Camera.Parameters adjustPreviewParameters(Camera.Parameters parameters) {
+      List<String> focusModes = parameters.getSupportedFocusModes();
+      if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
+        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+      else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
+        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+      return parameters;
+    }
+
+    @Override
+    public int getCameraId() {
+      return cameraId;
+    }
+
+    public void swapCameraId() {
+      if (isMultipleCameras()) {
+        if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
+          cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
+        else
+          cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
+      }
+    }
+
+    @Override
+    public void onCameraFail(FailureReason reason) {
+      super.onCameraFail(reason);
+      Toast.makeText(getContext(), R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show();
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/SurfacePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/SurfacePreviewStrategy.java
new file mode 100644
index 0000000000..0b95b8cf7a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/SurfacePreviewStrategy.java
@@ -0,0 +1,72 @@
+/***
+ 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;
+
+import android.hardware.Camera;
+import android.media.MediaRecorder;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import com.commonsware.cwac.camera.PreviewStrategy;
+
+import java.io.IOException;
+
+class SurfacePreviewStrategy implements PreviewStrategy,
+    SurfaceHolder.Callback {
+  private final CameraView cameraView;
+  private SurfaceView preview=null;
+  private SurfaceHolder previewHolder=null;
+
+  @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) {
+    cameraView.previewCreated();
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder holder, int format,
+                             int width, int height) {
+    cameraView.initPreview(width, height);
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder holder) {
+    cameraView.previewDestroyed();
+  }
+
+  @Override
+  public void attach(Camera camera) throws IOException {
+    camera.setPreviewDisplay(previewHolder);
+  }
+
+  @Override
+  public void attach(MediaRecorder recorder) {
+    recorder.setPreviewDisplay(previewHolder.getSurface());
+  }
+
+  @Override
+  public View getWidget() {
+    return(preview);
+  }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/TexturePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/TexturePreviewStrategy.java
new file mode 100644
index 0000000000..d63faeeeda
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/TexturePreviewStrategy.java
@@ -0,0 +1,88 @@
+package org.thoughtcrime.securesms.components;
+/***
+ 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.view.TextureView;
+import android.view.View;
+
+import com.commonsware.cwac.camera.PreviewStrategy;
+
+import java.io.IOException;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class TexturePreviewStrategy implements PreviewStrategy,
+    TextureView.SurfaceTextureListener {
+  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) {
+    this.surface=surface;
+
+    cameraView.previewCreated();
+    cameraView.initPreview(width, height);
+  }
+
+  @Override
+  public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
+                                          int width, int height) {
+    cameraView.previewReset(width, height);
+  }
+
+  @Override
+  public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+    cameraView.previewDestroyed();
+
+    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 View getWidget() {
+    return(widget);
+  }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
index 4d580995bd..af68acfcbf 100644
--- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java
+++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
@@ -175,8 +175,9 @@ public class ThumbnailView extends FrameLayout {
   private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) {
 
     final GenericRequestBuilder builder;
-    if (slide.isDraft()) builder = buildDraftGlideRequest(slide);
-    else                 builder = buildEncryptedPartGlideRequest(slide, masterSecret);
+    if      (slide.isDraft() && slide.isEncrypted()) builder = buildEncryptedDraftGlideRequest(slide, masterSecret);
+    else if (slide.isDraft())                        builder = buildDraftGlideRequest(slide);
+    else                                             builder = buildEncryptedPartGlideRequest(slide, masterSecret);
     return builder;
   }
 
@@ -186,6 +187,15 @@ public class ThumbnailView extends FrameLayout {
                                    .listener(new PduThumbnailSetListener(slide.getPart()));
   }
 
+  private GenericRequestBuilder buildEncryptedDraftGlideRequest(Slide slide, MasterSecret masterSecret) {
+    if (masterSecret == null) {
+      throw new IllegalStateException("null MasterSecret when loading encrypted draft thumbnail");
+    }
+
+    return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
+        .fitCenter();
+  }
+
   private GenericRequestBuilder buildEncryptedPartGlideRequest(Slide slide, MasterSecret masterSecret) {
     if (masterSecret == null) {
       throw new IllegalStateException("null MasterSecret when loading non-draft thumbnail");
diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java
index 631878f938..0a923b2016 100644
--- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java
@@ -101,10 +101,11 @@ public class DraftDatabase extends Database {
   }
 
   public static class Draft {
-    public static final String TEXT  = "text";
-    public static final String IMAGE = "image";
-    public static final String VIDEO = "video";
-    public static final String AUDIO = "audio";
+    public static final String TEXT            = "text";
+    public static final String IMAGE           = "image";
+    public static final String VIDEO           = "video";
+    public static final String AUDIO           = "audio";
+    public static final String ENCRYPTED_IMAGE = "encrypted_image";
 
     private final String type;
     private final String value;
@@ -124,10 +125,11 @@ public class DraftDatabase extends Database {
 
     public String getSnippet(Context context) {
       switch (type) {
-      case TEXT:  return value;
-      case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet);
-      case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet);
-      case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet);
+      case TEXT:            return value;
+      case ENCRYPTED_IMAGE:
+      case IMAGE:           return context.getString(R.string.DraftDatabase_Draft_image_snippet);
+      case VIDEO:           return context.getString(R.string.DraftDatabase_Draft_video_snippet);
+      case AUDIO:           return context.getString(R.string.DraftDatabase_Draft_audio_snippet);
       default:    return null;
       }
     }
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index f1dc5871a2..027a4fb534 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -24,6 +24,7 @@ import android.net.Uri;
 import android.os.Build;
 import android.provider.ContactsContract;
 import android.provider.MediaStore;
+import android.support.annotation.Nullable;
 import android.util.Log;
 import android.view.View;
 import android.view.animation.AlphaAnimation;
@@ -36,6 +37,7 @@ import android.widget.Toast;
 
 import org.thoughtcrime.securesms.R;
 import org.thoughtcrime.securesms.components.ThumbnailView;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.util.BitmapDecodingException;
 
 import java.io.File;
@@ -106,11 +108,24 @@ public class AttachmentManager {
     setMedia(new AudioSlide(context, audio));
   }
 
+  public void setEncryptedImage(Uri uri, MasterSecret masterSecret) throws IOException, BitmapDecodingException {
+    setMedia(new ImageSlide(context, masterSecret, uri), masterSecret);
+  }
+
   public void setMedia(final Slide slide) {
+    setMedia(slide, null);
+  }
+
+  public void setMedia(final Slide slide, @Nullable MasterSecret masterSecret) {
+    Slide thumbnailSlide = slideDeck.getThumbnailSlide(context);
+    if (thumbnailSlide != null && thumbnailSlide.isEncrypted()) {
+      Uri dataUri = slideDeck.getThumbnailSlide(context).getPart().getDataUri();
+      new File(dataUri.getPath()).delete();
+    }
     slideDeck.clear();
     slideDeck.addSlide(slide);
     attachmentView.setVisibility(View.VISIBLE);
-    thumbnail.setImageResource(slide);
+    thumbnail.setImageResource(slide, masterSecret);
     attachmentListener.onAttachmentChanged();
   }
 
diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
index 606e00c27b..3c962a324b 100644
--- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
@@ -20,25 +20,36 @@ import android.content.Context;
 import android.content.res.Resources.Theme;
 import android.net.Uri;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
 
 import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.util.BitmapDecodingException;
+import org.thoughtcrime.securesms.util.Util;
 
+import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 
 import ws.com.google.android.mms.ContentType;
 import ws.com.google.android.mms.pdu.PduPart;
 
 public class ImageSlide extends Slide {
   private static final String TAG = ImageSlide.class.getSimpleName();
+  private boolean encrypted = false;
 
   public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) {
     super(context, masterSecret, part);
   }
 
   public ImageSlide(Context context, Uri uri) throws IOException, BitmapDecodingException {
-    super(context, constructPartFromUri(uri));
+    this(context, null, uri);
+  }
+
+  public ImageSlide(Context context, MasterSecret masterSecret, Uri uri) throws IOException, BitmapDecodingException {
+    super(context, masterSecret, constructPartFromByteArrayAndUri(uri, decryptContent(uri, masterSecret), masterSecret != null));
+    encrypted = masterSecret != null;
   }
 
   @Override
@@ -62,12 +73,32 @@ public class ImageSlide extends Slide {
     return true;
   }
 
-  private static PduPart constructPartFromUri(Uri uri)
+  @Override
+  public boolean isEncrypted() {
+    return encrypted;
+  }
+
+  private static byte[] decryptContent(Uri uri, MasterSecret masterSecret) {
+    try {
+      if (masterSecret != null) {
+        InputStream inputStream = new DecryptingPartInputStream(new File(uri.getPath()), masterSecret);
+        return Util.readFully(inputStream);
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+  private static PduPart constructPartFromByteArrayAndUri(Uri uri, @Nullable byte[] data, boolean encrypted)
       throws IOException, BitmapDecodingException
   {
     PduPart part = new PduPart();
 
     part.setDataUri(uri);
+    if (data != null)
+      part.setData(data);
+    part.setEncrypted(encrypted);
     part.setContentType(ContentType.IMAGE_JPEG.getBytes());
     part.setContentId((System.currentTimeMillis()+"").getBytes());
     part.setName(("Image" + System.currentTimeMillis()).getBytes());
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index 6ffcdc3c86..1ff0b3c326 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -5,11 +5,13 @@ import android.content.Context;
 import android.content.UriMatcher;
 import android.net.Uri;
 
+import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.database.PartDatabase;
 import org.thoughtcrime.securesms.providers.PartProvider;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -46,7 +48,11 @@ public class PartAuthority {
         partUri = new PartUriParser(uri);
         return partDatabase.getThumbnailStream(masterSecret, partUri.getPartId());
       default:
-        return context.getContentResolver().openInputStream(uri);
+        String tempMediaDir = context.getDir("media", Context.MODE_PRIVATE).getPath();
+        if (uri.getPath().startsWith(tempMediaDir))
+          return  new DecryptingPartInputStream(new File(uri.getPath()), masterSecret);
+        else
+          return context.getContentResolver().openInputStream(uri);
       }
     } catch (SecurityException se) {
       throw new IOException(se);
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index b4ce8bc981..080bdf1f9c 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -66,6 +66,10 @@ public abstract class Slide {
     return false;
   }
 
+  public boolean isEncrypted() {
+    return false;
+  }
+
   public PduPart getPart() {
     return part;
   }
diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
index 74c9f5f797..484d585fab 100644
--- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java
+++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
@@ -209,7 +209,7 @@ public class BitmapUtil {
     }
   }
 
-  private static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
+  public static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
     Matrix matrix = new Matrix();
     matrix.postRotate(angle);
     Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);