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("• %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<>();