Merge "Swap in the correct home icon."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5e634a4..6beef44 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -92,7 +92,7 @@
         </receiver>
 
         <service
-            android:name=".CopyService"
+            android:name=".services.FileOperationService"
             android:exported="false">
         </service>
     </application>
diff --git a/res/drawable-hdpi/ic_doc_album_alpha.png b/res/drawable-hdpi/ic_doc_album_alpha.png
deleted file mode 100644
index 2b21c12..0000000
--- a/res/drawable-hdpi/ic_doc_album_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_apk_alpha.png b/res/drawable-hdpi/ic_doc_apk_alpha.png
deleted file mode 100644
index ed3ee45..0000000
--- a/res/drawable-hdpi/ic_doc_apk_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_audio_alpha.png b/res/drawable-hdpi/ic_doc_audio_alpha.png
deleted file mode 100644
index 1a3ebc4..0000000
--- a/res/drawable-hdpi/ic_doc_audio_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_certificate_alpha.png b/res/drawable-hdpi/ic_doc_certificate_alpha.png
deleted file mode 100644
index 3086a69..0000000
--- a/res/drawable-hdpi/ic_doc_certificate_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_codes_alpha.png b/res/drawable-hdpi/ic_doc_codes_alpha.png
deleted file mode 100644
index b86f0f7..0000000
--- a/res/drawable-hdpi/ic_doc_codes_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_compressed_alpha.png b/res/drawable-hdpi/ic_doc_compressed_alpha.png
deleted file mode 100644
index 9d0988d..0000000
--- a/res/drawable-hdpi/ic_doc_compressed_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_contact_am_alpha.png b/res/drawable-hdpi/ic_doc_contact_am_alpha.png
deleted file mode 100644
index 6c31360..0000000
--- a/res/drawable-hdpi/ic_doc_contact_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_event_am_alpha.png b/res/drawable-hdpi/ic_doc_event_am_alpha.png
deleted file mode 100644
index 1450ff0..0000000
--- a/res/drawable-hdpi/ic_doc_event_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_excel_alpha.png b/res/drawable-hdpi/ic_doc_excel_alpha.png
deleted file mode 100644
index 4ad54bb..0000000
--- a/res/drawable-hdpi/ic_doc_excel_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_folder_alpha.png b/res/drawable-hdpi/ic_doc_folder_alpha.png
deleted file mode 100644
index 7f7c636..0000000
--- a/res/drawable-hdpi/ic_doc_folder_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_font_alpha.png b/res/drawable-hdpi/ic_doc_font_alpha.png
deleted file mode 100644
index d867847..0000000
--- a/res/drawable-hdpi/ic_doc_font_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_generic_am_alpha.png b/res/drawable-hdpi/ic_doc_generic_am_alpha.png
deleted file mode 100644
index 5459767..0000000
--- a/res/drawable-hdpi/ic_doc_generic_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_image_alpha.png b/res/drawable-hdpi/ic_doc_image_alpha.png
deleted file mode 100644
index 0cbd992..0000000
--- a/res/drawable-hdpi/ic_doc_image_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_pdf_alpha.png b/res/drawable-hdpi/ic_doc_pdf_alpha.png
deleted file mode 100644
index db46702..0000000
--- a/res/drawable-hdpi/ic_doc_pdf_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_powerpoint_alpha.png b/res/drawable-hdpi/ic_doc_powerpoint_alpha.png
deleted file mode 100644
index b9f7af5..0000000
--- a/res/drawable-hdpi/ic_doc_powerpoint_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_presentation_alpha.png b/res/drawable-hdpi/ic_doc_presentation_alpha.png
deleted file mode 100644
index 1218c2f..0000000
--- a/res/drawable-hdpi/ic_doc_presentation_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_spreadsheet_am_alpha.png b/res/drawable-hdpi/ic_doc_spreadsheet_am_alpha.png
deleted file mode 100644
index 46d755f..0000000
--- a/res/drawable-hdpi/ic_doc_spreadsheet_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_text_am_alpha.png b/res/drawable-hdpi/ic_doc_text_am_alpha.png
deleted file mode 100644
index 933570b..0000000
--- a/res/drawable-hdpi/ic_doc_text_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_video_am_alpha.png b/res/drawable-hdpi/ic_doc_video_am_alpha.png
deleted file mode 100644
index 30cea25..0000000
--- a/res/drawable-hdpi/ic_doc_video_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_doc_word_alpha.png b/res/drawable-hdpi/ic_doc_word_alpha.png
deleted file mode 100644
index 67b60de..0000000
--- a/res/drawable-hdpi/ic_doc_word_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_album_alpha.png b/res/drawable-mdpi/ic_doc_album_alpha.png
deleted file mode 100644
index ac27eea..0000000
--- a/res/drawable-mdpi/ic_doc_album_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_apk_alpha.png b/res/drawable-mdpi/ic_doc_apk_alpha.png
deleted file mode 100644
index a4add51..0000000
--- a/res/drawable-mdpi/ic_doc_apk_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_audio_alpha.png b/res/drawable-mdpi/ic_doc_audio_alpha.png
deleted file mode 100644
index a9a7f20..0000000
--- a/res/drawable-mdpi/ic_doc_audio_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_certificate_alpha.png b/res/drawable-mdpi/ic_doc_certificate_alpha.png
deleted file mode 100644
index 26beb79..0000000
--- a/res/drawable-mdpi/ic_doc_certificate_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_codes_alpha.png b/res/drawable-mdpi/ic_doc_codes_alpha.png
deleted file mode 100644
index ed9cab7..0000000
--- a/res/drawable-mdpi/ic_doc_codes_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_compressed_alpha.png b/res/drawable-mdpi/ic_doc_compressed_alpha.png
deleted file mode 100644
index 451d287..0000000
--- a/res/drawable-mdpi/ic_doc_compressed_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_contact_am_alpha.png b/res/drawable-mdpi/ic_doc_contact_am_alpha.png
deleted file mode 100644
index 7bc02c9..0000000
--- a/res/drawable-mdpi/ic_doc_contact_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_event_am_alpha.png b/res/drawable-mdpi/ic_doc_event_am_alpha.png
deleted file mode 100644
index de39cd4..0000000
--- a/res/drawable-mdpi/ic_doc_event_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_excel_alpha.png b/res/drawable-mdpi/ic_doc_excel_alpha.png
deleted file mode 100644
index c3eb845..0000000
--- a/res/drawable-mdpi/ic_doc_excel_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_folder_alpha.png b/res/drawable-mdpi/ic_doc_folder_alpha.png
deleted file mode 100644
index 97f6e50..0000000
--- a/res/drawable-mdpi/ic_doc_folder_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_font_alpha.png b/res/drawable-mdpi/ic_doc_font_alpha.png
deleted file mode 100644
index b2f043f..0000000
--- a/res/drawable-mdpi/ic_doc_font_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_generic_am_alpha.png b/res/drawable-mdpi/ic_doc_generic_am_alpha.png
deleted file mode 100644
index 483d7fb..0000000
--- a/res/drawable-mdpi/ic_doc_generic_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_image_alpha.png b/res/drawable-mdpi/ic_doc_image_alpha.png
deleted file mode 100644
index 2941b8a..0000000
--- a/res/drawable-mdpi/ic_doc_image_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_pdf_alpha.png b/res/drawable-mdpi/ic_doc_pdf_alpha.png
deleted file mode 100644
index d3ac072..0000000
--- a/res/drawable-mdpi/ic_doc_pdf_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_powerpoint_alpha.png b/res/drawable-mdpi/ic_doc_powerpoint_alpha.png
deleted file mode 100644
index a43f902..0000000
--- a/res/drawable-mdpi/ic_doc_powerpoint_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_presentation_alpha.png b/res/drawable-mdpi/ic_doc_presentation_alpha.png
deleted file mode 100644
index c572cd1..0000000
--- a/res/drawable-mdpi/ic_doc_presentation_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_spreadsheet_am_alpha.png b/res/drawable-mdpi/ic_doc_spreadsheet_am_alpha.png
deleted file mode 100644
index 7ae671b..0000000
--- a/res/drawable-mdpi/ic_doc_spreadsheet_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_text_am_alpha.png b/res/drawable-mdpi/ic_doc_text_am_alpha.png
deleted file mode 100644
index a5ebd51..0000000
--- a/res/drawable-mdpi/ic_doc_text_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_video_am_alpha.png b/res/drawable-mdpi/ic_doc_video_am_alpha.png
deleted file mode 100644
index 89ac434..0000000
--- a/res/drawable-mdpi/ic_doc_video_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_doc_word_alpha.png b/res/drawable-mdpi/ic_doc_word_alpha.png
deleted file mode 100644
index 855c44b..0000000
--- a/res/drawable-mdpi/ic_doc_word_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_album_alpha.png b/res/drawable-xhdpi/ic_doc_album_alpha.png
deleted file mode 100644
index 4203d35..0000000
--- a/res/drawable-xhdpi/ic_doc_album_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_apk_alpha.png b/res/drawable-xhdpi/ic_doc_apk_alpha.png
deleted file mode 100644
index 41558f2..0000000
--- a/res/drawable-xhdpi/ic_doc_apk_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_audio_alpha.png b/res/drawable-xhdpi/ic_doc_audio_alpha.png
deleted file mode 100644
index e190c4d..0000000
--- a/res/drawable-xhdpi/ic_doc_audio_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_certificate_alpha.png b/res/drawable-xhdpi/ic_doc_certificate_alpha.png
deleted file mode 100644
index fabc8c3..0000000
--- a/res/drawable-xhdpi/ic_doc_certificate_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_codes_alpha.png b/res/drawable-xhdpi/ic_doc_codes_alpha.png
deleted file mode 100644
index d8ba6e0..0000000
--- a/res/drawable-xhdpi/ic_doc_codes_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_compressed_alpha.png b/res/drawable-xhdpi/ic_doc_compressed_alpha.png
deleted file mode 100644
index 1b5111a..0000000
--- a/res/drawable-xhdpi/ic_doc_compressed_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_contact_am_alpha.png b/res/drawable-xhdpi/ic_doc_contact_am_alpha.png
deleted file mode 100644
index e7b7460..0000000
--- a/res/drawable-xhdpi/ic_doc_contact_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_event_am_alpha.png b/res/drawable-xhdpi/ic_doc_event_am_alpha.png
deleted file mode 100644
index 3183bfc..0000000
--- a/res/drawable-xhdpi/ic_doc_event_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_excel_alpha.png b/res/drawable-xhdpi/ic_doc_excel_alpha.png
deleted file mode 100644
index 9a4e844..0000000
--- a/res/drawable-xhdpi/ic_doc_excel_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_folder_alpha.png b/res/drawable-xhdpi/ic_doc_folder_alpha.png
deleted file mode 100644
index 96d5721..0000000
--- a/res/drawable-xhdpi/ic_doc_folder_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_font_alpha.png b/res/drawable-xhdpi/ic_doc_font_alpha.png
deleted file mode 100644
index 23a1302..0000000
--- a/res/drawable-xhdpi/ic_doc_font_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_generic_am_alpha.png b/res/drawable-xhdpi/ic_doc_generic_am_alpha.png
deleted file mode 100644
index b0811f1..0000000
--- a/res/drawable-xhdpi/ic_doc_generic_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_image_alpha.png b/res/drawable-xhdpi/ic_doc_image_alpha.png
deleted file mode 100644
index 5f1f537..0000000
--- a/res/drawable-xhdpi/ic_doc_image_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_pdf_alpha.png b/res/drawable-xhdpi/ic_doc_pdf_alpha.png
deleted file mode 100644
index 8083584..0000000
--- a/res/drawable-xhdpi/ic_doc_pdf_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_powerpoint_alpha.png b/res/drawable-xhdpi/ic_doc_powerpoint_alpha.png
deleted file mode 100644
index 898e26a..0000000
--- a/res/drawable-xhdpi/ic_doc_powerpoint_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_presentation_alpha.png b/res/drawable-xhdpi/ic_doc_presentation_alpha.png
deleted file mode 100644
index 2e9c667..0000000
--- a/res/drawable-xhdpi/ic_doc_presentation_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_spreadsheet_am_alpha.png b/res/drawable-xhdpi/ic_doc_spreadsheet_am_alpha.png
deleted file mode 100644
index 56951c3..0000000
--- a/res/drawable-xhdpi/ic_doc_spreadsheet_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_text_am_alpha.png b/res/drawable-xhdpi/ic_doc_text_am_alpha.png
deleted file mode 100644
index 08ed9f0..0000000
--- a/res/drawable-xhdpi/ic_doc_text_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_video_am_alpha.png b/res/drawable-xhdpi/ic_doc_video_am_alpha.png
deleted file mode 100644
index cd64e56..0000000
--- a/res/drawable-xhdpi/ic_doc_video_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_doc_word_alpha.png b/res/drawable-xhdpi/ic_doc_word_alpha.png
deleted file mode 100644
index 0bc40de..0000000
--- a/res/drawable-xhdpi/ic_doc_word_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_album_alpha.png b/res/drawable-xxhdpi/ic_doc_album_alpha.png
deleted file mode 100644
index 60f59f5..0000000
--- a/res/drawable-xxhdpi/ic_doc_album_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_apk_alpha.png b/res/drawable-xxhdpi/ic_doc_apk_alpha.png
deleted file mode 100644
index 6006b12..0000000
--- a/res/drawable-xxhdpi/ic_doc_apk_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_audio_alpha.png b/res/drawable-xxhdpi/ic_doc_audio_alpha.png
deleted file mode 100644
index 7926188..0000000
--- a/res/drawable-xxhdpi/ic_doc_audio_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_certificate_alpha.png b/res/drawable-xxhdpi/ic_doc_certificate_alpha.png
deleted file mode 100644
index e65d74c..0000000
--- a/res/drawable-xxhdpi/ic_doc_certificate_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_codes_alpha.png b/res/drawable-xxhdpi/ic_doc_codes_alpha.png
deleted file mode 100644
index 7d32801..0000000
--- a/res/drawable-xxhdpi/ic_doc_codes_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_compressed_alpha.png b/res/drawable-xxhdpi/ic_doc_compressed_alpha.png
deleted file mode 100644
index d528708..0000000
--- a/res/drawable-xxhdpi/ic_doc_compressed_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_contact_am_alpha.png b/res/drawable-xxhdpi/ic_doc_contact_am_alpha.png
deleted file mode 100644
index 274c524..0000000
--- a/res/drawable-xxhdpi/ic_doc_contact_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_event_am_alpha.png b/res/drawable-xxhdpi/ic_doc_event_am_alpha.png
deleted file mode 100644
index 6053b0a..0000000
--- a/res/drawable-xxhdpi/ic_doc_event_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_excel_alpha.png b/res/drawable-xxhdpi/ic_doc_excel_alpha.png
deleted file mode 100644
index 78ee13b..0000000
--- a/res/drawable-xxhdpi/ic_doc_excel_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_folder_alpha.png b/res/drawable-xxhdpi/ic_doc_folder_alpha.png
deleted file mode 100644
index dcf9cc2..0000000
--- a/res/drawable-xxhdpi/ic_doc_folder_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_font_alpha.png b/res/drawable-xxhdpi/ic_doc_font_alpha.png
deleted file mode 100644
index 74fbbbc..0000000
--- a/res/drawable-xxhdpi/ic_doc_font_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_generic_am_alpha.png b/res/drawable-xxhdpi/ic_doc_generic_am_alpha.png
deleted file mode 100644
index 11a219a..0000000
--- a/res/drawable-xxhdpi/ic_doc_generic_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_image_alpha.png b/res/drawable-xxhdpi/ic_doc_image_alpha.png
deleted file mode 100644
index 9a461c9..0000000
--- a/res/drawable-xxhdpi/ic_doc_image_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_pdf_alpha.png b/res/drawable-xxhdpi/ic_doc_pdf_alpha.png
deleted file mode 100644
index 9a67956..0000000
--- a/res/drawable-xxhdpi/ic_doc_pdf_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_powerpoint_alpha.png b/res/drawable-xxhdpi/ic_doc_powerpoint_alpha.png
deleted file mode 100644
index 853aa8c..0000000
--- a/res/drawable-xxhdpi/ic_doc_powerpoint_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_presentation_alpha.png b/res/drawable-xxhdpi/ic_doc_presentation_alpha.png
deleted file mode 100644
index 84ae9ae..0000000
--- a/res/drawable-xxhdpi/ic_doc_presentation_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_spreadsheet_am_alpha.png b/res/drawable-xxhdpi/ic_doc_spreadsheet_am_alpha.png
deleted file mode 100644
index 111439e..0000000
--- a/res/drawable-xxhdpi/ic_doc_spreadsheet_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_text_am_alpha.png b/res/drawable-xxhdpi/ic_doc_text_am_alpha.png
deleted file mode 100644
index b876f86..0000000
--- a/res/drawable-xxhdpi/ic_doc_text_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_video_am_alpha.png b/res/drawable-xxhdpi/ic_doc_video_am_alpha.png
deleted file mode 100644
index 7cf656a..0000000
--- a/res/drawable-xxhdpi/ic_doc_video_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_doc_word_alpha.png b/res/drawable-xxhdpi/ic_doc_word_alpha.png
deleted file mode 100644
index acc3d7b..0000000
--- a/res/drawable-xxhdpi/ic_doc_word_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_album_alpha.png b/res/drawable-xxxhdpi/ic_doc_album_alpha.png
deleted file mode 100644
index d2dd494..0000000
--- a/res/drawable-xxxhdpi/ic_doc_album_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_apk_alpha.png b/res/drawable-xxxhdpi/ic_doc_apk_alpha.png
deleted file mode 100644
index 4f935bf..0000000
--- a/res/drawable-xxxhdpi/ic_doc_apk_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_audio_alpha.png b/res/drawable-xxxhdpi/ic_doc_audio_alpha.png
deleted file mode 100644
index 7499cbc..0000000
--- a/res/drawable-xxxhdpi/ic_doc_audio_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_certificate_alpha.png b/res/drawable-xxxhdpi/ic_doc_certificate_alpha.png
deleted file mode 100644
index ca12928..0000000
--- a/res/drawable-xxxhdpi/ic_doc_certificate_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_codes_alpha.png b/res/drawable-xxxhdpi/ic_doc_codes_alpha.png
deleted file mode 100644
index c4afa37..0000000
--- a/res/drawable-xxxhdpi/ic_doc_codes_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_compressed_alpha.png b/res/drawable-xxxhdpi/ic_doc_compressed_alpha.png
deleted file mode 100644
index 0b0aa04..0000000
--- a/res/drawable-xxxhdpi/ic_doc_compressed_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_contact_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_contact_am_alpha.png
deleted file mode 100644
index ebd0535..0000000
--- a/res/drawable-xxxhdpi/ic_doc_contact_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_event_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_event_am_alpha.png
deleted file mode 100644
index 29cdbb7..0000000
--- a/res/drawable-xxxhdpi/ic_doc_event_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_excel_alpha.png b/res/drawable-xxxhdpi/ic_doc_excel_alpha.png
deleted file mode 100644
index ca349b6..0000000
--- a/res/drawable-xxxhdpi/ic_doc_excel_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_folder_alpha.png b/res/drawable-xxxhdpi/ic_doc_folder_alpha.png
deleted file mode 100644
index 02249d2..0000000
--- a/res/drawable-xxxhdpi/ic_doc_folder_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_font_alpha.png b/res/drawable-xxxhdpi/ic_doc_font_alpha.png
deleted file mode 100644
index 469b911..0000000
--- a/res/drawable-xxxhdpi/ic_doc_font_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_generic_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_generic_am_alpha.png
deleted file mode 100644
index ef479c3..0000000
--- a/res/drawable-xxxhdpi/ic_doc_generic_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_image_alpha.png b/res/drawable-xxxhdpi/ic_doc_image_alpha.png
deleted file mode 100644
index 3168d6f..0000000
--- a/res/drawable-xxxhdpi/ic_doc_image_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_pdf_alpha.png b/res/drawable-xxxhdpi/ic_doc_pdf_alpha.png
deleted file mode 100644
index 9bb4d66..0000000
--- a/res/drawable-xxxhdpi/ic_doc_pdf_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_powerpoint_alpha.png b/res/drawable-xxxhdpi/ic_doc_powerpoint_alpha.png
deleted file mode 100644
index 88ba9ad..0000000
--- a/res/drawable-xxxhdpi/ic_doc_powerpoint_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_presentation_alpha.png b/res/drawable-xxxhdpi/ic_doc_presentation_alpha.png
deleted file mode 100644
index 5fe18da..0000000
--- a/res/drawable-xxxhdpi/ic_doc_presentation_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_spreadsheet_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_spreadsheet_am_alpha.png
deleted file mode 100644
index 1d05f6f..0000000
--- a/res/drawable-xxxhdpi/ic_doc_spreadsheet_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_text_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_text_am_alpha.png
deleted file mode 100644
index c2308fe..0000000
--- a/res/drawable-xxxhdpi/ic_doc_text_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_video_am_alpha.png b/res/drawable-xxxhdpi/ic_doc_video_am_alpha.png
deleted file mode 100644
index 9a173be..0000000
--- a/res/drawable-xxxhdpi/ic_doc_video_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_doc_word_alpha.png b/res/drawable-xxxhdpi/ic_doc_word_alpha.png
deleted file mode 100644
index a564f5a..0000000
--- a/res/drawable-xxxhdpi/ic_doc_word_alpha.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_check_circle.xml b/res/drawable/ic_check_circle.xml
new file mode 100644
index 0000000..d49ba6a
--- /dev/null
+++ b/res/drawable/ic_check_circle.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF009688"
+        android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10,-4.48 10,-10S17.52 2 12 2zm-2 15l-5,-5 1.41,-1.41L10 14.17l7.59,-7.59L19 8l-9 9z"/>
+</vector>
diff --git a/res/drawable/ic_doc_album.xml b/res/drawable/ic_doc_album.xml
index e7965e6..1ce3f02 100644
--- a/res/drawable/ic_doc_album.xml
+++ b/res/drawable/ic_doc_album.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_album_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10,-4.48 10,-10S17.52 2 12 2zm0 14.5c-2.49 0,-4.5,-2.01,-4.5,-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5,-2.01 4.5,-4.5 4.5zm0,-5.5c-.55 0,-1 .45,-1 1s.45 1 1 1 1,-.45 1,-1,-.45,-1,-1,-1z"/>
+</vector>
diff --git a/res/drawable/ic_doc_apk.xml b/res/drawable/ic_doc_apk.xml
index 4f8f06e..197445e 100644
--- a/res/drawable/ic_doc_apk.xml
+++ b/res/drawable/ic_doc_apk.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_apk_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5,-.67 1.5,-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5,-.67 1.5,-1.5V19h1c.55 0 1,-.45 1,-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0,-1.5.67,-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5,-.67 1.5,-1.5v-7c0,-.83,-.67,-1.5,-1.5,-1.5zm-4.97,-5.84l1.3,-1.3c.2,-.2.2,-.51 0,-.71,-.2,-.2,-.51,-.2,-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0,-1.86.23,-2.66.63L7.85.15c-.2,-.2,-.51,-.2,-.71 0,-.2.2,-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0,-1.99,-.97,-3.75,-2.47,-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z"/>
+</vector>
diff --git a/res/drawable/ic_doc_audio.xml b/res/drawable/ic_doc_audio.xml
index cf18b4f..454eea3 100644
--- a/res/drawable/ic_doc_audio.xml
+++ b/res/drawable/ic_doc_audio.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_audio_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFDB4437"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zM7.2 18c-.66 0,-1.2,-.54,-1.2,-1.2V12c0,-3.31 2.69,-6 6,-6s6 2.69 6 6v4.8c0 .66,-.54 1.2,-1.2 1.2H14v-4h2v-2c0,-2.21,-1.79,-4,-4,-4s-4 1.79,-4 4v2h2v4H7.2z"/>
+</vector>
diff --git a/res/drawable/ic_doc_certificate.xml b/res/drawable/ic_doc_certificate.xml
index c28ed4f..b99baf6 100644
--- a/res/drawable/ic_doc_certificate.xml
+++ b/res/drawable/ic_doc_certificate.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_certificate_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M17.81 4.47c-.08 0,-.16,-.02,-.23,-.06C15.66 3.42 14 3 12.01 3c-1.98 0,-3.86.47,-5.57 1.41,-.24.13,-.54.04,-.68,-.2,-.13,-.24,-.04,-.55.2,-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67,-.09.18,-.26.28,-.44.28zM3.5 9.72c-.1 0,-.2,-.03,-.29,-.09,-.23,-.16,-.28,-.47,-.12,-.7.99,-1.4 2.25,-2.5 3.75,-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54,-.12.7,-.23.16,-.54.11,-.7,-.12,-.9,-1.26,-2.04,-2.25,-3.39,-2.94,-2.87,-1.47,-6.54,-1.47,-9.4.01,-1.36.7,-2.5 1.7,-3.4 2.96,-.08.14,-.23.21,-.39.21zm6.25 12.07c-.13 0,-.26,-.05,-.35,-.15,-.87,-.87,-1.34,-1.43,-2.01,-2.64,-.69,-1.23,-1.05,-2.73,-1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66 2.42 5.66 5.39c0 .28,-.22.5,-.5.5s-.5,-.22,-.5,-.5c0,-2.42,-2.09,-4.39,-4.66,-4.39,-2.57 0,-4.66 1.97,-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71,-.11.1,-.24.15,-.37.15zm7.17,-1.85c-1.19 0,-2.24,-.3,-3.1,-.89,-1.49,-1.01,-2.38,-2.65,-2.38,-4.39 0,-.28.22,-.5.5,-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64,-.03 1.04,-.1.27,-.05.53.13.58.41.05.27,-.13.53,-.41.58,-.57.11,-1.07.12,-1.21.12zM14.91 22c-.04 0,-.09,-.01,-.13,-.02,-1.59,-.44,-2.63,-1.03,-3.72,-2.1,-1.4,-1.39,-2.17,-3.24,-2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08,-.87 2.08,-1.94c0,-3.77,-3.25,-6.83,-7.25,-6.83,-2.84 0,-5.44 1.58,-6.61 4.03,-.39.81,-.59 1.76,-.59 2.8 0 .78.07 2.01.67 3.61.1.26,-.03.55,-.29.64,-.26.1,-.55,-.04,-.64,-.29,-.49,-1.31,-.73,-2.61,-.73,-3.96 0,-1.2.23,-2.29.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62,-1.38 2.94,-3.08 2.94s-3.08,-1.32,-3.08,-2.94c0,-1.07,-.93,-1.94,-2.08,-1.94s-2.08.87,-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61,-.05.23,-.26.38,-.47.38z"/>
+</vector>
diff --git a/res/drawable/ic_doc_codes.xml b/res/drawable/ic_doc_codes.xml
index 0de721a..ea1c464 100644
--- a/res/drawable/ic_doc_codes.xml
+++ b/res/drawable/ic_doc_codes.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_codes_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M9.4 16.6L4.8 12l4.6,-4.6L8 6l-6 6 6 6 1.4,-1.4zm5.2 0l4.6,-4.6,-4.6,-4.6L16 6l6 6,-6 6,-1.4,-1.4z"/>
+</vector>
diff --git a/res/drawable/ic_doc_compressed.xml b/res/drawable/ic_doc_compressed.xml
index a49cfa4..e0eb669 100644
--- a/res/drawable/ic_doc_compressed.xml
+++ b/res/drawable/ic_doc_compressed.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_compressed_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-5 6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2v2zm0 8h-2v-2h-2v-2h2v2h2v2z"/>
+</vector>
diff --git a/res/drawable/ic_doc_contact.xml b/res/drawable/ic_doc_contact.xml
index bf550cb..a77cb6b 100644
--- a/res/drawable/ic_doc_contact.xml
+++ b/res/drawable/ic_doc_contact.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_contact_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M19 3H5c-1.11 0,-2 .89,-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.11,-.9,-2,-2,-2zm-7 3c1.65 0 3 1.35 3 3 0 1.66,-1.35 3,-3 3s-3,-1.34,-3,-3c0,-1.65 1.35,-3 3,-3zm6 12H6v-1c0,-2 4,-3.1 6,-3.1s6 1.1 6 3.1v1z"/>
+</vector>
diff --git a/res/drawable/ic_doc_document.xml b/res/drawable/ic_doc_document.xml
new file mode 100644
index 0000000..29251ad
--- /dev/null
+++ b/res/drawable/ic_doc_document.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF4883F3"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-1.99 6H7V7h10.01v2zm0 4H7v-2h10.01v2zm-3 4H7v-2h7.01v2z"/>
+</vector>
diff --git a/res/drawable/ic_doc_event.xml b/res/drawable/ic_doc_event.xml
index 25cf0f3..113f079 100644
--- a/res/drawable/ic_doc_event.xml
+++ b/res/drawable/ic_doc_event.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_event_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0,-1.99.9,-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2h-1V1h-2zm3 18H5V8h14v11z"/>
+</vector>
diff --git a/res/drawable/ic_doc_excel.xml b/res/drawable/ic_doc_excel.xml
index 3354725..3ed27e1 100644
--- a/res/drawable/ic_doc_excel.xml
+++ b/res/drawable/ic_doc_excel.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_excel_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF16A765"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-2.8 14h-2L12 13.2 9.8 17h-2l3.2,-5,-3.2,-5h2l2.2 3.8L14.2 7h2L13 12l3.2 5z"/>
+</vector>
diff --git a/res/drawable/ic_doc_folder.xml b/res/drawable/ic_doc_folder.xml
index 73de60e..dcbce01 100644
--- a/res/drawable/ic_doc_folder.xml
+++ b/res/drawable/ic_doc_folder.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_folder_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M10 4H4c-1.1 0,-1.99.9,-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2,-.9 2,-2V8c0,-1.1,-.9,-2,-2,-2h-8l-2,-2z"/>
+</vector>
diff --git a/res/drawable/ic_doc_font.xml b/res/drawable/ic_doc_font.xml
index c74cb9c..4c13d71 100644
--- a/res/drawable/ic_doc_font.xml
+++ b/res/drawable/ic_doc_font.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_font_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M9.93 13.5h4.14L12 7.98zM20 2H4c-1.1 0,-2 .9,-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2,-.9 2,-2V4c0,-1.1,-.9,-2,-2,-2zm-4.05 16.5l-1.14,-3H9.17l-1.12 3H5.96l5.11,-13h1.86l5.11 13h-2.09z"/>
+</vector>
diff --git a/res/drawable/ic_doc_generic.xml b/res/drawable/ic_doc_generic.xml
index a4ee29d..006dfba 100644
--- a/res/drawable/ic_doc_generic.xml
+++ b/res/drawable/ic_doc_generic.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_generic_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M6 2c-1.1 0,-1.99.9,-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2,-.9 2,-2V8l-6,-6H6zm7 7V3.5L18.5 9H13z"/>
+</vector>
diff --git a/res/drawable/ic_doc_image.xml b/res/drawable/ic_doc_image.xml
index 9d4c359..23953f7 100644
--- a/res/drawable/ic_doc_image.xml
+++ b/res/drawable/ic_doc_image.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_image_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFDB4437"
+        android:pathData="M21 19V5c0,-1.1,-.9,-2,-2,-2H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5,-4.5z"/>
+</vector>
diff --git a/res/drawable/ic_doc_pdf.xml b/res/drawable/ic_doc_pdf.xml
index 5c37498..b2d0193 100644
--- a/res/drawable/ic_doc_pdf.xml
+++ b/res/drawable/ic_doc_pdf.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_pdf_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFDB4437"
+        android:pathData="M7 11.5h1v-1H7v1zM19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-9.5 8.5c0 .83,-.67 1.5,-1.5 1.5H7v2H5.5V9H8c.83 0 1.5.67 1.5 1.5v1zm10,-1H17v1h1.5V13H17v2h-1.5V9h4v1.5zm-5 3c0 .83,-.67 1.5,-1.5 1.5h-2.5V9H13c.83 0 1.5.67 1.5 1.5v3zm-2.5 0h1v-3h-1v3z"/>
+</vector>
diff --git a/res/drawable/ic_doc_powerpoint.xml b/res/drawable/ic_doc_powerpoint.xml
index f0a6c39..aa5bfc8 100644
--- a/res/drawable/ic_doc_powerpoint.xml
+++ b/res/drawable/ic_doc_powerpoint.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_powerpoint_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFF7537"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zM9.8 13.4V17H8V7h4.3c1.53 0 2.15.3 2.8.89.65.59.9 1.37.9 2.34 0 1.02,-.26 1.8,-.9 2.35s-1.3.82,-2.8.82H9.8zm0,-1.4V8.4h2.3c.66 0 1.17.25 1.5.6.33.35.5.72.5 1.25 0 .55,-.18.95,-.5 1.25,-.32.31,-.7.5,-1.38.5H9.8z"/>
+</vector>
diff --git a/res/drawable/ic_doc_presentation.xml b/res/drawable/ic_doc_presentation.xml
index a14f866..7937bc1 100644
--- a/res/drawable/ic_doc_presentation.xml
+++ b/res/drawable/ic_doc_presentation.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_presentation_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFF4B400"
+        android:pathData="M19 3H5c-1.1 0,-1.99.9,-1.99 2v14c0 1.1.89 2 1.99 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm0 13H5V8h14v8z"/>
+</vector>
diff --git a/res/drawable/ic_doc_spreadsheet.xml b/res/drawable/ic_doc_spreadsheet.xml
index 40f2515..1663c0a 100644
--- a/res/drawable/ic_doc_spreadsheet.xml
+++ b/res/drawable/ic_doc_spreadsheet.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_spreadsheet_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF16A765"
+        android:pathData="M19 3H5c-1.1 0,-1.99.9,-1.99 2L3 8v11c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm0 8h-8v8H9v-8H5V9h4V5h2v4h8v2z"/>
+</vector>
diff --git a/res/drawable/ic_doc_text.xml b/res/drawable/ic_doc_text.xml
index ffa9e70..7fc04e8 100644
--- a/res/drawable/ic_doc_text.xml
+++ b/res/drawable/ic_doc_text.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_text_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF737373"
+        android:pathData="M14 2H6c-1.1 0,-1.99.9,-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2,-.9 2,-2V8l-6,-6zm2 16H8v-2h8v2zm0,-4H8v-2h8v2zm-3,-5V3.5L18.5 9H13z"/>
+</vector>
diff --git a/res/drawable/ic_doc_video.xml b/res/drawable/ic_doc_video.xml
index 2d048c2..ad4dae8 100644
--- a/res/drawable/ic_doc_video.xml
+++ b/res/drawable/ic_doc_video.xml
@@ -1,5 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_video_am_alpha"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFDB4437"
+        android:pathData="M18 4l2 4h-3l-2,-4h-2l2 4h-3l-2,-4H8l2 4H7L5 4H4c-1.1 0,-1.99.9,-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2,-.9 2,-2V4h-4z"/>
+</vector>
diff --git a/res/drawable/ic_doc_word.xml b/res/drawable/ic_doc_word.xml
index afcc533..7a3a0ec 100644
--- a/res/drawable/ic_doc_word.xml
+++ b/res/drawable/ic_doc_word.xml
@@ -1,4 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_doc_word_alpha"
-    android:tint="?android:attr/colorControlNormal" />
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF4883F3"
+        android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-3.5 14H14l-2,-7.5,-2 7.5H8.5L6.1 7h1.7l1.54 7.51L11.3 7h1.4l1.97 7.51L16.2 7h1.7l-2.4 10z"/>
+</vector>
diff --git a/res/layout-sw720dp-land/item_doc_list.xml b/res/layout-sw720dp-land/item_doc_list.xml
index fe06eaf..ecc26e1 100644
--- a/res/layout-sw720dp-land/item_doc_list.xml
+++ b/res/layout-sw720dp-land/item_doc_list.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+     Copyright (C) 2013 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.
@@ -18,8 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@color/item_doc_background"
-    android:orientation="horizontal"
-    android:focusable="true">
+    android:focusable="true"
+    android:orientation="horizontal" >
 
     <View
         android:id="@+id/focus_indicator"
@@ -29,71 +30,76 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/list_item_height"
-        android:paddingStart="@dimen/list_item_padding"
-        android:paddingEnd="@dimen/list_item_padding"
+        android:baselineAligned="false"
         android:gravity="center_vertical"
+        android:minHeight="@dimen/list_item_height"
         android:orientation="horizontal"
-        android:baselineAligned="false">
+        android:paddingEnd="@dimen/list_item_padding"
+        android:paddingStart="@dimen/list_item_padding" >
 
         <FrameLayout
             android:id="@android:id/icon"
-            android:layout_width="@dimen/icon_size"
-            android:layout_height="@dimen/icon_size"
-            android:layout_marginStart="0dp"
-            android:layout_marginEnd="16dp">
+            android:layout_width="@dimen/list_item_thumbnail_size"
+            android:layout_height="@dimen/list_item_thumbnail_size"
+            android:layout_marginEnd="16dp"
+            android:layout_marginStart="0dp" >
 
             <ImageView
                 android:id="@+id/icon_mime"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:scaleType="centerInside" />
 
             <ImageView
                 android:id="@+id/icon_thumb"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:contentDescription="@null" />
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:scaleType="centerCrop" />
+
+            <ImageView
+                android:id="@+id/icon_check"
+                android:layout_width="@dimen/check_icon_size"
+                android:layout_height="@dimen/check_icon_size"
+                android:layout_gravity="center"
+                android:alpha="0"
+                android:contentDescription="@null"
+                android:scaleType="fitCenter"
+                android:src="@drawable/ic_check_circle" />
 
         </FrameLayout>
 
         <!-- This is the one special case where we want baseline alignment! -->
+
         <LinearLayout
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:orientation="horizontal">
+            android:orientation="horizontal" >
 
             <TextView
                 android:id="@android:id/title"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_weight="0.5"
                 android:layout_marginEnd="12dp"
-                android:singleLine="true"
+                android:layout_weight="0.5"
                 android:ellipsize="middle"
+                android:singleLine="true"
                 android:textAlignment="viewStart"
                 android:textAppearance="@android:style/TextAppearance.Material.Subhead"
                 android:textColor="?android:attr/textColorPrimary" />
 
-            <ImageView
-                android:id="@android:id/icon1"
-                android:layout_width="@dimen/root_icon_size"
-                android:layout_height="@dimen/root_icon_size"
-                android:layout_marginEnd="8dp"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
-
             <TextView
                 android:id="@android:id/summary"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_weight="0.25"
                 android:layout_marginEnd="12dp"
-                android:singleLine="true"
+                android:layout_weight="0.25"
                 android:ellipsize="end"
+                android:singleLine="true"
                 android:textAlignment="viewStart"
                 android:textAppearance="@android:style/TextAppearance.Material.Body1"
                 android:textColor="?android:attr/textColorSecondary" />
@@ -102,11 +108,11 @@
                 android:id="@+id/size"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_weight="0.125"
                 android:layout_marginEnd="12dp"
+                android:layout_weight="0.125"
+                android:ellipsize="end"
                 android:minWidth="70dp"
                 android:singleLine="true"
-                android:ellipsize="end"
                 android:textAlignment="viewEnd"
                 android:textAppearance="@android:style/TextAppearance.Material.Body1"
                 android:textColor="?android:attr/textColorSecondary" />
@@ -115,17 +121,15 @@
                 android:id="@+id/date"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_weight="0.125"
                 android:layout_marginEnd="12dp"
+                android:layout_weight="0.125"
+                android:ellipsize="end"
                 android:minWidth="70dp"
                 android:singleLine="true"
-                android:ellipsize="end"
                 android:textAlignment="viewEnd"
                 android:textAppearance="@android:style/TextAppearance.Material.Body1"
                 android:textColor="?android:attr/textColorSecondary" />
-
         </LinearLayout>
-
     </LinearLayout>
 
 </com.android.documentsui.ListItem>
diff --git a/res/layout/fragment_directory.xml b/res/layout/fragment_directory.xml
index f9bbccb..1b5911d 100644
--- a/res/layout/fragment_directory.xml
+++ b/res/layout/fragment_directory.xml
@@ -23,11 +23,11 @@
     <ProgressBar
         android:id="@+id/progressbar"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/progress_bar_height"
         android:indeterminate="true"
         style="@style/TrimmedHorizontalProgressBar"
         android:visibility="gone"/>
-  
+
     <FrameLayout
         android:id="@+id/container_message_bar"
         android:layout_width="match_parent"
@@ -44,7 +44,7 @@
         android:layout_height="match_parent"
         android:orientation="vertical"
         android:visibility="gone">
-        
+
         <TextView
             android:id="@+id/message"
             android:layout_width="wrap_content"
@@ -58,9 +58,9 @@
             android:layout_height="wrap_content"
             android:text="@string/button_retry"
             style="?android:attr/buttonBarPositiveButtonStyle" />
-        
+
     </LinearLayout>
-    
+
     <!-- This FrameLayout works around b/24189541 -->
     <FrameLayout
         android:layout_width="match_parent"
@@ -68,6 +68,7 @@
 
         <android.support.v7.widget.RecyclerView
             android:id="@+id/list"
+            android:background="@color/window_background"
             android:scrollbars="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/res/layout/item_dir_grid.xml b/res/layout/item_dir_grid.xml
index c17b4c8..a08e029 100644
--- a/res/layout/item_dir_grid.xml
+++ b/res/layout/item_dir_grid.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+     Copyright (C) 2013 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.
@@ -20,31 +21,49 @@
     android:layout_margin="@dimen/grid_item_margin"
     android:background="@color/item_doc_background"
     android:elevation="5dp"
-    android:focusable="true">
+    android:focusable="true" >
 
     <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      android:orientation="horizontal"
-      android:paddingTop="16dp"
-      android:paddingBottom="16dp"
-      android:paddingLeft="12dp"
-      android:paddingRight="12dp">
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:paddingBottom="16dp"
+        android:paddingLeft="12dp"
+        android:paddingRight="12dp"
+        android:paddingTop="16dp"
+        android:gravity="center_vertical">
 
-        <ImageView
-            android:src="@drawable/ic_doc_folder"
+        <FrameLayout
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginEnd="8dp"
-            android:scaleType="centerInside"
-            android:contentDescription="@null"/>
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp" >
+
+            <ImageView
+                android:id="@+id/icon_mime_sm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:scaleType="centerInside"
+                android:src="@drawable/ic_doc_folder" />
+
+            <ImageView
+                android:id="@+id/icon_check"
+                android:layout_width="@dimen/check_icon_size"
+                android:layout_height="@dimen/check_icon_size"
+                android:alpha="0"
+                android:contentDescription="@null"
+                android:scaleType="fitCenter"
+                android:src="@drawable/ic_check_circle" />
+
+        </FrameLayout>
 
         <TextView
             android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:singleLine="true"
             android:ellipsize="middle"
+            android:singleLine="true"
             android:textAlignment="viewStart"
             android:textAppearance="@android:style/TextAppearance.Material.Subhead"
             android:textColor="@*android:color/primary_text_default_material_light" />
@@ -52,11 +71,12 @@
     </LinearLayout>
 
     <!-- An overlay that draws the item border when it is focused. -->
+
     <View
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:contentDescription="@null"
         android:background="@drawable/item_doc_grid_border"
+        android:contentDescription="@null"
         android:duplicateParentState="true" />
 
 </FrameLayout>
diff --git a/res/layout/item_doc_grid.xml b/res/layout/item_doc_grid.xml
index c0fc2c3..dd02d1c 100644
--- a/res/layout/item_doc_grid.xml
+++ b/res/layout/item_doc_grid.xml
@@ -39,10 +39,11 @@
             android:contentDescription="@null" />
 
         <com.android.documentsui.GridItemThumbnail
-            android:id="@+id/icon_mime"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scaleType="centerInside"
+            android:id="@+id/icon_mime_lg"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
             android:contentDescription="@null" />
 
     </FrameLayout>
@@ -61,13 +62,25 @@
         android:paddingRight="12dp">
 
         <ImageView
-            android:id="@android:id/icon1"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:id="@+id/icon_mime_sm"
+            android:layout_width="@dimen/grid_item_icon_size"
+            android:layout_height="@dimen/grid_item_icon_size"
             android:layout_marginEnd="8dp"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
-            android:scaleType="centerInside"
+            android:scaleType="center"
+            android:contentDescription="@null"/>
+
+        <ImageView
+            android:id="@+id/icon_check"
+            android:src="@drawable/ic_check_circle"
+            android:alpha="0"
+            android:layout_width="@dimen/check_icon_size"
+            android:layout_height="@dimen/check_icon_size"
+            android:layout_marginEnd="8dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:scaleType="fitCenter"
             android:contentDescription="@null"/>
 
         <TextView
@@ -75,7 +88,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentTop="true"
-            android:layout_toEndOf="@android:id/icon1"
+            android:layout_toEndOf="@id/icon_mime_sm"
             android:singleLine="true"
             android:ellipsize="middle"
             android:textAlignment="viewStart"
@@ -86,7 +99,7 @@
             android:id="@+id/size"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_toEndOf="@android:id/icon1"
+            android:layout_toEndOf="@id/icon_mime_sm"
             android:layout_below="@android:id/title"
             android:layout_marginEnd="4dp"
             android:singleLine="true"
diff --git a/res/layout/item_doc_list.xml b/res/layout/item_doc_list.xml
index e068423..8d98377 100644
--- a/res/layout/item_doc_list.xml
+++ b/res/layout/item_doc_list.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+     Copyright (C) 2013 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.
@@ -18,8 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@color/item_doc_background"
-    android:orientation="horizontal"
-    android:focusable="true">
+    android:focusable="true"
+    android:orientation="horizontal" >
 
     <View
         android:id="@+id/focus_indicator"
@@ -29,82 +30,76 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/list_item_height"
-        android:paddingStart="@dimen/list_item_padding"
-        android:paddingEnd="@dimen/list_item_padding"
+        android:baselineAligned="false"
         android:gravity="center_vertical"
+        android:minHeight="@dimen/list_item_height"
         android:orientation="horizontal"
-        android:baselineAligned="false">
+        android:paddingEnd="@dimen/list_item_padding"
+        android:paddingStart="@dimen/list_item_padding" >
 
         <FrameLayout
             android:id="@android:id/icon"
-            android:layout_width="@dimen/icon_size"
-            android:layout_height="@dimen/icon_size"
-            android:layout_marginEnd="16dp">
+            android:layout_width="@dimen/list_item_thumbnail_size"
+            android:layout_height="@dimen/list_item_thumbnail_size"
+            android:layout_marginEnd="16dp" >
 
             <ImageView
                 android:id="@+id/icon_mime"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:scaleType="centerInside" />
 
             <ImageView
                 android:id="@+id/icon_thumb"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:contentDescription="@null" />
+                android:contentDescription="@null"
+                android:scaleType="centerCrop" />
 
+            <ImageView
+                android:id="@+id/icon_check"
+                android:layout_width="@dimen/check_icon_size"
+                android:layout_height="@dimen/check_icon_size"
+                android:layout_gravity="center"
+                android:alpha="0"
+                android:contentDescription="@null"
+                android:scaleType="fitCenter"
+                android:src="@drawable/ic_check_circle" />
         </FrameLayout>
 
         <LinearLayout
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:orientation="vertical">
+            android:orientation="vertical" >
 
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:baselineAligned="false">
-
-                <TextView
-                    android:id="@android:id/title"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:singleLine="true"
-                    android:ellipsize="middle"
-                    android:textAlignment="viewStart"
-                    android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-                    android:textColor="?android:attr/textColorPrimary" />
-
-                <ImageView
-                    android:id="@android:id/icon1"
-                    android:layout_width="@dimen/root_icon_size"
-                    android:layout_height="@dimen/root_icon_size"
-                    android:layout_marginStart="8dp"
-                    android:scaleType="centerInside"
-                    android:contentDescription="@null" />
-
-            </LinearLayout>
+            <TextView
+                android:id="@android:id/title"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:ellipsize="middle"
+                android:singleLine="true"
+                android:textAlignment="viewStart"
+                android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+                android:textColor="?android:attr/textColorPrimary" />
 
             <LinearLayout
                 android:id="@+id/line2"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:baselineAligned="false"
                 android:gravity="center_vertical"
-                android:orientation="horizontal"
-                android:baselineAligned="false">
+                android:orientation="horizontal" >
 
                 <TextView
                     android:id="@+id/date"
                     android:layout_width="90dp"
                     android:layout_height="wrap_content"
-                    android:singleLine="true"
                     android:ellipsize="end"
+                    android:singleLine="true"
                     android:textAlignment="viewStart"
                     android:textAppearance="@android:style/TextAppearance.Material.Body1"
                     android:textColor="?android:attr/textColorSecondary" />
@@ -114,8 +109,8 @@
                     android:layout_width="90dp"
                     android:layout_height="wrap_content"
                     android:layout_marginStart="8dp"
-                    android:singleLine="true"
                     android:ellipsize="end"
+                    android:singleLine="true"
                     android:textAlignment="viewStart"
                     android:textAppearance="@android:style/TextAppearance.Material.Body1"
                     android:textColor="?android:attr/textColorSecondary" />
@@ -124,18 +119,15 @@
                     android:id="@android:id/summary"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:layout_weight="1"
                     android:layout_marginStart="8dp"
-                    android:singleLine="true"
+                    android:layout_weight="1"
                     android:ellipsize="end"
+                    android:singleLine="true"
                     android:textAlignment="viewStart"
                     android:textAppearance="@android:style/TextAppearance.Material.Body1"
                     android:textColor="?android:attr/textColorSecondary" />
-
             </LinearLayout>
-
         </LinearLayout>
-
     </LinearLayout>
 
 </com.android.documentsui.ListItem>
diff --git a/res/menu/activity.xml b/res/menu/activity.xml
index 7e0649b..a3cfde8 100644
--- a/res/menu/activity.xml
+++ b/res/menu/activity.xml
@@ -15,11 +15,19 @@
 -->
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- showAsAction flag impacts the behavior of SearchView.
+     When set to collapseActionView, collapsing SearchView to icon is the
+     default behavior. It would fit UX, however after expanding SearchView is
+     shown on the left site of the toolbar (replacing title). Since no way to
+     prevent this behavior was found, the flag is set to always. SearchView is
+     always visible by default and it is being collapse manually by calling
+     setIconified() method
+-->
     <item
         android:id="@+id/menu_search"
         android:title="@string/menu_search"
         android:icon="@drawable/ic_menu_search"
-        android:showAsAction="always|collapseActionView"
+        android:showAsAction="always"
         android:actionViewClass="android.widget.SearchView"
         android:imeOptions="actionSearch" />
     <item
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 153c673..c868d34 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -33,4 +33,6 @@
     <color name="item_doc_background">#fffafafa</color>
     <color name="item_doc_background_selected">#ffe0f2f1</color>
 
+    <color name="menu_search_background">#ff676f74</color>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 060871d..5adb165 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -15,9 +15,18 @@
 -->
 
 <resources>
+    <dimen name="grid_container_padding">10dp</dimen>
+    <dimen name="list_container_padding">0dp</dimen>
+
     <dimen name="icon_size">40dp</dimen>
     <dimen name="root_icon_size">24dp</dimen>
     <dimen name="root_icon_margin">0dp</dimen>
+    <dimen name="check_icon_size">30dp</dimen>
+
+    <dimen name="list_item_thumbnail_size">40dp</dimen>
+    <dimen name="grid_item_icon_size">30dp</dimen>
+
+    <dimen name="progress_bar_height">4dp</dimen>
 
     <dimen name="grid_width">152dp</dimen>
     <dimen name="grid_height">176dp</dimen>
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 9c0a04c..be54496 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -38,13 +38,14 @@
 import android.provider.DocumentsContract.Root;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
+import android.text.TextUtils;
 import android.util.Log;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.MenuItem.OnActionExpandListener;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemSelectedListener;
@@ -60,6 +61,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.internal.util.Preconditions;
 
 import libcore.io.IoUtils;
 
@@ -111,6 +113,13 @@
         setContentView(mLayoutId);
 
         mRoots = DocumentsApplication.getRootsCache(this);
+        mRoots.setOnCacheUpdateListener(
+                new RootsCache.OnCacheUpdateListener() {
+                    @Override
+                    public void onCacheUpdate() {
+                        new HandleRootsChangedTask().execute(getCurrentRoot());
+                    }
+                });
         mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
         mSearchManager = new SearchManager();
 
@@ -167,6 +176,12 @@
         return true;
     }
 
+    @Override
+    protected void onDestroy() {
+        mRoots.setOnCacheUpdateListener(null);
+        super.onDestroy();
+    }
+
     State buildDefaultState() {
         State state = new State();
 
@@ -191,10 +206,7 @@
 
     void onRootPicked(RootInfo root) {
         // Clear entire backstack and start in new root
-        mState.stack.root = root;
-        mState.stack.clear();
-        mState.stackTouched = true;
-
+        mState.onRootChanged(root);
         mSearchManager.update(root);
 
         // Recents is always in memory, so we just load it directly.
@@ -203,7 +215,7 @@
         if (mRoots.isRecentsRoot(root)) {
             onCurrentDirectoryChanged(ANIM_SIDE);
         } else {
-            new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory());
+            new PickRootTask(root, true).executeOnExecutor(getExecutorForCurrentDirectory());
         }
     }
 
@@ -214,6 +226,7 @@
                 case R.id.menu_advanced:
                 case R.id.menu_file_size:
                 case R.id.menu_new_window:
+                case R.id.menu_search:
                     break;
                 default:
                     item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
@@ -305,8 +318,7 @@
 
     void openContainerDocument(DocumentInfo doc) {
         checkArgument(doc.isContainer());
-        mState.stack.push(doc);
-        mState.stackTouched = true;
+        mState.pushDocument(doc);
         onCurrentDirectoryChanged(ANIM_DOWN);
     }
 
@@ -315,6 +327,8 @@
      * the (abstract) directoryChanged method will be called.
      * @param anim
      */
+    // TODO: Refactor the usage of the method - now it is called not only when the directory
+    // changed, but also to refresh the content of the directory while searching
     final void onCurrentDirectoryChanged(int anim) {
         mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
         onDirectoryChanged(anim);
@@ -325,7 +339,11 @@
         }
 
         updateActionBar();
-        invalidateOptionsMenu();
+
+        // Prevents searchView from being recreated while searching
+        if (!mSearchManager.isSearching()) {
+            invalidateOptionsMenu();
+        }
     }
 
     final List<String> getExcludedAuthorities() {
@@ -450,7 +468,7 @@
             return;
         }
 
-        if (!mState.stackTouched) {
+        if (!mState.hasLocationChanged()) {
             super.onBackPressed();
             return;
         }
@@ -471,9 +489,7 @@
         try {
             // Update the restored stack to ensure we have freshest data
             stack.updateDocuments(getContentResolver());
-
-            mState.stack = stack;
-            mState.stackTouched = true;
+            mState.setStack(stack);
             onCurrentDirectoryChanged(ANIM_SIDE);
 
         } catch (FileNotFoundException e) {
@@ -481,31 +497,35 @@
         }
     }
 
+    private DocumentInfo getRootDocumentBlocking(RootInfo root) {
+        try {
+            final Uri uri = DocumentsContract.buildDocumentUri(
+                    root.authority, root.documentId);
+            return DocumentInfo.fromUri(getContentResolver(), uri);
+        } catch (FileNotFoundException e) {
+            Log.w(mTag, "Failed to find root", e);
+            return null;
+        }
+    }
+
     final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
         private RootInfo mRoot;
+        private boolean mTouched;
 
-        public PickRootTask(RootInfo root) {
+        public PickRootTask(RootInfo root, boolean touched) {
             mRoot = root;
+            mTouched = touched;
         }
 
         @Override
         protected DocumentInfo doInBackground(Void... params) {
-            try {
-                final Uri uri = DocumentsContract.buildDocumentUri(
-                        mRoot.authority, mRoot.documentId);
-                return DocumentInfo.fromUri(getContentResolver(), uri);
-            } catch (FileNotFoundException e) {
-                Log.w(mTag, "Failed to find root", e);
-                return null;
-            }
+            return getRootDocumentBlocking(mRoot);
         }
 
         @Override
         protected void onPostExecute(DocumentInfo result) {
-            if (result != null) {
-                mState.stack.push(result);
-                mState.stackTouched = true;
-                onCurrentDirectoryChanged(ANIM_SIDE);
+            if (result != null && !isDestroyed()) {
+                openContainerDocument(result);
             }
         }
     }
@@ -591,6 +611,40 @@
         }
     }
 
+    final class HandleRootsChangedTask extends AsyncTask<RootInfo, Void, RootInfo> {
+        DocumentInfo mHome;
+
+        @Override
+        protected RootInfo doInBackground(RootInfo... roots) {
+            checkArgument(roots.length == 1);
+            final RootInfo currentRoot = roots[0];
+            final Collection<RootInfo> cachedRoots = mRoots.getRootsBlocking();
+            RootInfo homeRoot = null;
+            for (final RootInfo root : cachedRoots) {
+                if (root.isHome()) {
+                    homeRoot = root;
+                }
+                if (root.getUri().equals(currentRoot.getUri())) {
+                    // We don't need to change the current root as the current root was not removed.
+                    return null;
+                }
+            }
+            Preconditions.checkNotNull(homeRoot);
+            mHome = getRootDocumentBlocking(homeRoot);
+            return homeRoot;
+        }
+
+        @Override
+        protected void onPostExecute(RootInfo homeRoot) {
+            if (homeRoot != null && mHome != null && !isDestroyed()) {
+                // Clear entire backstack and start in new root
+                mState.onRootChanged(homeRoot);
+                mSearchManager.update(homeRoot);
+                openContainerDocument(mHome);
+            }
+        }
+    }
+
     final class ItemSelectedListener implements OnItemSelectedListener {
 
         boolean mIgnoreNextNavigation;
@@ -603,8 +657,7 @@
             }
 
             while (mState.stack.size() > position + 1) {
-                mState.stackTouched = true;
-                mState.stack.pop();
+                mState.popDocument();
             }
             onCurrentDirectoryChanged(ANIM_UP);
         }
@@ -682,7 +735,7 @@
      * Facade over the various search parts in the menu.
      */
     final class SearchManager implements
-            SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
+            SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
             DocumentsToolBar.OnActionViewCollapsedListener {
 
         private boolean mSearchExpanded;
@@ -700,9 +753,10 @@
             mView = (SearchView) mMenu.getActionView();
 
             mActionBar.setOnActionViewCollapsedListener(this);
-            mMenu.setOnActionExpandListener(this);
             mView.setOnQueryTextListener(this);
             mView.setOnCloseListener(this);
+            mView.setOnSearchClickListener(this);
+            mView.setOnQueryTextFocusChangeListener(this);
         }
 
         /**
@@ -755,19 +809,13 @@
          *     search currently.
          */
         boolean cancelSearch() {
-            boolean collapsed = false;
-            boolean closed = false;
-
-            if (mActionBar.hasExpandedActionView()) {
-                mActionBar.collapseActionView();
-                collapsed = true;
-            }
-
             if (isExpanded() || isSearching()) {
-                onClose();
-                closed = true;
+                // If the query string is not empty search view won't get iconified
+                mView.setQuery("", false);
+                mView.setIconified(true);
+                return true;
             }
-            return collapsed || closed;
+            return false;
         }
 
         boolean isSearching() {
@@ -778,6 +826,11 @@
             return mSearchExpanded;
         }
 
+        /**
+         * Clears the search.
+         * @return True if the default behavior of clearing/dismissing SearchView should be
+         *      overridden. False otherwise.
+         */
         @Override
         public boolean onClose() {
             mSearchExpanded = false;
@@ -786,33 +839,33 @@
                 return false;
             }
 
-            mState.currentSearch = null;
-            onCurrentDirectoryChanged(ANIM_NONE);
+            mView.setBackgroundColor(
+                    getResources().getColor(android.R.color.transparent, null));
+
+            // Refresh the directory if a search was done
+            if(mState.currentSearch != null) {
+                mState.currentSearch = null;
+                onCurrentDirectoryChanged(ANIM_NONE);
+            }
+
             return false;
         }
 
+        /**
+         * Sets mSearchExpanded.
+         * Called when search icon is clicked to start search.
+         * Used to detect when the view expanded instead of onMenuItemActionExpand, because
+         * SearchView has showAsAction set to always and onMenuItemAction* methods are not called.
+         */
         @Override
-        public boolean onMenuItemActionExpand(MenuItem item) {
+        public void onClick (View v) {
             mSearchExpanded = true;
-            updateActionBar();
-            return true;
-        }
-
-        @Override
-        public boolean onMenuItemActionCollapse(MenuItem item) {
-            mSearchExpanded = false;
-            if (mIgnoreNextCollapse) {
-                mIgnoreNextCollapse = false;
-                return true;
-            }
-            mState.currentSearch = null;
-            onCurrentDirectoryChanged(ANIM_NONE);
-            return true;
+            mView.setBackgroundColor(
+                    getResources().getColor(R.color.menu_search_background, null));
         }
 
         @Override
         public boolean onQueryTextSubmit(String query) {
-            mSearchExpanded = true;
             mState.currentSearch = query;
             mView.clearFocus();
             onCurrentDirectoryChanged(ANIM_NONE);
@@ -825,6 +878,18 @@
         }
 
         @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            if(!hasFocus) {
+                if(mState.currentSearch == null) {
+                    mView.setIconified(true);
+                }
+                else if(TextUtils.isEmpty(mView.getQuery())) {
+                    cancelSearch();
+                }
+            }
+        }
+
+        @Override
         public void onActionViewCollapsed() {
             updateActionBar();
         }
diff --git a/src/com/android/documentsui/CopyService.java b/src/com/android/documentsui/CopyService.java
deleted file mode 100644
index 6a5911b..0000000
--- a/src/com/android/documentsui/CopyService.java
+++ /dev/null
@@ -1,714 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui;
-
-import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.model.DocumentInfo.getCursorLong;
-import static com.android.documentsui.model.DocumentInfo.getCursorString;
-
-import android.app.Activity;
-import android.app.IntentService;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.design.widget.Snackbar;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import com.android.documentsui.model.DocumentInfo;
-import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.model.RootInfo;
-
-import libcore.io.IoUtils;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-public class CopyService extends IntentService {
-    public static final String TAG = "CopyService";
-
-    private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
-    public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
-    public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
-    public static final String EXTRA_TRANSFER_MODE = "com.android.documentsui.TRANSFER_MODE";
-
-    public static final int TRANSFER_MODE_COPY = 1;
-    public static final int TRANSFER_MODE_MOVE = 2;
-
-    // TODO: Move it to a shared file when more operations are implemented.
-    public static final int FAILURE_COPY = 1;
-
-    // Parameters of the copy job. Requests to an IntentService are serialized so this code only
-    // needs to deal with one job at a time.
-    // NOTE: This must be declared by concrete type as the concrete type
-    // is required by putParcelableArrayListExtra.
-    private final ArrayList<DocumentInfo> mFailedFiles = new ArrayList<>();
-
-    private PowerManager mPowerManager;
-
-    private NotificationManager mNotificationManager;
-    private Notification.Builder mProgressBuilder;
-
-    // Jobs are serialized but a job ID is used, to avoid mixing up cancellation requests.
-    private String mJobId;
-    private volatile boolean mIsCancelled;
-    private long mBatchSize;
-    private long mBytesCopied;
-    private long mStartTime;
-    private long mLastNotificationTime;
-    // Speed estimation
-    private long mBytesCopiedSample;
-    private long mSampleTime;
-    private long mSpeed;
-    private long mRemainingTime;
-    // Provider clients are acquired for the duration of each copy job. Note that there is an
-    // implicit assumption that all srcs come from the same authority.
-    private ContentProviderClient mSrcClient;
-    private ContentProviderClient mDstClient;
-
-    // For testing only.
-    @Nullable private TestOnlyListener mJobFinishedListener;
-
-    public CopyService() {
-        super("CopyService");
-    }
-
-    /**
-     * Starts the service for a copy operation.
-     *
-     * @param context Context for the intent.
-     * @param srcDocs A list of src files to copy.
-     * @param dstStack The copy destination stack.
-     */
-    public static void start(Activity activity, List<DocumentInfo> srcDocs, DocumentStack dstStack,
-            int mode) {
-        final Resources res = activity.getResources();
-        final Intent copyIntent = new Intent(activity, CopyService.class);
-        copyIntent.putParcelableArrayListExtra(
-                EXTRA_SRC_LIST,
-                // Don't create a copy unless absolutely necessary :)
-                srcDocs instanceof ArrayList
-                    ? (ArrayList<DocumentInfo>) srcDocs
-                    : new ArrayList<DocumentInfo>(srcDocs));
-        copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
-        copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
-
-        int toastMessage = (mode == TRANSFER_MODE_COPY) ? R.plurals.copy_begin
-                : R.plurals.move_begin;
-        Snackbars.makeSnackbar(activity,
-                res.getQuantityString(toastMessage, srcDocs.size(), srcDocs.size()),
-                Snackbar.LENGTH_SHORT).show();
-        activity.startService(copyIntent);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent.hasExtra(EXTRA_CANCEL)) {
-            handleCancel(intent);
-        }
-        return super.onStartCommand(intent, flags, startId);
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        if (intent.hasExtra(EXTRA_CANCEL)) {
-            handleCancel(intent);
-            return;
-        }
-
-        final PowerManager.WakeLock wakeLock = mPowerManager
-                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-        final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
-        final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
-        // Copy by default.
-        final int transferMode = intent.getIntExtra(EXTRA_TRANSFER_MODE, TRANSFER_MODE_COPY);
-
-        try {
-            wakeLock.acquire();
-
-            // Acquire content providers.
-            mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
-                    srcs.get(0).authority);
-            mDstClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
-                    stack.peek().authority);
-
-            setupCopyJob(srcs, stack, transferMode);
-
-            final String opDesc = transferMode == TRANSFER_MODE_COPY ? "copy" : "move";
-            DocumentInfo srcInfo;
-            DocumentInfo dstInfo;
-            for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
-                srcInfo = srcs.get(i);
-                dstInfo = stack.peek();
-
-                // Guard unsupported recursive operation.
-                if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
-                    if (DEBUG) Log.d(TAG,
-                            "Skipping recursive " + opDesc + " of directory " + dstInfo.derivedUri);
-                    mFailedFiles.add(srcInfo);
-                    continue;
-                }
-
-                if (DEBUG) Log.d(TAG,
-                        "Performing " + opDesc + " of " + srcInfo.displayName
-                        + " (" + srcInfo.derivedUri + ")" + " to " + dstInfo.displayName
-                        + " (" + dstInfo.derivedUri + ")");
-
-                copy(srcInfo, dstInfo, transferMode);
-            }
-        } catch (Exception e) {
-            // Catch-all to prevent any copy errors from wedging the app.
-            Log.e(TAG, "Exceptions occurred during copying", e);
-        } finally {
-            if (DEBUG) Log.d(TAG, "Cleaning up after copy");
-            ContentProviderClient.releaseQuietly(mSrcClient);
-            ContentProviderClient.releaseQuietly(mDstClient);
-
-            wakeLock.release();
-
-            // Dismiss the ongoing copy notification when the copy is done.
-            mNotificationManager.cancel(mJobId, 0);
-
-            if (mFailedFiles.size() > 0) {
-                Log.e(TAG, mFailedFiles.size() + " files failed to copy");
-                final Context context = getApplicationContext();
-                final Intent navigateIntent = buildNavigateIntent(context, stack);
-                navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
-                navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
-                navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
-
-                final int titleResourceId = (transferMode == TRANSFER_MODE_COPY ?
-                        R.plurals.copy_error_notification_title :
-                        R.plurals.move_error_notification_title);
-                final Notification.Builder errorBuilder = new Notification.Builder(this)
-                        .setContentTitle(context.getResources().getQuantityString(titleResourceId,
-                                mFailedFiles.size(), mFailedFiles.size()))
-                        .setContentText(getString(R.string.notification_touch_for_details))
-                        .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent,
-                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT))
-                        .setCategory(Notification.CATEGORY_ERROR)
-                        .setSmallIcon(R.drawable.ic_menu_copy)
-                        .setAutoCancel(true);
-                mNotificationManager.notify(mJobId, 0, errorBuilder.build());
-            }
-
-            if (mJobFinishedListener != null) {
-                mJobFinishedListener.onFinished(mFailedFiles);
-            }
-
-            if (DEBUG) Log.d(TAG, "Done cleaning up");
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mPowerManager = getSystemService(PowerManager.class);
-        mNotificationManager = getSystemService(NotificationManager.class);
-    }
-
-    /**
-     * Sets up the CopyService to start tracking and sending notifications for the given batch of
-     * files.
-     *
-     * @param srcs A list of src files to copy.
-     * @param stack The copy destination stack.
-     * @param transferMode The mode (i.e. copy, or move)
-     * @throws RemoteException
-     */
-    private void setupCopyJob(ArrayList<DocumentInfo> srcs, DocumentStack stack, int transferMode)
-            throws RemoteException {
-        final boolean copying = (transferMode == TRANSFER_MODE_COPY);
-        // Create an ID for this copy job. Use the timestamp.
-        mJobId = String.valueOf(SystemClock.elapsedRealtime());
-        // Reset the cancellation flag.
-        mIsCancelled = false;
-
-        final Context context = getApplicationContext();
-        final Intent navigateIntent = buildNavigateIntent(context, stack);
-
-        final String contentTitle = getString(copying ? R.string.copy_notification_title
-                : R.string.move_notification_title);
-        mProgressBuilder = new Notification.Builder(this)
-                .setContentTitle(contentTitle)
-                .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, 0))
-                .setCategory(Notification.CATEGORY_PROGRESS)
-                .setSmallIcon(R.drawable.ic_menu_copy)
-                .setOngoing(true);
-
-        final Intent cancelIntent = new Intent(this, CopyService.class);
-        cancelIntent.putExtra(EXTRA_CANCEL, mJobId);
-        mProgressBuilder.addAction(R.drawable.ic_cab_cancel,
-                getString(android.R.string.cancel), PendingIntent.getService(this, 0,
-                        cancelIntent,
-                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT));
-
-        // Send an initial progress notification.
-        final String contentText = getString(copying ? R.string.copy_preparing
-                : R.string.move_preparing);
-        mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up.
-        mProgressBuilder.setContentText(contentText);
-        mNotificationManager.notify(mJobId, 0, mProgressBuilder.build());
-
-        // Reset batch parameters.
-        mFailedFiles.clear();
-        mBatchSize = calculateFileSizes(srcs);
-        mBytesCopied = 0;
-        mStartTime = SystemClock.elapsedRealtime();
-        mLastNotificationTime = 0;
-        mBytesCopiedSample = 0;
-        mSampleTime = 0;
-        mSpeed = 0;
-        mRemainingTime = 0;
-
-        // TODO: Check preconditions for copy.
-        // - check that the destination has enough space and is writeable?
-        // - check MIME types?
-    }
-
-    /**
-     * Sets a callback to be run when the next run job is finished.
-     * This is test ONLY instrumentation. The alternative is for us to add
-     * broadcast intents SOLELY for the purpose of testing.
-     * @param listener
-     */
-    @VisibleForTesting
-    void addFinishedListener(TestOnlyListener listener) {
-        this.mJobFinishedListener = listener;
-
-    }
-
-    /**
-     * Only used for testing. Is that obvious enough?
-     */
-    @VisibleForTesting
-    interface TestOnlyListener {
-        void onFinished(List<DocumentInfo> failed);
-    }
-
-    /**
-     * Calculates the cumulative size of all the documents in the list. Directories are recursed
-     * into and totaled up.
-     *
-     * @param srcs
-     * @return Size in bytes.
-     * @throws RemoteException
-     */
-    private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
-        long result = 0;
-        for (DocumentInfo src : srcs) {
-            if (src.isDirectory()) {
-                // Directories need to be recursed into.
-                result += calculateFileSizesHelper(src.derivedUri);
-            } else {
-                result += src.size;
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Calculates (recursively) the cumulative size of all the files under the given directory.
-     *
-     * @throws RemoteException
-     */
-    private long calculateFileSizesHelper(Uri uri) throws RemoteException {
-        final String authority = uri.getAuthority();
-        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority,
-                DocumentsContract.getDocumentId(uri));
-        final String queryColumns[] = new String[] {
-                Document.COLUMN_DOCUMENT_ID,
-                Document.COLUMN_MIME_TYPE,
-                Document.COLUMN_SIZE
-        };
-
-        long result = 0;
-        Cursor cursor = null;
-        try {
-            cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
-            while (cursor.moveToNext()) {
-                if (Document.MIME_TYPE_DIR.equals(
-                        getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
-                    // Recurse into directories.
-                    final Uri subdirUri = DocumentsContract.buildDocumentUri(authority,
-                            getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
-                    result += calculateFileSizesHelper(subdirUri);
-                } else {
-                    // This may return -1 if the size isn't defined. Ignore those cases.
-                    long size = getCursorLong(cursor, Document.COLUMN_SIZE);
-                    result += size > 0 ? size : 0;
-                }
-            }
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-
-        return result;
-    }
-
-    /**
-     * Cancels the current copy job, if its ID matches the given ID.
-     *
-     * @param intent The cancellation intent.
-     */
-    private void handleCancel(Intent intent) {
-        final String cancelledId = intent.getStringExtra(EXTRA_CANCEL);
-        // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
-        // cancellation requests from affecting unrelated copy jobs.  However, if the current job ID
-        // is null, the service most likely crashed and was revived by the incoming cancel intent.
-        // In that case, always allow the cancellation to proceed.
-        if (Objects.equals(mJobId, cancelledId) || mJobId == null) {
-            // Set the cancel flag. This causes the copy loops to exit.
-            mIsCancelled = true;
-            // Dismiss the progress notification here rather than in the copy loop. This preserves
-            // interactivity for the user in case the copy loop is stalled.
-            mNotificationManager.cancel(cancelledId, 0);
-        }
-    }
-
-    /**
-     * Logs progress on the current copy operation. Displays/Updates the progress notification.
-     *
-     * @param bytesCopied
-     */
-    private void makeProgress(long bytesCopied) {
-        mBytesCopied += bytesCopied;
-        double done = (double) mBytesCopied / mBatchSize;
-        String percent = NumberFormat.getPercentInstance().format(done);
-
-        // Update time estimate
-        long currentTime = SystemClock.elapsedRealtime();
-        long elapsedTime = currentTime - mStartTime;
-
-        // Send out progress notifications once a second.
-        if (currentTime - mLastNotificationTime > 1000) {
-            updateRemainingTimeEstimate(elapsedTime);
-            mProgressBuilder.setProgress(100, (int) (done * 100), false);
-            mProgressBuilder.setContentInfo(percent);
-            if (mRemainingTime > 0) {
-                mProgressBuilder.setContentText(getString(R.string.copy_remaining,
-                        DateUtils.formatDuration(mRemainingTime)));
-            } else {
-                mProgressBuilder.setContentText(null);
-            }
-            mNotificationManager.notify(mJobId, 0, mProgressBuilder.build());
-            mLastNotificationTime = currentTime;
-        }
-    }
-
-    /**
-     * Generates an estimate of the remaining time in the copy.
-     *
-     * @param elapsedTime The time elapsed so far.
-     */
-    private void updateRemainingTimeEstimate(long elapsedTime) {
-        final long sampleDuration = elapsedTime - mSampleTime;
-        final long sampleSpeed = ((mBytesCopied - mBytesCopiedSample) * 1000) / sampleDuration;
-        if (mSpeed == 0) {
-            mSpeed = sampleSpeed;
-        } else {
-            mSpeed = ((3 * mSpeed) + sampleSpeed) / 4;
-        }
-
-        if (mSampleTime > 0 && mSpeed > 0) {
-            mRemainingTime = ((mBatchSize - mBytesCopied) * 1000) / mSpeed;
-        } else {
-            mRemainingTime = 0;
-        }
-
-        mSampleTime = elapsedTime;
-        mBytesCopiedSample = mBytesCopied;
-    }
-
-    /**
-     * Copies a the given documents to the given location.
-     *
-     * @param srcInfo DocumentInfos for the documents to copy.
-     * @param dstDirInfo The destination directory.
-     * @param mode The transfer mode (copy or move).
-     * @return True on success, false on failure.
-     * @throws RemoteException
-     */
-    private boolean copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
-            throws RemoteException {
-        // When copying within the same provider, try to use optimized copying and moving.
-        // If not supported, then fallback to byte-by-byte copy/move.
-        if (srcInfo.authority.equals(dstDirInfo.authority)) {
-            switch (mode) {
-                case TRANSFER_MODE_COPY:
-                    if ((srcInfo.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
-                        if (DocumentsContract.copyDocument(mSrcClient, srcInfo.derivedUri,
-                                dstDirInfo.derivedUri) == null) {
-                            mFailedFiles.add(srcInfo);
-                        }
-                        return false;
-                    }
-                    break;
-                case TRANSFER_MODE_MOVE:
-                    if ((srcInfo.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
-                        if (DocumentsContract.moveDocument(mSrcClient, srcInfo.derivedUri,
-                                dstDirInfo.derivedUri) == null) {
-                            mFailedFiles.add(srcInfo);
-                        }
-                        return false;
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown transfer mode.");
-            }
-        }
-
-        final String dstMimeType;
-        final String dstDisplayName;
-
-        // If the file is virtual, but can be converted to another format, then try to copy it
-        // as such format. Also, append an extension for the target mime type (if known).
-        if (srcInfo.isVirtualDocument()) {
-            if (!srcInfo.isTypedDocument()) {
-                // Impossible to copy a file which is virtual, but not typed.
-                mFailedFiles.add(srcInfo);
-                return false;
-            }
-            final String[] streamTypes = getContentResolver().getStreamTypes(
-                    srcInfo.derivedUri, "*/*");
-            if (streamTypes != null && streamTypes.length > 0) {
-                dstMimeType = streamTypes[0];
-                final String extension = MimeTypeMap.getSingleton().
-                        getExtensionFromMimeType(dstMimeType);
-                dstDisplayName = srcInfo.displayName +
-                        (extension != null ? "." + extension : srcInfo.displayName);
-            } else {
-                // The provider says that it supports typed documents, but doesn't say
-                // anything about available formats.
-                // TODO: Log failures. b/26192412
-                mFailedFiles.add(srcInfo);
-                return false;
-            }
-        } else {
-            dstMimeType = srcInfo.mimeType;
-            dstDisplayName = srcInfo.displayName;
-        }
-
-        // Create the target document (either a file or a directory), then copy recursively the
-        // contents (bytes or children).
-        final Uri dstUri = DocumentsContract.createDocument(mDstClient,
-                dstDirInfo.derivedUri, dstMimeType, dstDisplayName);
-        if (dstUri == null) {
-            // If this is a directory, the entire subdir will not be copied over.
-            mFailedFiles.add(srcInfo);
-            return false;
-        }
-
-        DocumentInfo dstInfo = null;
-        try {
-            dstInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
-        } catch (FileNotFoundException e) {
-            mFailedFiles.add(srcInfo);
-            return false;
-        }
-
-        final boolean success;
-        if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
-            success = copyDirectoryHelper(srcInfo, dstInfo, mode);
-        } else {
-            success = copyFileHelper(srcInfo, dstInfo, dstMimeType, mode);
-        }
-
-        if (mode == TRANSFER_MODE_MOVE && success) {
-            // This is racey. We should make sure that we never delete a directory after
-            // it changed, so we don't remove a file which had not been copied earlier
-            // to the target location.
-            try {
-                DocumentsContract.deleteDocument(mSrcClient, srcInfo.derivedUri);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to delete source after moving: " + srcInfo.derivedUri, e);
-                throw e;
-            }
-        }
-
-        return success;
-    }
-
-    /**
-     * Returns true if {@code doc} is a descendant of {@code parentDoc}.
-     * @throws RemoteException
-     */
-    boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc)
-            throws RemoteException {
-        if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
-            return DocumentsContract.isChildDocument(
-                    mDstClient, doc.derivedUri, parentDoc.derivedUri);
-        }
-        return false;
-    }
-
-    /**
-     * Handles recursion into a directory and copying its contents. Note that in linux terms, this
-     * does the equivalent of "cp src/* dst", not "cp -r src dst".
-     *
-     * @param srcDirInfo Info of the directory to copy from. The routine will copy the directory's
-     *            contents, not the directory itself.
-     * @param dstDirInfo Info of the directory to copy to. Must be created beforehand.
-     * @return True on success, false if some of the children failed to copy.
-     * @throws RemoteException
-     */
-    private boolean copyDirectoryHelper(
-            DocumentInfo srcDirInfo, DocumentInfo dstDirInfo, int mode)
-            throws RemoteException {
-        // Recurse into directories. Copy children into the new subdirectory.
-        final String queryColumns[] = new String[] {
-                Document.COLUMN_DISPLAY_NAME,
-                Document.COLUMN_DOCUMENT_ID,
-                Document.COLUMN_MIME_TYPE,
-                Document.COLUMN_SIZE,
-                Document.COLUMN_FLAGS
-        };
-        Cursor cursor = null;
-        boolean success = true;
-        try {
-            // Iterate over srcs in the directory; copy to the destination directory.
-            final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirInfo.authority,
-                    srcDirInfo.documentId);
-            cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
-            DocumentInfo srcInfo;
-            while (cursor.moveToNext()) {
-                srcInfo = DocumentInfo.fromCursor(cursor, srcDirInfo.authority);
-                success &= copy(srcInfo, dstDirInfo, mode);
-            }
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-
-        return success;
-    }
-
-    /**
-     * Handles copying a single file.
-     *
-     * @param srcUriInfo Info of the file to copy from.
-     * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand.
-     * @param mimeType Mime type for the target. Can be different than source for virtual files.
-     * @return True on success, false on error.
-     * @throws RemoteException
-     */
-    private boolean copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, String mimeType,
-            int mode) throws RemoteException {
-        // Copy an individual file.
-        CancellationSignal canceller = new CancellationSignal();
-        ParcelFileDescriptor srcFile = null;
-        ParcelFileDescriptor dstFile = null;
-        InputStream src = null;
-        OutputStream dst = null;
-
-        boolean success = true;
-        try {
-            // If the file is virtual, but can be converted to another format, then try to copy it
-            // as such format.
-            if (srcInfo.isVirtualDocument() && srcInfo.isTypedDocument()) {
-                final AssetFileDescriptor srcFileAsAsset =
-                        mSrcClient.openTypedAssetFileDescriptor(
-                                srcInfo.derivedUri, mimeType, null, canceller);
-                srcFile = srcFileAsAsset.getParcelFileDescriptor();
-                src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
-            } else {
-                srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller);
-                src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
-            }
-
-            dstFile = mDstClient.openFile(dstInfo.derivedUri, "w", canceller);
-            dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
-
-            byte[] buffer = new byte[8192];
-            int len;
-            while ((len = src.read(buffer)) != -1) {
-                if (mIsCancelled) {
-                    success = false;
-                    break;
-                }
-                dst.write(buffer, 0, len);
-                makeProgress(len);
-            }
-
-            srcFile.checkError();
-        } catch (IOException e) {
-            success = false;
-            mFailedFiles.add(srcInfo);
-
-            if (dstFile != null) {
-                try {
-                    dstFile.closeWithError(e.getMessage());
-                } catch (IOException closeError) {
-                    Log.e(TAG, "Error closing destination", closeError);
-                }
-            }
-        } finally {
-            // This also ensures the file descriptors are closed.
-            IoUtils.closeQuietly(src);
-            IoUtils.closeQuietly(dst);
-        }
-
-        if (!success) {
-            // Clean up half-copied files.
-            canceller.cancel();
-            try {
-                DocumentsContract.deleteDocument(mDstClient, dstInfo.derivedUri);
-            } catch (RemoteException e) {
-                // RemoteExceptions usually signal that the connection is dead, so there's no
-                // point attempting to continue. Propagate the exception up so the copy job is
-                // cancelled.
-                Log.w(TAG, "Failed to cleanup after copy error: " + srcInfo.derivedUri, e);
-                throw e;
-            }
-        }
-
-        return success;
-    }
-
-    /**
-     * Creates an intent for navigating back to the destination directory.
-     */
-    private Intent buildNavigateIntent(Context context, DocumentStack stack) {
-        Intent intent = new Intent(context, FilesActivity.class);
-        intent.setAction(DocumentsContract.ACTION_BROWSE);
-        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
-        return intent;
-    }
-}
diff --git a/src/com/android/documentsui/DocumentsActivity.java b/src/com/android/documentsui/DocumentsActivity.java
index 8ca2cfb..223af89 100644
--- a/src/com/android/documentsui/DocumentsActivity.java
+++ b/src/com/android/documentsui/DocumentsActivity.java
@@ -54,6 +54,7 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
 
 import java.util.Arrays;
 import java.util.List;
@@ -154,8 +155,8 @@
         if (state.action == ACTION_PICK_COPY_DESTINATION) {
             state.directoryCopy = intent.getBooleanExtra(
                     Shared.EXTRA_DIRECTORY_COPY, false);
-            state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
-                    CopyService.TRANSFER_MODE_COPY);
+            state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
+                    FileOperationService.OPERATION_COPY);
         }
 
         return state;
@@ -307,8 +308,10 @@
         mSearchManager.showMenu(!picking);
 
         // No display options in recent directories
-        grid.setVisible(!(picking && recents));
-        list.setVisible(!(picking && recents));
+        if (picking && recents) {
+            grid.setVisible(false);
+            list.setVisible(false);
+        }
 
         fileSize.setVisible(fileSize.isVisible() && !picking);
         settings.setVisible(false);
@@ -481,7 +484,7 @@
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
             intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
-            intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mState.transferMode);
+            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.transferMode);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/src/com/android/documentsui/FailureDialogFragment.java b/src/com/android/documentsui/FailureDialogFragment.java
index 23074f0..7f6f1c6 100644
--- a/src/com/android/documentsui/FailureDialogFragment.java
+++ b/src/com/android/documentsui/FailureDialogFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -27,6 +29,8 @@
 
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperations;
 
 import java.util.ArrayList;
 
@@ -37,20 +41,20 @@
         implements DialogInterface.OnClickListener {
     private static final String TAG = "FailureDialogFragment";
 
-    private int mTransferMode;
+    private int mOperationType;
     private ArrayList<DocumentInfo> mFailedSrcList;
 
     public static void show(FragmentManager fm, int failure,
-            ArrayList<DocumentInfo> failedSrcList, DocumentStack dstStack, int transferMode) {
+            ArrayList<DocumentInfo> failedSrcList, DocumentStack dstStack, int operationType) {
         // TODO: Add support for other failures than copy.
-        if (failure != CopyService.FAILURE_COPY) {
+        if (failure != FileOperationService.FAILURE_COPY) {
             return;
         }
 
         final Bundle args = new Bundle();
-        args.putInt(CopyService.EXTRA_FAILURE, failure);
-        args.putInt(CopyService.EXTRA_TRANSFER_MODE, transferMode);
-        args.putParcelableArrayList(CopyService.EXTRA_SRC_LIST, failedSrcList);
+        args.putInt(FileOperationService.EXTRA_FAILURE, failure);
+        args.putInt(FileOperationService.EXTRA_OPERATION, operationType);
+        args.putParcelableArrayList(FileOperationService.EXTRA_SRC_LIST, failedSrcList);
 
         final FragmentTransaction ft = fm.beginTransaction();
         final FailureDialogFragment fragment = new FailureDialogFragment();
@@ -63,10 +67,12 @@
     @Override
     public void onClick(DialogInterface dialog, int whichButton) {
         if (whichButton == DialogInterface.BUTTON_POSITIVE) {
-            CopyService.start(getActivity(), mFailedSrcList,
+            FileOperations.start(
+                    getActivity(),
+                    mFailedSrcList,
                     (DocumentStack) getActivity().getIntent().getParcelableExtra(
                             Shared.EXTRA_STACK),
-                            mTransferMode);
+                    mOperationType);
         }
     }
 
@@ -74,16 +80,27 @@
     public Dialog onCreateDialog(Bundle inState) {
         super.onCreate(inState);
 
-        mTransferMode = getArguments().getInt(CopyService.EXTRA_TRANSFER_MODE);
-        mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
+        mOperationType = getArguments().getInt(FileOperationService.EXTRA_OPERATION);
+        mFailedSrcList = getArguments().getParcelableArrayList(FileOperationService.EXTRA_SRC_LIST);
 
         final StringBuilder list = new StringBuilder("<p>");
         for (DocumentInfo documentInfo : mFailedSrcList) {
             list.append(String.format("&#8226; %s<br>", documentInfo.displayName));
         }
         list.append("</p>");
-        final String messageFormat = getString(mTransferMode == CopyService.TRANSFER_MODE_COPY ?
-                R.string.copy_failure_alert_content : R.string.move_failure_alert_content);
+
+        // TODO: Add support for other file operations.
+        checkArgument(
+                mOperationType == FileOperationService.OPERATION_COPY
+                || mOperationType == FileOperationService.OPERATION_MOVE);
+
+        int messageId = mOperationType == FileOperationService.OPERATION_COPY
+                ? R.string.copy_failure_alert_content
+                : R.string.move_failure_alert_content;
+
+        final String messageFormat = getString(
+                messageId);
+
         final String message = String.format(messageFormat, list.toString());
 
         return new AlertDialog.Builder(getActivity())
diff --git a/src/com/android/documentsui/FilesActivity.java b/src/com/android/documentsui/FilesActivity.java
index e308f3f..0bd09f6 100644
--- a/src/com/android/documentsui/FilesActivity.java
+++ b/src/com/android/documentsui/FilesActivity.java
@@ -49,6 +49,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -120,20 +121,22 @@
                     ProviderExecutor.forAuthority(homeUri.getAuthority()));
         }
 
-        final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
-        final int transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
-                CopyService.TRANSFER_MODE_COPY);
+        final int failure = intent.getIntExtra(FileOperationService.EXTRA_FAILURE, 0);
+        final int opType = intent.getIntExtra(
+                FileOperationService.EXTRA_OPERATION,
+                FileOperationService.OPERATION_COPY);
+
         // DialogFragment takes care of restoring the dialog on configuration change.
         // Only show it manually for the first time (icicle is null).
         if (icicle == null && failure != 0) {
             final ArrayList<DocumentInfo> failedSrcList =
-                    intent.getParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST);
+                    intent.getParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST);
             FailureDialogFragment.show(
                     getFragmentManager(),
                     failure,
                     failedSrcList,
                     mState.stack,
-                    transferMode);
+                    opType);
         }
     }
 
diff --git a/src/com/android/documentsui/IconUtils.java b/src/com/android/documentsui/IconUtils.java
index a1213d2..c28fae8 100644
--- a/src/com/android/documentsui/IconUtils.java
+++ b/src/com/android/documentsui/IconUtils.java
@@ -159,8 +159,8 @@
         add("application/vnd.sun.xml.calc.template", icon);
         add("application/x-kspread", icon);
 
-        // Text
-        icon = R.drawable.ic_doc_text;
+        // Document
+        icon = R.drawable.ic_doc_document;
         add("application/vnd.oasis.opendocument.text", icon);
         add("application/vnd.oasis.opendocument.text-master", icon);
         add("application/vnd.oasis.opendocument.text-template", icon);
diff --git a/src/com/android/documentsui/RecentsCreateFragment.java b/src/com/android/documentsui/RecentsCreateFragment.java
index bb6c3b5..7dac0c1 100644
--- a/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/src/com/android/documentsui/RecentsCreateFragment.java
@@ -107,7 +107,7 @@
                 mAdapter.update(data);
 
                 // When launched into empty recents, show drawer
-                if (mAdapter.isEmpty() && !state.stackTouched &&
+                if (mAdapter.isEmpty() && !state.hasLocationChanged() &&
                         context instanceof DocumentsActivity) {
                     ((DocumentsActivity) context).setRootsDrawerOpen(true);
                 }
diff --git a/src/com/android/documentsui/RootsCache.java b/src/com/android/documentsui/RootsCache.java
index 72ee6cb..21e7566 100644
--- a/src/com/android/documentsui/RootsCache.java
+++ b/src/com/android/documentsui/RootsCache.java
@@ -63,6 +63,7 @@
 
     private final Context mContext;
     private final ContentObserver mObserver;
+    private OnCacheUpdateListener mCacheUpdateListener;
 
     private final RootInfo mRecentsRoot = new RootInfo();
 
@@ -94,6 +95,10 @@
         }
     }
 
+    static interface OnCacheUpdateListener {
+        void onCacheUpdate();
+    }
+
     /**
      * Gather roots from all known storage providers.
      */
@@ -209,6 +214,13 @@
             return null;
         }
 
+        @Override
+        protected void onPostExecute(Void result) {
+            if (mCacheUpdateListener != null) {
+                mCacheUpdateListener.onCacheUpdate();
+            }
+        }
+
         private void handleDocumentsProvider(ProviderInfo info) {
             // Ignore stopped packages for now; we might query them
             // later during UI interaction.
@@ -348,6 +360,10 @@
         }
     }
 
+    public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) {
+        mCacheUpdateListener = cacheUpdateListener;
+    }
+
     @VisibleForTesting
     static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
         final List<RootInfo> matching = new ArrayList<>();
diff --git a/src/com/android/documentsui/RootsFragment.java b/src/com/android/documentsui/RootsFragment.java
index 4c844c4..beff196 100644
--- a/src/com/android/documentsui/RootsFragment.java
+++ b/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
 
             for (final RootInfo root : roots) {
                 final RootItem item = new RootItem(root);
-                if (root.isLibrary() || root.isHome()) {
+                if (root.isLibrary()) {
                     libraries.add(item);
                 } else {
                     others.add(item);
diff --git a/src/com/android/documentsui/Shared.java b/src/com/android/documentsui/Shared.java
index c3366c3..22cb25a 100644
--- a/src/com/android/documentsui/Shared.java
+++ b/src/com/android/documentsui/Shared.java
@@ -20,6 +20,9 @@
 import android.text.format.DateUtils;
 import android.text.format.Time;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** @hide */
 public final class Shared {
     /** Intent action name to pick a copy destination. */
@@ -64,4 +67,13 @@
         return DateUtils.formatDateTime(context, when, flags);
     }
 
+    /**
+     * A convenient way to transform any list into a (parcelable) ArrayList.
+     * Uses cast if possible, else creates a new list with entries from {@code list}.
+     */
+    public static <T> ArrayList<T> asArrayList(List<T> list) {
+        return list instanceof ArrayList
+            ? (ArrayList<T>) list
+            : new ArrayList<T>(list);
+    }
 }
diff --git a/src/com/android/documentsui/State.java b/src/com/android/documentsui/State.java
index 46372c0..2f0224f 100644
--- a/src/com/android/documentsui/State.java
+++ b/src/com/android/documentsui/State.java
@@ -24,6 +24,7 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
+import com.android.documentsui.model.RootInfo;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -49,7 +50,6 @@
     public boolean localOnly;
     public boolean forceAdvanced;
     public boolean showAdvanced;
-    public boolean stackTouched;
     public boolean restored;
     public boolean directoryCopy;
     public boolean openableOnly;
@@ -87,6 +87,8 @@
     public static final int SORT_ORDER_LAST_MODIFIED = 2;
     public static final int SORT_ORDER_SIZE = 3;
 
+    private boolean mStackTouched;
+
     public void initAcceptMimes(Intent intent) {
         if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
             acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
@@ -96,6 +98,31 @@
         }
     }
 
+    public void onRootChanged(RootInfo root) {
+        stack.root = root;
+        stack.clear();
+        mStackTouched = true;
+    }
+
+    public void pushDocument(DocumentInfo info) {
+        stack.push(info);
+        mStackTouched = true;
+    }
+
+    public void popDocument() {
+        stack.pop();
+        mStackTouched = true;
+    }
+
+    public void setStack(DocumentStack stack) {
+        this.stack = stack;
+        mStackTouched = true;
+    }
+
+    public boolean hasLocationChanged() {
+        return mStackTouched;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -113,7 +140,6 @@
         out.writeInt(localOnly ? 1 : 0);
         out.writeInt(forceAdvanced ? 1 : 0);
         out.writeInt(showAdvanced ? 1 : 0);
-        out.writeInt(stackTouched ? 1 : 0);
         out.writeInt(restored ? 1 : 0);
         DurableUtils.writeToParcel(out, stack);
         out.writeString(currentSearch);
@@ -121,6 +147,7 @@
         out.writeList(selectedDocumentsForCopy);
         out.writeList(excludedAuthorities);
         out.writeInt(openableOnly ? 1 : 0);
+        out.writeInt(mStackTouched ? 1 : 0);
     }
 
     public static final Creator<State> CREATOR = new Creator<State>() {
@@ -137,7 +164,6 @@
             state.localOnly = in.readInt() != 0;
             state.forceAdvanced = in.readInt() != 0;
             state.showAdvanced = in.readInt() != 0;
-            state.stackTouched = in.readInt() != 0;
             state.restored = in.readInt() != 0;
             DurableUtils.readFromParcel(in, state.stack);
             state.currentSearch = in.readString();
@@ -145,6 +171,7 @@
             in.readList(state.selectedDocumentsForCopy, null);
             in.readList(state.excludedAuthorities, null);
             state.openableOnly = in.readInt() != 0;
+            state.mStackTouched = in.readInt() != 0;
             return state;
         }
 
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 9617582..22e81c6 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -78,7 +78,6 @@
 import android.widget.TextView;
 
 import com.android.documentsui.BaseActivity;
-import com.android.documentsui.CopyService;
 import com.android.documentsui.DirectoryLoader;
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.DocumentClipper;
@@ -100,6 +99,8 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperations;
 
 import com.google.common.collect.Lists;
 
@@ -406,12 +407,11 @@
                     state.derivedMode = result.mode;
                 }
                 state.derivedSortOrder = result.sortOrder;
-                ((BaseActivity) context).onStateChanged();
 
                 updateDisplayState();
 
                 // When launched into empty recents, show drawer
-                if (mType == TYPE_RECENT_OPEN && mModel.isEmpty() && !state.stackTouched &&
+                if (mType == TYPE_RECENT_OPEN && mModel.isEmpty() && !state.hasLocationChanged() &&
                         context instanceof DocumentsActivity) {
                     ((DocumentsActivity) context).setRootsDrawerOpen(true);
                 }
@@ -455,9 +455,15 @@
             return;
         }
 
-        CopyService.start(getActivity(), getDisplayState().selectedDocumentsForCopy,
+        int operationType = data.getIntExtra(
+                FileOperationService.EXTRA_OPERATION,
+                FileOperationService.OPERATION_COPY);
+
+        FileOperations.start(
+                getActivity(),
+                getDisplayState().selectedDocumentsForCopy,
                 (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
-                data.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_COPY));
+                operationType);
     }
 
     private boolean onSingleTapUp(MotionEvent e) {
@@ -595,11 +601,11 @@
                 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
         }
 
-        mRecView.setLayoutManager(layout);
-        // TODO: Once b/23691541 is resolved, use a listener within MultiSelectManager instead of
-        // imperatively calling this function.
-        mSelectionManager.handleLayoutChanged();
+        int pad = getDirectoryPadding(mode);
+        mRecView.setPadding(pad, pad, pad, pad);
         // setting layout manager automatically invalidates existing ViewHolders.
+        mRecView.setLayoutManager(layout);
+        mSelectionManager.handleLayoutChanged();  // RecyclerView doesn't do this for us
         mIconHelper.setMode(mode);
     }
 
@@ -615,6 +621,20 @@
         return columnCount;
     }
 
+    private int getDirectoryPadding(int mode) {
+        switch (mode) {
+            case MODE_GRID:
+                return getResources().getDimensionPixelSize(
+                        R.dimen.grid_container_padding);
+            case MODE_LIST:
+                return getResources().getDimensionPixelSize(
+                        R.dimen.list_container_padding);
+            case MODE_UNKNOWN:
+            default:
+                throw new IllegalArgumentException("Unsupported layout mode: " + mode);
+        }
+    }
+
     @Override
     public int getColumnCount() {
         return mColumnCount;
@@ -739,14 +759,14 @@
                     return true;
 
                 case R.id.menu_copy_to:
-                    transferDocuments(selection, CopyService.TRANSFER_MODE_COPY);
+                    transferDocuments(selection, FileOperationService.OPERATION_COPY);
                     mode.finish();
                     return true;
 
                 case R.id.menu_move_to:
                     // Exit selection mode first, so we avoid deselecting deleted documents.
                     mode.finish();
-                    transferDocuments(selection, CopyService.TRANSFER_MODE_MOVE);
+                    transferDocuments(selection, FileOperationService.OPERATION_MOVE);
                     return true;
 
                 case R.id.menu_copy_to_clipboard:
@@ -898,7 +918,7 @@
                     }
                 }
                 intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
-                intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
+                intent.putExtra(FileOperationService.EXTRA_OPERATION, mode);
                 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
             }
         }.execute(selected);
@@ -1035,7 +1055,7 @@
             tmpStack = curStack;
         }
 
-        CopyService.start(getActivity(), docs, tmpStack, CopyService.TRANSFER_MODE_COPY);
+        FileOperations.copy(getActivity(), docs, tmpStack);
     }
 
     private ClipData getClipDataFromDocuments(List<DocumentInfo> docs) {
diff --git a/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java b/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java
deleted file mode 100644
index ab67a5b..0000000
--- a/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.dirlist;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.widget.Space;
-
-import com.android.documentsui.R;
-import com.android.documentsui.State;
-
-final class EmptyDocumentHolder extends DocumentHolder {
-    public EmptyDocumentHolder(Context context) {
-        super(context, new Space(context));
-
-        // Per UX spec, this puts a bigger gap between the folders and documents in the grid.
-        final int gridMargin = context.getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
-        itemView.setMinimumHeight(gridMargin * 2);
-    }
-
-    public void bind(Cursor cursor, String modelId, State state) {
-        // Nothing to bind.
-        return;
-    }
-}
diff --git a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
index 11ff263..e672327 100644
--- a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
+++ b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.provider.DocumentsContract.Document;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.documentsui.R;
@@ -30,10 +31,24 @@
 
 final class GridDirectoryHolder extends DocumentHolder {
     final TextView mTitle;
+    private ImageView mIconCheck;
+    private ImageView mIconMime;
+
     public GridDirectoryHolder(Context context, ViewGroup parent) {
         super(context, parent, R.layout.item_dir_grid);
 
         mTitle = (TextView) itemView.findViewById(android.R.id.title);
+        mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime_sm);
+        mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
+    }
+
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        float checkAlpha = selected ? 1f : 0f;
+
+        mIconCheck.animate().alpha(checkAlpha).start();
+        mIconMime.animate().alpha(1f - checkAlpha).start();
     }
 
     /**
diff --git a/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
index 63c667b..c4ac0f5 100644
--- a/src/com/android/documentsui/dirlist/GridDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
@@ -32,6 +32,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.documentsui.IconUtils;
 import com.android.documentsui.R;
 import com.android.documentsui.RootCursorWrapper;
 import com.android.documentsui.Shared;
@@ -43,8 +44,10 @@
     final TextView mTitle;
     final TextView mDate;
     final TextView mSize;
-    final ImageView mIconMime;
+    final ImageView mIconMimeLg;
+    final ImageView mIconMimeSm;
     final ImageView mIconThumb;
+    final ImageView mIconCheck;
     final IconHelper mIconHelper;
 
     public GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
@@ -53,11 +56,23 @@
         mTitle = (TextView) itemView.findViewById(android.R.id.title);
         mDate = (TextView) itemView.findViewById(R.id.date);
         mSize = (TextView) itemView.findViewById(R.id.size);
-        mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
+        mIconMimeLg = (ImageView) itemView.findViewById(R.id.icon_mime_lg);
+        mIconMimeSm = (ImageView) itemView.findViewById(R.id.icon_mime_sm);
         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
+        mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
+
         mIconHelper = iconHelper;
     }
 
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        float checkAlpha = selected ? 1f : 0f;
+
+        mIconCheck.animate().alpha(checkAlpha).start();
+        mIconMimeSm.animate().alpha(1f - checkAlpha).start();
+    }
+
     /**
      * Bind this view to the given document for display.
      * @param cursor Pointing to the item to be bound.
@@ -80,13 +95,15 @@
 
         mIconHelper.stopLoading(mIconThumb);
 
-        mIconMime.animate().cancel();
-        mIconMime.setAlpha(1f);
+        mIconMimeLg.animate().cancel();
+        mIconMimeLg.setAlpha(1f);
         mIconThumb.animate().cancel();
         mIconThumb.setAlpha(0f);
 
+        mIconMimeSm.setImageDrawable(IconUtils.loadMimeIcon(mContext, docMimeType));
+
         final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
-        mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime);
+        mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg);
 
         if (mHideTitles) {
             mTitle.setVisibility(View.GONE);
diff --git a/src/com/android/documentsui/dirlist/IconHelper.java b/src/com/android/documentsui/dirlist/IconHelper.java
index ff70eaf..0314077 100644
--- a/src/com/android/documentsui/dirlist/IconHelper.java
+++ b/src/com/android/documentsui/dirlist/IconHelper.java
@@ -91,7 +91,8 @@
                 thumbSize = mContext.getResources().getDimensionPixelSize(R.dimen.grid_width);
                 break;
             case MODE_LIST:
-                thumbSize = mContext.getResources().getDimensionPixelSize(R.dimen.icon_size);
+                thumbSize = mContext.getResources().getDimensionPixelSize(
+                        R.dimen.list_item_thumbnail_size);
                 break;
             case MODE_UNKNOWN:
             default:
diff --git a/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
index c22e91d..00ea27b 100644
--- a/src/com/android/documentsui/dirlist/ListDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
@@ -44,7 +44,7 @@
     final TextView mSize;
     final ImageView mIconMime;
     final ImageView mIconThumb;
-    final ImageView mIcon1;
+    final ImageView mIconCheck;
     final IconHelper mIconHelper;
 
     public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
@@ -56,11 +56,21 @@
         mSize = (TextView) itemView.findViewById(R.id.size);
         mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
-        mIcon1 = (ImageView) itemView.findViewById(android.R.id.icon1);
+        mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
 
         mIconHelper = iconHelper;
     }
 
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        float checkAlpha = selected ? 1f : 0f;
+
+        mIconCheck.animate().alpha(checkAlpha).start();
+        mIconMime.animate().alpha(1f - checkAlpha).start();
+        mIconThumb.animate().alpha(1f - checkAlpha).start();
+    }
+
     /**
      * Bind this view to the given document for display.
      * @param cursor Pointing to the item to be bound.
@@ -86,7 +96,9 @@
         mIconHelper.stopLoading(mIconThumb);
 
         mIconMime.animate().cancel();
+        mIconMime.setAlpha(1f);
         mIconThumb.animate().cancel();
+        mIconThumb.setAlpha(0f);
 
         final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
         mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime);
@@ -121,6 +133,5 @@
         final float iconAlpha = enabled ? 1f : 0.5f;
         mIconMime.setAlpha(iconAlpha);
         mIconThumb.setAlpha(iconAlpha);
-        mIcon1.setAlpha(iconAlpha);
     }
 }
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index f2bade5..cf21d15 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -187,7 +187,7 @@
                     }
                     break;
                 case SORT_ORDER_LAST_MODIFIED:
-                    longValues[pos] = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
+                    longValues[pos] = getLastModified(mCursor);
                     stringValues[pos] = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
                     break;
                 case SORT_ORDER_SIZE:
@@ -309,11 +309,19 @@
                 } else {
                     final long lhs = pivotValue;
                     final long rhs = sortKey[mid];
-                    // Sort in descending numerical order. This matches legacy behaviour, which yields
-                    // largest or most recent items on top.
+                    // Sort in descending numerical order. This matches legacy behaviour, which
+                    // yields largest or most recent items on top.
                     compare = -Long.compare(lhs, rhs);
                 }
 
+                // If numerical comparison yields a tie, use document ID as a tie breaker.  This
+                // will yield stable results even if incoming items are continually shuffling and
+                // have identical numerical sort keys.  One common example of this scenario is seen
+                // when sorting a set of active downloads by mod time.
+                if (compare == 0) {
+                    compare = pivotId.compareTo(ids.get(mid));
+                }
+
                 if (compare < 0) {
                     right = mid;
                 } else {
@@ -350,6 +358,16 @@
         }
     }
 
+    /**
+     * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
+     * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
+     * when sorting by date.
+     */
+    long getLastModified(Cursor cursor) {
+        long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
+        return (l == -1) ? Long.MAX_VALUE : l;
+    }
+
     @Nullable Cursor getItem(String modelId) {
         Integer pos = mPositions.get(modelId);
         if (pos != null) {
diff --git a/src/com/android/documentsui/dirlist/MultiSelectManager.java b/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 5994df9..d868fb4 100644
--- a/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -945,16 +945,27 @@
             if (vh != null) {
                 vh.itemView.requestFocus();
             } else {
-                // Don't smooth scroll; that taxes the system unnecessarily and makes the scroll
-                // handling logic below more complicated.  See b/24865658.
-                mView.scrollToPosition(pos);
+                mView.smoothScrollToPosition(pos);
                 // Set a one-time listener to request focus when the scroll has completed.
                 mView.addOnScrollListener(
                     new RecyclerView.OnScrollListener() {
                         @Override
-                        public void onScrolled(RecyclerView view, int dx, int dy) {
-                            view.findViewHolderForAdapterPosition(pos).itemView.requestFocus();
-                            view.removeOnScrollListener(this);
+                        public void onScrollStateChanged (RecyclerView view, int newState) {
+                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                                // When scrolling stops, find the item and focus it.
+                                RecyclerView.ViewHolder vh =
+                                        view.findViewHolderForAdapterPosition(pos);
+                                if (vh != null) {
+                                    vh.itemView.requestFocus();
+                                } else {
+                                    // This might happen in weird corner cases, e.g. if the user is
+                                    // scrolling while a delete operation is in progress.  In that
+                                    // case, just don't attempt to focus the missing item.
+                                    Log.w(
+                                        TAG, "Unable to focus position " + pos + " after a scroll");
+                                }
+                                view.removeOnScrollListener(this);
+                            }
                         }
                     });
             }
@@ -1913,37 +1924,41 @@
         // Here we unpack information from the event and pass it to an more
         // easily tested method....basically eliminating the need to synthesize
         // events and views and so on in our tests.
-        int position = findTargetPosition(view, keyCode);
-        if (position == RecyclerView.NO_POSITION) {
+        int endPos = findTargetPosition(view, keyCode);
+        if (endPos == RecyclerView.NO_POSITION) {
             // If there is no valid navigation target, don't handle the keypress.
             return false;
         }
 
-        return attemptChangeFocus(position, event.isShiftPressed());
+        int startPos = mEnvironment.getAdapterPositionForChildView(view);
+
+        return changeFocus(startPos, endPos, event.isShiftPressed());
     }
 
     /**
+     * @param startPosition The current focus position.
      * @param targetPosition The adapter position to focus.
      * @param extendSelection
      */
     @VisibleForTesting
-    boolean attemptChangeFocus(int targetPosition, boolean extendSelection) {
+    boolean changeFocus(int startPosition, int targetPosition, boolean extendSelection) {
         // Focus the new file.
         mEnvironment.focusItem(targetPosition);
 
         if (extendSelection) {
-            if (!hasSelection()) {
-                // If there is no selection, start a selection when the user presses shift-arrow.
-                toggleSelection(targetPosition);
-                setSelectionRangeBegin(targetPosition);
-            } else if (!mSingleSelect) {
-                mRanger.snapSelection(targetPosition);
-                notifySelectionChanged();
-            } else {
+            if (mSingleSelect) {
                 // We're in single select and have an existing selection.
                 // Our best guess as to what the user would expect is to advance the selection.
                 clearSelection();
                 toggleSelection(targetPosition);
+            } else {
+                if (!hasSelection()) {
+                    // No selection - start a selection when the user presses shift-arrow.
+                    toggleSelection(startPosition);
+                    setSelectionRangeBegin(startPosition);
+                }
+                mRanger.snapSelection(targetPosition);
+                notifySelectionChanged();
             }
         }
 
diff --git a/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java b/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
index b4782f0..2485ad9 100644
--- a/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
+++ b/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
@@ -18,10 +18,16 @@
 
 import static com.android.internal.util.Preconditions.checkArgument;
 
+import android.content.Context;
+import android.database.Cursor;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView.AdapterDataObserver;
 import android.util.SparseArray;
 import android.view.ViewGroup;
+import android.widget.Space;
+
+import com.android.documentsui.R;
+import com.android.documentsui.State;
 
 import java.util.List;
 
@@ -76,6 +82,8 @@
     public void onBindViewHolder(DocumentHolder holder, int p, List<Object> payload) {
         if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
             mDelegate.onBindViewHolder(holder, toDelegatePosition(p), payload);
+        } else {
+            ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
         }
     }
 
@@ -83,6 +91,8 @@
     public void onBindViewHolder(DocumentHolder holder, int p) {
         if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
             mDelegate.onBindViewHolder(holder, toDelegatePosition(p));
+        } else {
+            ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
         }
     }
 
@@ -106,7 +116,11 @@
         List<String> modelIds = mDelegate.getModelIds();
         for (int i = 0; i < modelIds.size(); i++) {
             if (!isDirectory(model, i)) {
-                mBreakPosition = i;
+                // If the break is the first thing in the list, then there are actually no
+                // directories. In that case, don't insert a break at all.
+                if (i > 0) {
+                    mBreakPosition = i;
+                }
                 break;
             }
         }
@@ -214,4 +228,33 @@
             throw new UnsupportedOperationException();
         }
     }
+
+    /**
+     * The most elegant transparent blank box that spans N rows ever conceived.
+     */
+    private static final class EmptyDocumentHolder extends DocumentHolder {
+        final int mVisibleHeight;
+
+        public EmptyDocumentHolder(Context context) {
+            super(context, new Space(context));
+
+            // Per UX spec, this puts a bigger gap between the folders and documents in the grid.
+            mVisibleHeight = context.getResources().getDimensionPixelSize(
+                    R.dimen.grid_item_margin);
+        }
+
+        public void bind(State state) {
+            bind(null, null, state);
+        }
+
+        @Override
+        public void bind(Cursor cursor, String modelId, State state) {
+            if (state.derivedMode == State.MODE_GRID) {
+                itemView.setMinimumHeight(mVisibleHeight);
+            } else {
+                itemView.setMinimumHeight(0);
+            }
+            return;
+        }
+    }
 }
diff --git a/src/com/android/documentsui/model/DocumentInfo.java b/src/com/android/documentsui/model/DocumentInfo.java
index 215c6e6..83df18c 100644
--- a/src/com/android/documentsui/model/DocumentInfo.java
+++ b/src/com/android/documentsui/model/DocumentInfo.java
@@ -255,10 +255,6 @@
         return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
     }
 
-    public boolean isTypedDocument() {
-        return (flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) != 0;
-    }
-
     public int hashCode() {
         return derivedUri.hashCode() + mimeType.hashCode();
     }
diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java
new file mode 100644
index 0000000..8f89b4e
--- /dev/null
+++ b/src/com/android/documentsui/services/CopyJob.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.services;
+
+import static android.os.SystemClock.elapsedRealtime;
+import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow;
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.android.documentsui.R;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+
+import libcore.io.IoUtils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.List;
+
+class CopyJob extends Job {
+    private static final String TAG = "CopyJob";
+    private static final int PROGRESS_INTERVAL_MILLIS = 1000;
+    final List<DocumentInfo> mSrcFiles;
+
+    // Provider clients are acquired for the duration of each copy job. Note that there is an
+    // implicit assumption that all srcs come from the same authority.
+    ContentProviderClient srcClient;
+    ContentProviderClient dstClient;
+
+    private long mStartTime = -1;
+    private long mBatchSize;
+    private long mBytesCopied;
+    private long mLastNotificationTime;
+    // Speed estimation
+    private long mBytesCopiedSample;
+    private long mSampleTime;
+    private long mSpeed;
+    private long mRemainingTime;
+
+    /**
+     * Copies files to a destination identified by {@code destination}.
+     * @see @link {@link Job} constructor for most param descriptions.
+     *
+     * @param srcs List of files to be copied.
+     */
+    CopyJob(Context serviceContext, Context appContext, Listener listener,
+            String id, DocumentStack destination, List<DocumentInfo> srcs) {
+        super(OPERATION_COPY, serviceContext, appContext, listener, id, destination);
+
+        checkArgument(!srcs.isEmpty());
+        this.mSrcFiles = srcs;
+    }
+
+    @Override
+    Builder createProgressBuilder() {
+        return super.createProgressBuilder(
+                serviceContext.getString(R.string.copy_notification_title),
+                R.drawable.ic_menu_copy,
+                serviceContext.getString(android.R.string.cancel),
+                R.drawable.ic_cab_cancel);
+    }
+
+    @Override
+    public Notification getSetupNotification() {
+        return getSetupNotification(serviceContext.getString(R.string.copy_preparing));
+    }
+
+    public boolean shouldUpdateProgress() {
+        // Wait a while between updates :)
+        return elapsedRealtime() - mLastNotificationTime > PROGRESS_INTERVAL_MILLIS;
+    }
+
+    Notification getProgressNotification(@StringRes int msgId) {
+        double completed = (double) this.mBytesCopied / mBatchSize;
+        mProgressBuilder.setProgress(100, (int) (completed * 100), false);
+        mProgressBuilder.setContentInfo(
+                NumberFormat.getPercentInstance().format(completed));
+        if (mRemainingTime > 0) {
+            mProgressBuilder.setContentText(serviceContext.getString(msgId,
+                    DateUtils.formatDuration(mRemainingTime)));
+        } else {
+            mProgressBuilder.setContentText(null);
+        }
+
+        // Remember when we last returned progress so we can provide an answer
+        // in shouldUpdateProgress.
+        mLastNotificationTime = elapsedRealtime();
+        return mProgressBuilder.build();
+    }
+
+    public Notification getProgressNotification() {
+        return getProgressNotification(R.string.copy_remaining);
+    }
+
+    void onBytesCopied(long numBytes) {
+        this.mBytesCopied += numBytes;
+    }
+
+    /**
+     * Generates an estimate of the remaining time in the copy.
+     */
+    void updateRemainingTimeEstimate() {
+        long elapsedTime = elapsedRealtime() - mStartTime;
+
+        final long sampleDuration = elapsedTime - mSampleTime;
+        final long sampleSpeed = ((mBytesCopied - mBytesCopiedSample) * 1000) / sampleDuration;
+        if (mSpeed == 0) {
+            mSpeed = sampleSpeed;
+        } else {
+            mSpeed = ((3 * mSpeed) + sampleSpeed) / 4;
+        }
+
+        if (mSampleTime > 0 && mSpeed > 0) {
+            mRemainingTime = ((mBatchSize - mBytesCopied) * 1000) / mSpeed;
+        } else {
+            mRemainingTime = 0;
+        }
+
+        mSampleTime = elapsedTime;
+        mBytesCopiedSample = mBytesCopied;
+    }
+
+    @Override
+    Notification getFailureNotification() {
+        return getFailureNotification(
+                R.plurals.copy_error_notification_title, R.drawable.ic_menu_copy);
+    }
+
+    @Override
+    void run(FileOperationService service) throws RemoteException {
+        mStartTime = elapsedRealtime();
+
+        // Acquire content providers.
+        srcClient = acquireUnstableProviderOrThrow(
+                getContentResolver(),
+                mSrcFiles.get(0).authority);
+        dstClient = acquireUnstableProviderOrThrow(
+                getContentResolver(),
+                stack.peek().authority);
+
+        // client
+        mBatchSize = calculateSize(srcClient, mSrcFiles);
+
+        DocumentInfo srcInfo;
+        DocumentInfo dstInfo;
+        for (int i = 0; i < mSrcFiles.size() && !isCanceled(); ++i) {
+            srcInfo = mSrcFiles.get(i);
+            dstInfo = stack.peek();
+
+            // Guard unsupported recursive operation.
+            if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
+                if (DEBUG) Log.d(TAG, "Skipping recursive operation on directory "
+                        + dstInfo.derivedUri);
+                onFileFailed(srcInfo);
+                continue;
+            }
+
+            if (DEBUG) Log.d(TAG,
+                    "Performing op-type:" + type() + " of " + srcInfo.displayName
+                    + " (" + srcInfo.derivedUri + ")" + " to " + dstInfo.displayName
+                    + " (" + dstInfo.derivedUri + ")");
+
+            processDocument(srcInfo, dstInfo);
+        }
+    }
+
+    /**
+     * Logs progress on the current copy operation. Displays/Updates the progress notification.
+     *
+     * @param bytesCopied
+     */
+    private void makeCopyProgress(long bytesCopied) {
+        onBytesCopied(bytesCopied);
+        if (shouldUpdateProgress()) {
+            updateRemainingTimeEstimate();
+            listener.onProgress(this);
+        }
+    }
+
+    /**
+     * Copies a the given document to the given location.
+     *
+     * @param srcInfo DocumentInfos for the documents to copy.
+     * @param dstDirInfo The destination directory.
+     * @param mode The transfer mode (copy or move).
+     * @return True on success, false on failure.
+     * @throws RemoteException
+     */
+    boolean processDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException {
+
+        // TODO: When optimized copy kicks in, we'll not making any progress updates.
+        // For now. Local storage isn't using optimized copy.
+
+        // When copying within the same provider, try to use optimized copying and moving.
+        // If not supported, then fallback to byte-by-byte copy/move.
+        if (srcInfo.authority.equals(dstDirInfo.authority)) {
+            if ((srcInfo.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
+                if (DocumentsContract.copyDocument(srcClient, srcInfo.derivedUri,
+                        dstDirInfo.derivedUri) == null) {
+                    onFileFailed(srcInfo);
+                }
+                return false;
+            }
+        }
+
+        // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
+        return byteCopyDocument(srcInfo, dstDirInfo);
+    }
+
+    boolean byteCopyDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo)
+            throws RemoteException {
+        final String dstMimeType;
+        final String dstDisplayName;
+
+        // If the file is virtual, but can be converted to another format, then try to copy it
+        // as such format. Also, append an extension for the target mime type (if known).
+        if (srcInfo.isVirtualDocument()) {
+            final String[] streamTypes = getContentResolver().getStreamTypes(
+                    srcInfo.derivedUri, "*/*");
+            if (streamTypes != null && streamTypes.length > 0) {
+                dstMimeType = streamTypes[0];
+                final String extension = MimeTypeMap.getSingleton().
+                        getExtensionFromMimeType(dstMimeType);
+                dstDisplayName = srcInfo.displayName +
+                        (extension != null ? "." + extension : srcInfo.displayName);
+            } else {
+                // The virtual file is not available as any alternative streamable format.
+                // TODO: Log failures.
+                onFileFailed(srcInfo);
+                return false;
+            }
+        } else {
+            dstMimeType = srcInfo.mimeType;
+            dstDisplayName = srcInfo.displayName;
+        }
+
+        // Create the target document (either a file or a directory), then copy recursively the
+        // contents (bytes or children).
+        final Uri dstUri = DocumentsContract.createDocument(dstClient,
+                dstDirInfo.derivedUri, dstMimeType, dstDisplayName);
+        if (dstUri == null) {
+            // If this is a directory, the entire subdir will not be copied over.
+            onFileFailed(srcInfo);
+            return false;
+        }
+
+        DocumentInfo dstInfo = null;
+        try {
+            dstInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
+        } catch (FileNotFoundException e) {
+            onFileFailed(srcInfo);
+            return false;
+        }
+
+        final boolean success;
+        if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+            success = copyDirectoryHelper(srcInfo, dstInfo);
+        } else {
+            success = copyFileHelper(srcInfo, dstInfo, dstMimeType);
+        }
+
+        return success;
+    }
+
+    /**
+     * Handles recursion into a directory and copying its contents. Note that in linux terms, this
+     * does the equivalent of "cp src/* dst", not "cp -r src dst".
+     *
+     * @param srcDirInfo Info of the directory to copy from. The routine will copy the directory's
+     *            contents, not the directory itself.
+     * @param dstDirInfo Info of the directory to copy to. Must be created beforehand.
+     * @return True on success, false if some of the children failed to copy.
+     * @throws RemoteException
+     */
+    private boolean copyDirectoryHelper(DocumentInfo srcDirInfo, DocumentInfo dstDirInfo)
+            throws RemoteException {
+        // Recurse into directories. Copy children into the new subdirectory.
+        final String queryColumns[] = new String[] {
+                Document.COLUMN_DISPLAY_NAME,
+                Document.COLUMN_DOCUMENT_ID,
+                Document.COLUMN_MIME_TYPE,
+                Document.COLUMN_SIZE,
+                Document.COLUMN_FLAGS
+        };
+        Cursor cursor = null;
+        boolean success = true;
+        try {
+            // Iterate over srcs in the directory; copy to the destination directory.
+            final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirInfo.authority,
+                    srcDirInfo.documentId);
+            cursor = srcClient.query(queryUri, queryColumns, null, null, null);
+            DocumentInfo srcInfo;
+            while (cursor.moveToNext()) {
+                srcInfo = DocumentInfo.fromCursor(cursor, srcDirInfo.authority);
+                success &= processDocument(srcInfo, dstDirInfo);
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        return success;
+    }
+
+    /**
+     * Handles copying a single file.
+     *
+     * @param srcUriInfo Info of the file to copy from.
+     * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand.
+     * @param mimeType Mime type for the target. Can be different than source for virtual files.
+     * @return True on success, false on error.
+     * @throws RemoteException
+     */
+    private boolean copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, String mimeType)
+            throws RemoteException {
+        // Copy an individual file.
+        CancellationSignal canceller = new CancellationSignal();
+        ParcelFileDescriptor srcFile = null;
+        ParcelFileDescriptor dstFile = null;
+        InputStream src = null;
+        OutputStream dst = null;
+
+        boolean success = true;
+        try {
+            // If the file is virtual, but can be converted to another format, then try to copy it
+            // as such format.
+            if (srcInfo.isVirtualDocument()) {
+                final AssetFileDescriptor srcFileAsAsset =
+                        srcClient.openTypedAssetFileDescriptor(
+                                srcInfo.derivedUri, mimeType, null, canceller);
+                srcFile = srcFileAsAsset.getParcelFileDescriptor();
+                src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
+            } else {
+                srcFile = srcClient.openFile(srcInfo.derivedUri, "r", canceller);
+                src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
+            }
+
+            dstFile = dstClient.openFile(dstInfo.derivedUri, "w", canceller);
+            dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
+
+            byte[] buffer = new byte[8192];
+            int len;
+            while ((len = src.read(buffer)) != -1) {
+                if (isCanceled()) {
+                    if (DEBUG) Log.d(TAG, "Canceled copy mid-copy. Id:" + id);
+                    success = false;
+                    break;
+                }
+                dst.write(buffer, 0, len);
+                makeCopyProgress(len);
+            }
+
+            srcFile.checkError();
+        } catch (IOException e) {
+            success = false;
+            onFileFailed(srcInfo);
+
+            if (dstFile != null) {
+                try {
+                    dstFile.closeWithError(e.getMessage());
+                } catch (IOException closeError) {
+                    Log.e(TAG, "Error closing destination", closeError);
+                }
+            }
+        } finally {
+            // This also ensures the file descriptors are closed.
+            IoUtils.closeQuietly(src);
+            IoUtils.closeQuietly(dst);
+        }
+
+        if (!success) {
+            // Clean up half-copied files.
+            canceller.cancel();
+            try {
+                DocumentsContract.deleteDocument(dstClient, dstInfo.derivedUri);
+            } catch (RemoteException e) {
+                // RemoteExceptions usually signal that the connection is dead, so there's no
+                // point attempting to continue. Propagate the exception up so the copy job is
+                // cancelled.
+                Log.w(TAG, "Failed to cleanup after copy error: " + srcInfo.derivedUri, e);
+                throw e;
+            }
+        }
+
+        return success;
+    }
+
+    /**
+     * Calculates the cumulative size of all the documents in the list. Directories are recursed
+     * into and totaled up.
+     *
+     * @param srcs
+     * @return Size in bytes.
+     * @throws RemoteException
+     */
+    private static long calculateSize(ContentProviderClient client, List<DocumentInfo> srcs)
+            throws RemoteException {
+        long result = 0;
+
+        for (DocumentInfo src : srcs) {
+            if (src.isDirectory()) {
+                // Directories need to be recursed into.
+                result += calculateFileSizesRecursively(client, src.derivedUri);
+            } else {
+                result += src.size;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Calculates (recursively) the cumulative size of all the files under the given directory.
+     *
+     * @throws RemoteException
+     */
+    private static long calculateFileSizesRecursively(
+            ContentProviderClient client, Uri uri) throws RemoteException {
+        final String authority = uri.getAuthority();
+        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority,
+                DocumentsContract.getDocumentId(uri));
+        final String queryColumns[] = new String[] {
+                Document.COLUMN_DOCUMENT_ID,
+                Document.COLUMN_MIME_TYPE,
+                Document.COLUMN_SIZE
+        };
+
+        long result = 0;
+        Cursor cursor = null;
+        try {
+            cursor = client.query(queryUri, queryColumns, null, null, null);
+            while (cursor.moveToNext()) {
+                if (Document.MIME_TYPE_DIR.equals(
+                        getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
+                    // Recurse into directories.
+                    final Uri dirUri = DocumentsContract.buildDocumentUri(authority,
+                            getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+                    result += calculateFileSizesRecursively(client, dirUri);
+                } else {
+                    // This may return -1 if the size isn't defined. Ignore those cases.
+                    long size = getCursorLong(cursor, Document.COLUMN_SIZE);
+                    result += size > 0 ? size : 0;
+                }
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        return result;
+    }
+
+    @Override
+    void cleanup() {
+        ContentProviderClient.releaseQuietly(srcClient);
+        ContentProviderClient.releaseQuietly(dstClient);
+    }
+
+    /**
+     * Returns true if {@code doc} is a descendant of {@code parentDoc}.
+     * @throws RemoteException
+     */
+    boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc)
+            throws RemoteException {
+        if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
+            return DocumentsContract.isChildDocument(
+                    dstClient, doc.derivedUri, parentDoc.derivedUri);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/services/FileOperationService.java b/src/com/android/documentsui/services/FileOperationService.java
new file mode 100644
index 0000000..6d87ecf
--- /dev/null
+++ b/src/com/android/documentsui/services/FileOperationService.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.services;
+
+import static android.os.SystemClock.elapsedRealtime;
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.IntDef;
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.PowerManager;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.documentsui.Shared;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+
+import com.google.common.base.Objects;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileOperationService extends IntentService implements Job.Listener {
+    public static final String TAG = "FileOperationService";
+
+    public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
+    public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION";
+    public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
+    public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
+    public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
+
+    public static final int OPERATION_UNKNOWN = -1;
+    public static final int OPERATION_COPY = 1;
+    public static final int OPERATION_MOVE = 2;
+    public static final int OPERATION_DELETE = 3;
+
+    @IntDef(flag = true, value = {
+            OPERATION_UNKNOWN,
+            OPERATION_COPY,
+            OPERATION_MOVE,
+            OPERATION_DELETE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OpType {}
+
+    // TODO: Move it to a shared file when more operations are implemented.
+    public static final int FAILURE_COPY = 1;
+
+    private PowerManager mPowerManager;
+
+    private NotificationManager mNotificationManager;
+
+    // TODO: Rework service to support multiple concurrent jobs.
+    private volatile Job mJob;
+
+    // For testing only.
+    @Nullable private TestOnlyListener mJobFinishedListener;
+
+    public FileOperationService() {
+        super("FileOperationService");
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        if (DEBUG) Log.d(TAG, "Created.");
+        mPowerManager = getSystemService(PowerManager.class);
+        mNotificationManager = getSystemService(NotificationManager.class);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (DEBUG) Log.d(TAG, "onStartCommand: " + intent);
+        if (intent.hasExtra(EXTRA_CANCEL)) {
+            handleCancel(intent);
+            return START_REDELIVER_INTENT;
+        } else {
+            return super.onStartCommand(intent, flags, startId);
+        }
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        if (DEBUG) Log.d(TAG, "onHandleIntent: " + intent);
+
+        String jobId = intent.getStringExtra(EXTRA_JOB_ID);
+        @OpType int operationType = intent.getIntExtra(EXTRA_OPERATION, OPERATION_UNKNOWN);
+        checkArgument(jobId != null);
+        if (intent.hasExtra(EXTRA_CANCEL)) {
+            handleCancel(intent);
+            return;
+        }
+
+        checkArgument(operationType != OPERATION_UNKNOWN);
+
+        PowerManager.WakeLock wakeLock = mPowerManager.newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
+        DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
+
+        Job job = createJob(operationType, jobId, srcs, stack);
+
+        try {
+            wakeLock.acquire();
+
+            mNotificationManager.notify(job.id, 0, job.getSetupNotification());
+            job.run(this);
+
+        } catch (Exception e) {
+            // Catch-all to prevent any copy errors from wedging the app.
+            Log.e(TAG, "Exceptions occurred during copying", e);
+        } finally {
+            if (DEBUG) Log.d(TAG, "Cleaning up after copy");
+
+            job.cleanup();
+            wakeLock.release();
+
+            // Dismiss the ongoing copy notification when the copy is done.
+            mNotificationManager.cancel(job.id, 0);
+
+            if (job.failed()) {
+                Log.e(TAG, job.failedFiles.size() + " files failed to copy");
+                mNotificationManager.notify(job.id, 0, job.getFailureNotification());
+            }
+
+            // TEST ONLY CODE...<raised eyebrows>
+            if (mJobFinishedListener != null) {
+                mJobFinishedListener.onFinished(job.failedFiles);
+            }
+
+            deleteJob(job);
+            if (DEBUG) Log.d(TAG, "Done cleaning up");
+        }
+    }
+
+    /**
+     * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID".
+     *
+     * @param intent The cancellation intent.
+     */
+    private void handleCancel(Intent intent) {
+        checkArgument(intent.hasExtra(EXTRA_CANCEL));
+        String jobId = checkNotNull(intent.getStringExtra(EXTRA_JOB_ID));
+
+        // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
+        // cancellation requests from affecting unrelated copy jobs.  However, if the current job ID
+        // is null, the service most likely crashed and was revived by the incoming cancel intent.
+        // In that case, always allow the cancellation to proceed.
+        if (mJob != null && Objects.equal(jobId, mJob.id)) {
+            mJob.cancel();
+        }
+
+        // Dismiss the progress notification here rather than in the copy loop. This preserves
+        // interactivity for the user in case the copy loop is stalled.
+        // Try to cancel it even if we don't have a job id...in case there is some sad
+        // orphan notification.
+        mNotificationManager.cancel(jobId, 0);
+    }
+
+    public static String createJobId() {
+        return String.valueOf(elapsedRealtime());
+    }
+
+    Job createJob(
+            @OpType int operationType, String id, ArrayList<DocumentInfo> srcs,
+            DocumentStack stack) {
+
+        checkState(mJob == null);
+
+        switch (operationType) {
+            case OPERATION_COPY:
+                mJob = new CopyJob(this, getApplicationContext(), this, id, stack, srcs);
+                break;
+            case OPERATION_MOVE:
+                mJob = new MoveJob(this, getApplicationContext(), this, id, stack, srcs);
+                break;
+            case OPERATION_DELETE:
+                throw new UnsupportedOperationException();
+            default:
+                throw new UnsupportedOperationException();
+        }
+
+        return checkNotNull(mJob);
+    }
+
+    void deleteJob(Job job) {
+        checkArgument(job == mJob);
+        mJob = null;
+    }
+
+    @Override
+    public void onProgress(CopyJob job) {
+        if (DEBUG) Log.d(TAG, "On copy progress...");
+        mNotificationManager.notify(job.id, 0, job.getProgressNotification());
+    }
+
+    @Override
+    public void onProgress(MoveJob job) {
+        if (DEBUG) Log.d(TAG, "On move progress...");
+        mNotificationManager.notify(job.id, 0, job.getProgressNotification());
+    }
+
+    /**
+     * Sets a callback to be run when the next run job is finished.
+     * This is test ONLY instrumentation. The alternative is for us to add
+     * broadcast intents SOLELY for the purpose of testing.
+     * @param listener
+     */
+    @VisibleForTesting
+    void addFinishedListener(TestOnlyListener listener) {
+        this.mJobFinishedListener = listener;
+    }
+
+    /**
+     * Only used for testing. Is that obvious enough?
+     */
+    @VisibleForTesting
+    interface TestOnlyListener {
+        void onFinished(List<DocumentInfo> failed);
+    }
+}
diff --git a/src/com/android/documentsui/services/FileOperations.java b/src/com/android/documentsui/services/FileOperations.java
new file mode 100644
index 0000000..88bf03b
--- /dev/null
+++ b/src/com/android/documentsui/services/FileOperations.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.services;
+
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.Shared.EXTRA_STACK;
+import static com.android.documentsui.Shared.asArrayList;
+import static com.android.documentsui.Shared.getQuantityString;
+import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
+import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
+import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
+import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Parcelable;
+import android.support.design.widget.Snackbar;
+import android.util.Log;
+
+import com.android.documentsui.R;
+import com.android.documentsui.Snackbars;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperationService.OpType;
+
+import java.util.List;
+
+/**
+ * Helper functions for starting various file operations.
+ */
+public final class FileOperations {
+
+    private static final String TAG = "FileOperations";
+
+    private FileOperations() {}
+
+    /**
+     * Tries to start the activity. Returns the job id.
+     */
+    public static String start(
+            Activity activity, List<DocumentInfo> srcDocs, DocumentStack stack,
+            int operationType) {
+
+        if (DEBUG) Log.d(TAG, "Handling generic 'start' call.");
+
+        switch (operationType) {
+            case OPERATION_COPY:
+                return FileOperations.copy(activity, srcDocs, stack);
+            case OPERATION_MOVE:
+                return FileOperations.move(activity, srcDocs, stack);
+            case OPERATION_DELETE:
+                return FileOperations.delete(activity, srcDocs, stack);
+            default:
+                throw new UnsupportedOperationException("Unknown operation: " + operationType);
+        }
+    }
+
+    /**
+     * Makes a best effort to cancel operation identified by jobId.
+     *
+     * @param context Context for the intent.
+     * @param jobId The id of the job to cancel.
+     *     Use {@link FileOperationService#createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @param dstStack The copy destination stack.
+     */
+    public static void cancel(Activity activity, String jobId) {
+        if (DEBUG) Log.d(TAG, "Attempting to canceling operation: " + jobId);
+
+        Intent intent = new Intent(activity, FileOperationService.class);
+        intent.putExtra(EXTRA_CANCEL, true);
+        intent.putExtra(EXTRA_JOB_ID, jobId);
+
+        activity.startService(intent);
+    }
+
+    /**
+     * Starts the service for a copy operation.
+     *
+     * @param context Context for the intent.
+     * @param jobId A unique jobid for this job.
+     *     Use {@link FileOperationService#createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @param destination The copy destination stack.
+     */
+    public static String copy(
+            Activity activity, List<DocumentInfo> srcDocs, DocumentStack destination) {
+        String jobId = FileOperationService.createJobId();
+        if (DEBUG) Log.d(TAG, "Initiating 'copy' operation id: " + jobId);
+
+        Intent intent = createBaseIntent(OPERATION_COPY, activity, jobId, srcDocs, destination);
+
+        createSharedSnackBar(activity, R.plurals.copy_begin, srcDocs.size())
+                .show();
+
+        activity.startService(intent);
+
+        return jobId;
+    }
+
+    /**
+     * Starts the service for a move operation.
+     *
+     * @param jobId A unique jobid for this job.
+     *     Use {@link FileOperationService#createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @param destination The move destination stack.
+     */
+    public static String move(
+            Activity activity, List<DocumentInfo> srcDocs, DocumentStack destination) {
+        String jobId = FileOperationService.createJobId();
+        if (DEBUG) Log.d(TAG, "Initiating 'move' operation id: " + jobId);
+
+        Intent intent = createBaseIntent(OPERATION_MOVE, activity, jobId, srcDocs, destination);
+
+        createSharedSnackBar(activity, R.plurals.move_begin, srcDocs.size())
+                .show();
+
+        activity.startService(intent);
+
+        return jobId;
+    }
+
+    /**
+     * Starts the service for a move operation.
+     *
+     * @param jobId A unique jobid for this job.
+     *     Use {@link FileOperationService#createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @return Id of the job.
+     */
+    public static String delete(
+            Activity activity, List<DocumentInfo> srcDocs, DocumentStack location) {
+        String jobId = FileOperationService.createJobId();
+        if (DEBUG) Log.d(TAG, "Initiating 'delete' operation id: " + jobId);
+
+        Intent intent = createBaseIntent(OPERATION_DELETE, activity, jobId, srcDocs, location);
+        activity.startService(intent);
+
+        return jobId;
+    }
+
+    /**
+     * Starts the service for a move operation.
+     *
+     * @param jobId A unique jobid for this job.
+     *     Use {@link FileOperationService#createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @return Id of the job.
+     */
+    public static Intent createBaseIntent(
+            @OpType int operationType, Activity activity, String jobId,
+            List<DocumentInfo> srcDocs, DocumentStack localeStack) {
+
+        Intent intent = new Intent(activity, FileOperationService.class);
+        intent.putExtra(EXTRA_JOB_ID, jobId);
+        intent.putParcelableArrayListExtra(
+                EXTRA_SRC_LIST, asArrayList(srcDocs));
+        intent.putExtra(EXTRA_STACK, (Parcelable) localeStack);
+        intent.putExtra(EXTRA_OPERATION, operationType);
+
+        return intent;
+    }
+
+    private static Snackbar createSharedSnackBar(Activity activity, int contentId, int fileCount) {
+        Resources res = activity.getResources();
+        return Snackbars.makeSnackbar(
+                activity,
+                getQuantityString(activity, contentId, fileCount),
+                Snackbar.LENGTH_SHORT);
+    }
+}
diff --git a/src/com/android/documentsui/services/Job.java b/src/com/android/documentsui/services/Job.java
new file mode 100644
index 0000000..5c37a87
--- /dev/null
+++ b/src/com/android/documentsui/services/Job.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.services;
+
+import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.DrawableRes;
+import android.annotation.PluralsRes;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+
+import com.android.documentsui.FilesActivity;
+import com.android.documentsui.R;
+import com.android.documentsui.Shared;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperationService.OpType;
+
+import java.util.ArrayList;
+
+abstract class Job {
+
+    final Context serviceContext;
+    final Context appContext;
+    final Listener listener;
+
+    final @OpType int mOpType;
+    final String id;
+    final DocumentStack stack;
+
+    final ArrayList<DocumentInfo> failedFiles = new ArrayList<>();
+    final Notification.Builder mProgressBuilder;
+
+    private volatile boolean mCanceled;
+
+    /**
+     * A simple progressable job, much like an AsyncTask, but with support
+     * for providing various related notification, progress and navigation information.
+     * @param opType
+     *
+     * @param serviceContext The context of the service in which this job is running.
+     *     This is usually just "this".
+     * @param appContext The context of the invoking application. This is usually
+     *     just {@code getApplicationContext()}.
+     * @param listener
+     * @param id Arbitrary string ID
+     * @param stack The documents stack context relating to this request. This is the
+     *     destination in the Files app where the user will be take when the
+     *     navigation intent is invoked (presumably from notification).
+     */
+    Job(@OpType int opType, Context serviceContext, Context appContext, Listener listener,
+            String id, DocumentStack stack) {
+
+        checkArgument(opType != OPERATION_UNKNOWN);
+        this.serviceContext = serviceContext;
+        this.appContext = appContext;
+        this.listener = listener;
+        mOpType = opType;
+
+        this.id = id;
+        this.stack = stack;
+
+        mProgressBuilder = createProgressBuilder();
+    }
+
+    abstract void run(FileOperationService service) throws RemoteException;
+    abstract void cleanup();
+
+    @OpType int type() {
+        return mOpType;
+    }
+
+    abstract Notification getSetupNotification();
+    // TODO: Progress notification for deletes.
+    // abstract Notification getProgressNotification(long bytesCopied);
+    abstract Notification getFailureNotification();
+
+    final void cancel() {
+        mCanceled = true;
+    }
+
+    final boolean isCanceled() {
+        return mCanceled;
+    }
+
+    final ContentResolver getContentResolver() {
+        return serviceContext.getContentResolver();
+    }
+
+    void onFileFailed(DocumentInfo file) {
+        failedFiles.add(file);
+    }
+
+    final boolean failed() {
+        return !failedFiles.isEmpty();
+    }
+
+    Notification getSetupNotification(String content) {
+        mProgressBuilder.setProgress(0, 0, true);
+        mProgressBuilder.setContentText(content);
+        return mProgressBuilder.build();
+    }
+
+    Notification getFailureNotification(@PluralsRes int titleId, @DrawableRes int icon) {
+        final Intent navigateIntent = buildNavigateIntent();
+        navigateIntent.putExtra(FileOperationService.EXTRA_FAILURE, FileOperationService.FAILURE_COPY);
+        navigateIntent.putExtra(FileOperationService.EXTRA_OPERATION, mOpType);
+
+        navigateIntent.putParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST, failedFiles);
+
+        final Notification.Builder errorBuilder = new Notification.Builder(serviceContext)
+                .setContentTitle(serviceContext.getResources().getQuantityString(titleId,
+                        failedFiles.size(), failedFiles.size()))
+                .setContentText(serviceContext.getString(R.string.notification_touch_for_details))
+                .setContentIntent(PendingIntent.getActivity(appContext, 0, navigateIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT))
+                .setCategory(Notification.CATEGORY_ERROR)
+                .setSmallIcon(icon)
+                .setAutoCancel(true);
+        return errorBuilder.build();
+    }
+
+    abstract Builder createProgressBuilder();
+
+    final Builder createProgressBuilder(
+            String title, @DrawableRes int icon,
+            String actionTitle, @DrawableRes int actionIcon) {
+        Notification.Builder progressBuilder = new Notification.Builder(serviceContext)
+                .setContentTitle(title)
+                .setContentIntent(
+                        PendingIntent.getActivity(appContext, 0, buildNavigateIntent(), 0))
+                .setCategory(Notification.CATEGORY_PROGRESS)
+                .setSmallIcon(icon)
+                .setOngoing(true);
+
+        final Intent cancelIntent = createCancelIntent();
+
+        progressBuilder.addAction(
+                actionIcon,
+                actionTitle,
+                PendingIntent.getService(
+                        serviceContext,
+                        0,
+                        cancelIntent,
+                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT));
+
+        return progressBuilder;
+    }
+
+    /**
+     * Creates an intent for navigating back to the destination directory.
+     */
+    Intent buildNavigateIntent() {
+        Intent intent = new Intent(serviceContext, FilesActivity.class);
+        intent.setAction(DocumentsContract.ACTION_BROWSE);
+        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+        return intent;
+    }
+
+    Intent createCancelIntent() {
+        final Intent cancelIntent = new Intent(serviceContext, FileOperationService.class);
+        cancelIntent.putExtra(FileOperationService.EXTRA_CANCEL, true);
+        cancelIntent.putExtra(FileOperationService.EXTRA_JOB_ID, id);
+        return cancelIntent;
+    }
+
+    interface Listener {
+        void onProgress(CopyJob job);
+        void onProgress(MoveJob job);
+    }
+}
diff --git a/src/com/android/documentsui/services/MoveJob.java b/src/com/android/documentsui/services/MoveJob.java
new file mode 100644
index 0000000..4817f58
--- /dev/null
+++ b/src/com/android/documentsui/services/MoveJob.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.services;
+
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.content.Context;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import com.android.documentsui.R;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+
+import java.util.List;
+
+final class MoveJob extends CopyJob {
+
+    private static final String TAG = "MoveJob";
+
+    /**
+     * Moves files to a destination identified by {@code destination}.
+     * Performs most work by delegating to CopyJob, then deleting
+     * a file after it has been copied.
+     *
+     * @see @link {@link Job} constructor for most param descriptions.
+     *
+     * @param srcs List of files to be moved.
+     */
+    MoveJob(Context serviceContext, Context appContext, Listener listener,
+            String id, DocumentStack destination, List<DocumentInfo> srcs) {
+        super(serviceContext, appContext, listener, id, destination, srcs);
+    }
+
+    @Override
+    int type() {
+        return FileOperationService.OPERATION_MOVE;
+    }
+
+    @Override
+    Builder createProgressBuilder() {
+        return super.createProgressBuilder(
+                serviceContext.getString(R.string.move_notification_title),
+                R.drawable.ic_menu_copy,
+                serviceContext.getString(android.R.string.cancel),
+                R.drawable.ic_cab_cancel);
+    }
+
+    @Override
+    public Notification getSetupNotification() {
+        return getSetupNotification(serviceContext.getString(R.string.move_preparing));
+    }
+
+    @Override
+    public Notification getProgressNotification() {
+        return getProgressNotification(R.string.copy_preparing);
+    }
+
+    @Override
+    Notification getFailureNotification() {
+        return getFailureNotification(
+                R.plurals.move_error_notification_title, R.drawable.ic_menu_copy);
+    }
+
+    /**
+     * Copies a the given document to the given location.
+     *
+     * @param srcInfo DocumentInfos for the documents to copy.
+     * @param dstDirInfo The destination directory.
+     * @param mode The transfer mode (copy or move).
+     * @return True on success, false on failure.
+     * @throws RemoteException
+     */
+    @Override
+    boolean processDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException {
+
+        // TODO: When optimized copy kicks in, we're not making any progress updates. FIX IT!
+
+        // When copying within the same provider, try to use optimized copying and moving.
+        // If not supported, then fallback to byte-by-byte copy/move.
+        if (srcInfo.authority.equals(dstDirInfo.authority)) {
+            if ((srcInfo.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
+                if (DocumentsContract.moveDocument(srcClient, srcInfo.derivedUri,
+                        dstDirInfo.derivedUri) == null) {
+                    onFileFailed(srcInfo);
+                }
+                return false;
+            }
+        }
+
+        // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
+        boolean success = byteCopyDocument(srcInfo, dstDirInfo);
+
+        if (success) {
+            // This is racey. We should make sure that we never delete a directory after
+            // it changed, so we don't remove a file which had not been copied earlier
+            // to the target location.
+            try {
+                DocumentsContract.deleteDocument(srcClient, srcInfo.derivedUri);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to delete source after copy: " + srcInfo.derivedUri, e);
+                return false;
+            }
+        }
+
+        return success;
+    }
+}
diff --git a/tests/src/com/android/documentsui/StateTest.java b/tests/src/com/android/documentsui/StateTest.java
new file mode 100644
index 0000000..b74b985
--- /dev/null
+++ b/tests/src/com/android/documentsui/StateTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.documentsui.model.DocumentInfo;
+
+@SmallTest
+public class StateTest extends AndroidTestCase {
+    public void testPushDocument() {
+        final State state = new State();
+        final DocumentInfo infoFirst = new DocumentInfo();
+        infoFirst.displayName = "firstDirectory";
+        final DocumentInfo infoSecond = new DocumentInfo();
+        infoSecond.displayName = "secondDirectory";
+        assertFalse(state.hasLocationChanged());
+        state.pushDocument(infoFirst);
+        state.pushDocument(infoSecond);
+        assertTrue(state.hasLocationChanged());
+        assertEquals("secondDirectory", state.stack.getFirst().displayName);
+        state.popDocument();
+        assertEquals("firstDirectory", state.stack.getFirst().displayName);
+    }
+}
diff --git a/tests/src/com/android/documentsui/StubProvider.java b/tests/src/com/android/documentsui/StubProvider.java
index 2c311a7..50f4628 100644
--- a/tests/src/com/android/documentsui/StubProvider.java
+++ b/tests/src/com/android/documentsui/StubProvider.java
@@ -316,12 +316,9 @@
             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
         final StubDocument document = mStorage.get(documentId);
-        if (document == null || !document.file.isFile()) {
+        if (document == null || !document.file.isFile() || document.streamTypes == null) {
             throw new FileNotFoundException();
         }
-        if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) {
-            throw new IllegalStateException("Tried to open a non-typed document as typed.");
-        }
         for (final String mimeType : document.streamTypes) {
             // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI
             // doesn't use them for getStreamTypes nor openTypedDocument.
@@ -349,13 +346,13 @@
             throw new IllegalArgumentException(
                     "The provided Uri is incorrect, or the file is gone.");
         }
-        if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) {
-            return null;
-        }
         if (!"*/*".equals(mimeTypeFilter)) {
             // Not used by DocumentsUI, so don't bother implementing it.
             throw new UnsupportedOperationException();
         }
+        if (document.streamTypes == null) {
+            return null;
+        }
         return document.streamTypes.toArray(new String[document.streamTypes.size()]);
     }
 
@@ -628,9 +625,6 @@
                 File file, String mimeType, List<String> streamTypes, StubDocument parent) {
             int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
                     | Document.FLAG_VIRTUAL_DOCUMENT;
-            if (streamTypes.size() > 0) {
-                flags |= Document.FLAG_SUPPORTS_TYPED_DOCUMENT;
-            }
             return new StubDocument(file, mimeType, streamTypes, flags, parent);
         }
 
diff --git a/tests/src/com/android/documentsui/dirlist/ModelTest.java b/tests/src/com/android/documentsui/dirlist/ModelTest.java
index bed7c9c..a5f0656 100644
--- a/tests/src/com/android/documentsui/dirlist/ModelTest.java
+++ b/tests/src/com/android/documentsui/dirlist/ModelTest.java
@@ -34,8 +34,10 @@
 
 import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
@@ -50,6 +52,7 @@
         Document.COLUMN_FLAGS,
         Document.COLUMN_DISPLAY_NAME,
         Document.COLUMN_SIZE,
+        Document.COLUMN_LAST_MODIFIED,
         Document.COLUMN_MIME_TYPE
     };
 
@@ -263,6 +266,43 @@
         assertEquals(ITEM_COUNT, seen.cardinality());
     }
 
+    public void testSort_time() {
+        final int DL_COUNT = 3;
+        MatrixCursor c = new MatrixCursor(COLUMNS);
+        Set<String> currentDownloads = new HashSet<>();
+
+        // Add some files
+        for (int i = 0; i < ITEM_COUNT; i++) {
+            MatrixCursor.RowBuilder row = c.newRow();
+            row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
+            row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
+            row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
+        }
+        // Add some current downloads (no timestamp)
+        for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) {
+            MatrixCursor.RowBuilder row = c.newRow();
+            String id = Integer.toString(i);
+            row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
+            row.add(Document.COLUMN_DOCUMENT_ID, id);
+            currentDownloads.add(Model.createModelId(AUTHORITY, id));
+        }
+
+        DirectoryResult r = new DirectoryResult();
+        r.cursor = c;
+        r.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
+        model.update(r);
+
+        List<String> ids = model.getModelIds();
+
+        // Check that all items were accounted for
+        assertEquals(ITEM_COUNT + DL_COUNT, ids.size());
+
+        // Check that active downloads are sorted to the top.
+        for (int i = 0; i < DL_COUNT; i++) {
+            assertTrue(currentDownloads.contains(ids.get(i)));
+        }
+    }
+
     // Tests that Model.delete works correctly.
     public void testDelete() throws Exception {
         // Simulate deleting 2 files.
diff --git a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index e06199e..7a3b6d4 100644
--- a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -166,6 +166,12 @@
         assertRangeSelection(0, 7);
     }
 
+    public void testKeyboardSelection() {
+        // This simulates shift-navigation.
+        keyToPosition(5, 10, true);
+        assertRangeSelection(5, 10);
+    }
+
     public void testSingleSelectMode() {
         mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
@@ -186,7 +192,7 @@
         mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(20);
-        keyToPosition(22, true);
+        keyToPosition(20, 22, true);
         assertSelection(items.get(22));
     }
 
@@ -250,8 +256,8 @@
         mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
     }
 
-    private void keyToPosition(int position, boolean shift) {
-        mManager.attemptChangeFocus(position, shift);
+    private void keyToPosition(int startPos, int endPos, boolean shift) {
+        mManager.changeFocus(startPos, endPos, shift);
     }
 
     private void assertSelected(String... expected) {
diff --git a/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java b/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
new file mode 100644
index 0000000..398885c
--- /dev/null
+++ b/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.DocumentsContract.Document;
+import android.support.v7.widget.RecyclerView;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+import android.view.ViewGroup;
+
+import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.RootCursorWrapper;
+import com.android.documentsui.State;
+
+import java.util.List;
+
+@SmallTest
+public class SectionBreakDocumentsAdapterWrapperTest extends AndroidTestCase {
+
+    private static final String AUTHORITY = "test_authority";
+    private static final String[] NAMES = new String[] {
+            "4",
+            "foo",
+            "1",
+            "bar",
+            "*(Ljifl;a",
+            "0",
+            "baz",
+            "2",
+            "3",
+            "%$%VD"
+    };
+
+    private TestModel mModel;
+    private SectionBreakDocumentsAdapterWrapper mAdapter;
+
+    public void setUp() {
+
+        final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY);
+        DocumentsAdapter.Environment env = new TestEnvironment(testContext);
+
+        mModel = new TestModel(testContext, AUTHORITY);
+        mAdapter = new SectionBreakDocumentsAdapterWrapper(
+            env,
+            new ModelBackedDocumentsAdapter(
+                env, new IconHelper(testContext, State.MODE_GRID)));
+
+        mModel.addUpdateListener(mAdapter);
+    }
+
+    // Tests that the item count is correct for a directory containing only subdirs.
+    public void testItemCount_allDirs() {
+        MatrixCursor c = new MatrixCursor(TestModel.COLUMNS);
+
+        for (int i = 0; i < 5; ++i) {
+            MatrixCursor.RowBuilder row = c.newRow();
+            row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
+            row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
+            row.add(Document.COLUMN_SIZE, i);
+            row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+        }
+        DirectoryResult r = new DirectoryResult();
+        r.cursor = c;
+        r.sortOrder = State.SORT_ORDER_SIZE;
+        mModel.update(r);
+
+        assertEquals(mModel.getItemCount(), mAdapter.getItemCount());
+    }
+
+    // Tests that the item count is correct for a directory containing only files.
+    public void testItemCount_allFiles() {
+        mModel.update(NAMES);
+        assertEquals(mModel.getItemCount(), mAdapter.getItemCount());
+    }
+
+    // Tests that the item count is correct for a directory containing files and subdirs.
+    public void testItemCount_mixed() {
+        MatrixCursor c = new MatrixCursor(TestModel.COLUMNS);
+
+        for (int i = 0; i < 5; ++i) {
+            MatrixCursor.RowBuilder row = c.newRow();
+            row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
+            row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
+            row.add(Document.COLUMN_SIZE, i);
+            String mimeType =(i < 2) ? Document.MIME_TYPE_DIR : "text/*";
+            row.add(Document.COLUMN_MIME_TYPE, mimeType);
+        }
+        DirectoryResult r = new DirectoryResult();
+        r.cursor = c;
+        r.sortOrder = State.SORT_ORDER_SIZE;
+        mModel.update(r);
+
+        assertEquals(mModel.getItemCount() + 1, mAdapter.getItemCount());
+    }
+
+    private final class TestEnvironment implements DocumentsAdapter.Environment {
+        private final Context testContext;
+
+        private TestEnvironment(Context testContext) {
+            this.testContext = testContext;
+        }
+
+        @Override
+        public boolean isSelected(String id) {
+            return false;
+        }
+
+        @Override
+        public boolean isDocumentEnabled(String mimeType, int flags) {
+            return true;
+        }
+
+        @Override
+        public void initDocumentHolder(DocumentHolder holder) {}
+
+        @Override
+        public Model getModel() {
+            return mModel;
+        }
+
+        @Override
+        public State getDisplayState() {
+            return null;
+        }
+
+        @Override
+        public Context getContext() {
+            return testContext;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return 4;
+        }
+
+        @Override
+        public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {}
+    }
+
+    private static class DummyListener implements Model.UpdateListener {
+        public void onModelUpdate(Model model) {}
+        public void onModelUpdateFailed(Exception e) {}
+    }
+
+    private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+        public int getItemCount() { return 0; }
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return null;
+        }
+    }
+}
diff --git a/tests/src/com/android/documentsui/dirlist/TestModel.java b/tests/src/com/android/documentsui/dirlist/TestModel.java
index 3a537a6..f9cd3b2 100644
--- a/tests/src/com/android/documentsui/dirlist/TestModel.java
+++ b/tests/src/com/android/documentsui/dirlist/TestModel.java
@@ -29,7 +29,7 @@
 
 public class TestModel extends Model {
 
-    private static final String[] COLUMNS = new String[]{
+    static final String[] COLUMNS = new String[]{
         RootCursorWrapper.COLUMN_AUTHORITY,
         Document.COLUMN_DOCUMENT_ID,
         Document.COLUMN_FLAGS,
diff --git a/tests/src/com/android/documentsui/CopyServiceTest.java b/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
similarity index 92%
rename from tests/src/com/android/documentsui/CopyServiceTest.java
rename to tests/src/com/android/documentsui/services/FileOperationServiceTest.java
index 24a8113..35aad60 100644
--- a/tests/src/com/android/documentsui/CopyServiceTest.java
+++ b/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.services;
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -34,6 +34,9 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import com.android.documentsui.DocumentsProviderHelper;
+import com.android.documentsui.Shared;
+import com.android.documentsui.StubProvider;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
@@ -54,10 +57,10 @@
 import java.util.concurrent.TimeoutException;
 
 @MediumTest
-public class CopyServiceTest extends ServiceTestCase<CopyService> {
+public class FileOperationServiceTest extends ServiceTestCase<FileOperationService> {
 
-    public CopyServiceTest() {
-        super(CopyService.class);
+    public FileOperationServiceTest() {
+        super(FileOperationService.class);
     }
 
     private static String AUTHORITY = "com.android.documentsui.stubprovider";
@@ -139,7 +142,9 @@
                 testContent.getBytes());
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        moveIntent.putExtra(
+                FileOperationService.EXTRA_OPERATION,
+                FileOperationService.OPERATION_MOVE);
         startService(moveIntent);
 
         // 3 operations: file creation, writing data, deleting original.
@@ -235,7 +240,7 @@
         Uri testDir = createTestDirectory(srcPath);
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        moveIntent.putExtra(FileOperationService.EXTRA_OPERATION, FileOperationService.OPERATION_MOVE);
         startService(moveIntent);
 
         // 2 operations: Directory creation, and removal of the original.
@@ -270,7 +275,7 @@
         mStorage.createRegularFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        moveIntent.putExtra(FileOperationService.EXTRA_OPERATION, FileOperationService.OPERATION_MOVE);
         startService(moveIntent);
 
         // dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
@@ -311,10 +316,8 @@
 
     public void testCopyVirtualNonTypedFile() throws Exception {
         String srcPath = "/non-typed.sth";
-        // Empty stream types causes the FLAG_SUPPORTS_TYPED_DOCUMENT to be not set.
-        ArrayList<String> streamTypes = new ArrayList<>();
         Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type",
-                streamTypes, "I love Tokyo!".getBytes());
+                null /* streamTypes */, "I love Tokyo!".getBytes());
 
         Intent intent = createCopyIntent(Lists.newArrayList(testFile));
         startService(intent);
@@ -334,7 +337,7 @@
         mStorage.simulateReadErrorsForFile(testFile);
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        moveIntent.putExtra(FileOperationService.EXTRA_OPERATION, FileOperationService.OPERATION_MOVE);
         startService(moveIntent);
 
         try {
@@ -376,7 +379,7 @@
         mStorage.simulateReadErrorsForFile(errFile);
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
-        moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+        moveIntent.putExtra(FileOperationService.EXTRA_OPERATION, FileOperationService.OPERATION_MOVE);
         startService(moveIntent);
 
         // - dst dir creation,
@@ -424,8 +427,14 @@
 
         DocumentStack stack = new DocumentStack();
         stack.push(DocumentInfo.fromUri(mResolver, dst));
-        final Intent copyIntent = new Intent(mContext, CopyService.class);
-        copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
+        final Intent copyIntent = new Intent(mContext, FileOperationService.class);
+        copyIntent.putExtra(
+                FileOperationService.EXTRA_OPERATION,
+                FileOperationService.OPERATION_COPY);
+        copyIntent.putExtra(
+                FileOperationService.EXTRA_JOB_ID,
+                FileOperationService.createJobId());
+        copyIntent.putParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST, srcDocs);
         copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
 
         return copyIntent;
@@ -511,7 +520,7 @@
         mResolver.addProvider(AUTHORITY, mStorage);
     }
 
-    private final class CopyJobListener implements CopyService.TestOnlyListener {
+    private final class CopyJobListener implements FileOperationService.TestOnlyListener {
 
         final CountDownLatch latch = new CountDownLatch(1);
         final List<DocumentInfo> failedDocs = new ArrayList<>();