Use Window#setTitle to communicate selection change to TalkBack.
am: c8739d979a

Change-Id: I18d0b3d53573ebd360f170e141edef0aea1b4ba7
diff --git a/res/color/item_doc_list_background_activated.xml b/res/color/item_doc_list_background_activated.xml
deleted file mode 100644
index 7d7a110..0000000
--- a/res/color/item_doc_list_background_activated.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:state_activated="true"
-        android:color="?android:attr/colorAccent"
-        android:alpha="0.1" />
-    <item
-        android:color="@android:color/transparent" />
-</selector>
diff --git a/res/color/item_title.xml b/res/color/item_title.xml
index 82ffb3b..de28025 100644
--- a/res/color/item_title.xml
+++ b/res/color/item_title.xml
@@ -17,7 +17,7 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:state_enabled="true"
-        android:color="?android:attr/textColorPrimary" />
+        android:color="@color/item_doc_title" />
     <item
         android:state_enabled="false"
         android:color="@*android:color/secondary_text_default_material_light"/>
diff --git a/res/drawable/drag_shadow_background.xml b/res/drawable/drag_shadow_background.xml
index d6ec373..5940bb3 100644
--- a/res/drawable/drag_shadow_background.xml
+++ b/res/drawable/drag_shadow_background.xml
@@ -21,8 +21,8 @@
       android:width="1dp"
       android:color="#ff9f9f9f" />
   <corners
-      android:bottomRightRadius="3dp"
-      android:bottomLeftRadius="3dp"
-      android:topLeftRadius="3dp"
-      android:topRightRadius="3dp"/>
+      android:bottomRightRadius="2dp"
+      android:bottomLeftRadius="2dp"
+      android:topLeftRadius="2dp"
+      android:topRightRadius="2dp"/>
 </shape>
diff --git a/res/drawable/drop_badge_states.xml b/res/drawable/drop_badge_states.xml
new file mode 100644
index 0000000..f859b7c
--- /dev/null
+++ b/res/drawable/drop_badge_states.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        app:state_droppable="true"
+        app:state_drop_hovered="true"
+        android:drawable="@drawable/ic_drop_ok_badge" />
+    <item
+        app:state_droppable="false"
+        app:state_drop_hovered="true"
+        android:drawable="@drawable/ic_drop_not_ok_badge" />
+    <item
+        app:state_drop_hovered="false"
+        android:drawable="@android:color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/ic_drop_not_ok_badge.xml b/res/drawable/ic_drop_not_ok_badge.xml
new file mode 100644
index 0000000..45c8b6d
--- /dev/null
+++ b/res/drawable/ic_drop_not_ok_badge.xml
@@ -0,0 +1,38 @@
+<!--
+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="15dp"
+        android:height="15dp"
+        android:viewportWidth="30.0"
+        android:viewportHeight="30.0">
+
+    <group
+         android:name="whiteBg">
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M0,15a15,15 0 1,0 30,0a15,15 0 1,0 -30,0" />
+    </group>
+
+    <group
+         android:name="badge"
+         android:translateX="2"
+         android:translateY="2">
+        <path
+            android:fillColor="#FFC53929"
+            android:pathData="M3.8056487,3.8056487 C-1.26854957,8.87984696 -1.26854957,17.1162267 3.8056487,22.190425 C8.87984696,27.2646233 17.1162267,27.2646233 22.190425,22.190425 C27.2646233,17.1162267 27.2646233,8.87984696 22.190425,3.8056487 C17.1162267,-1.26854957 8.87984696,-1.26854957 3.8056487,3.8056487 L3.8056487,3.8056487 Z M16.5335708,17.9477843 L12.9980369,14.4122504 L9.46250295,17.9477843 L8.04828938,16.5335708 L11.5838233,12.9980369 L8.04828938,9.46250295 L9.46250295,8.04828938 L12.9980369,11.5838233 L16.5335708,8.04828938 L17.9477843,9.46250295 L14.4122504,12.9980369 L17.9477843,16.5335708 L16.5335708,17.9477843 L16.5335708,17.9477843 Z" />
+    </group>
+</vector>
diff --git a/res/drawable/ic_drop_ok_badge.xml b/res/drawable/ic_drop_ok_badge.xml
new file mode 100644
index 0000000..48e3e83
--- /dev/null
+++ b/res/drawable/ic_drop_ok_badge.xml
@@ -0,0 +1,37 @@
+<!--
+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="15dp"
+        android:height="15dp"
+        android:viewportWidth="30.0"
+        android:viewportHeight="30.0">
+
+    <group
+         android:name="whiteBg">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M0,15a15,15 0 1,0 30,0a15,15 0 1,0 -30,0" />
+    </group>
+
+    <group
+         android:name="badge"
+         android:translateX="2"
+         android:translateY="2">
+    <path
+        android:fillColor="#FF0B8043"
+        android:pathData="M13,0 C5.824,0 0,5.824 0,13 C0,20.176 5.824,26 13,26 C20.176,26 26,20.176 26,13 C26,5.824 20.176,0 13,0 L13,0 Z M19,14 L14,14 L14,19 L12,19 L12,14 L7,14 L7,12 L12,12 L12,7 L14,7 L14,12 L19,12 L19,14 Z" />
+    </group>
+</vector>
diff --git a/res/drawable/item_doc_list_background.xml b/res/drawable/item_doc_list_background.xml
deleted file mode 100644
index 13910bb..0000000
--- a/res/drawable/item_doc_list_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
-     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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="@color/item_doc_list_background_activated" />
-</shape>
diff --git a/res/layout/drag_shadow_layout.xml b/res/layout/drag_shadow_layout.xml
index 26613ef..e94c443 100644
--- a/res/layout/drag_shadow_layout.xml
+++ b/res/layout/drag_shadow_layout.xml
@@ -23,13 +23,7 @@
     android:gravity="center_vertical|left"
     android:background="@drawable/drag_shadow_background">
 
-    <ImageView
-        android:id="@android:id/icon"
-        android:layout_width="@dimen/root_icon_size"
-        android:layout_height="@dimen/root_icon_size"
-        android:scaleType="centerInside"
-        android:contentDescription="@null"
-        android:duplicateParentState="true"/>
+   <include layout="@layout/drop_badge"/>
 
     <TextView
         android:id="@android:id/title"
diff --git a/res/drawable/drag_shadow_background_no_drop.xml b/res/layout/drop_badge.xml
similarity index 61%
rename from res/drawable/drag_shadow_background_no_drop.xml
rename to res/layout/drop_badge.xml
index b92c30a..485f400 100644
--- a/res/drawable/drag_shadow_background_no_drop.xml
+++ b/res/layout/drop_badge.xml
@@ -14,15 +14,11 @@
      limitations under the License.
 -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-  <solid android:color="@color/item_drag_shadow_background_no_drop" />
-  <stroke
-      android:width="1dp"
-      android:color="#ff9f9f9f" />
-  <corners
-      android:bottomRightRadius="3dp"
-      android:bottomLeftRadius="3dp"
-      android:topLeftRadius="3dp"
-      android:topRightRadius="3dp"/>
-</shape>
+<com.android.documentsui.DropBadgeView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/root_icon_size"
+        android:layout_height="@dimen/root_icon_size"
+        android:scaleType="centerInside"
+        android:contentDescription="@null"
+        android:duplicateParentState="true"/>
\ No newline at end of file
diff --git a/res/layout/item_subdir.xml b/res/layout/item_subdir.xml
index 18a6f41..578e676 100644
--- a/res/layout/item_subdir.xml
+++ b/res/layout/item_subdir.xml
@@ -32,7 +32,6 @@
         android:singleLine="true"
         android:ellipsize="end"
         android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-        android:textColor="@*android:color/primary_text_default_material_light" />
+        android:textColor="@color/item_doc_title" />
 
 </LinearLayout>
diff --git a/res/layout/item_subdir_title.xml b/res/layout/item_subdir_title.xml
index 51f67a4..e57acb5 100644
--- a/res/layout/item_subdir_title.xml
+++ b/res/layout/item_subdir_title.xml
@@ -30,5 +30,5 @@
         android:textAlignment="viewStart"
         android:drawablePadding="12dp"
         android:drawableRight="@drawable/ic_breadcrumb_arrow_down"
-        android:textColor="?android:attr/textColorPrimary" />
+        android:textColor="@color/item_doc_title" />
 </LinearLayout>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index feace71..eea3262 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -16,95 +16,144 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="2783841764617238354">"Dokumenti"</string>
-    <string name="files_label" msgid="6051402950202690279">"Datoteke"</string>
-    <string name="downloads_label" msgid="959113951084633612">"Preuzimanja"</string>
-    <string name="title_open" msgid="4353228937663917801">"Otvori sa"</string>
-    <string name="title_save" msgid="2433679664882857999">"Sačuvaj u"</string>
-    <string name="menu_create_dir" msgid="2547620241173881754">"Novi direktorijum"</string>
-    <string name="menu_grid" msgid="6878021334497835259">"Prikaz mreže"</string>
-    <string name="menu_list" msgid="7279285939892417279">"Prikaz liste"</string>
-    <string name="menu_sort" msgid="7677740407158414452">"Sortiraj prema"</string>
-    <string name="menu_search" msgid="3816712084502856974">"Pretraži"</string>
-    <string name="menu_settings" msgid="6008033148948428823">"Podešavanja"</string>
-    <string name="menu_open" msgid="432922957274920903">"Otvori"</string>
-    <string name="menu_save" msgid="2394743337684426338">"Sačuvaj"</string>
-    <string name="menu_share" msgid="3075149983979628146">"Deli"</string>
-    <string name="menu_delete" msgid="8138799623850614177">"Izbriši"</string>
-    <string name="menu_select_all" msgid="8323579667348729928">"Izaberi sve"</string>
-    <string name="menu_copy" msgid="3612326052677229148">"Kopiraj na..."</string>
-    <string name="menu_move" msgid="1828090633118079817">"Premesti u..."</string>
-    <string name="menu_new_window" msgid="1226032889278727538">"Novi prozor"</string>
-    <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiraj"</string>
-    <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Nalepi"</string>
-    <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Prikaži internu memoriju"</string>
-    <string name="menu_advanced_show" product="default" msgid="5792182900084144261">"Prikaži SD karticu"</string>
-    <string name="menu_advanced_hide" product="nosdcard" msgid="4218809952721972589">"Sakrij internu memoriju"</string>
-    <string name="menu_advanced_hide" product="default" msgid="4845869969015718848">"Sakrij SD karticu"</string>
-    <string name="menu_file_size_show" msgid="3240323619260823076">"Prikaži veličinu datoteke"</string>
-    <string name="menu_file_size_hide" msgid="8881975928502581042">"Sakrij veličinu datoteke"</string>
-    <string name="button_select" msgid="527196987259139214">"Izaberi"</string>
-    <string name="button_copy" msgid="8706475544635021302">"Kopiraj"</string>
-    <string name="button_move" msgid="2202666023104202232">"Premesti"</string>
-    <string name="button_dismiss" msgid="3714065566893946085">"Odbaci"</string>
-    <string name="button_retry" msgid="4392027584153752797">"Pokušaj ponovo"</string>
-    <string name="sort_name" msgid="9183560467917256779">"Prema imenu"</string>
-    <string name="sort_date" msgid="586080032956151448">"Prema datumu izmene"</string>
-    <string name="sort_size" msgid="3350681319735474741">"Prema veličini"</string>
-    <string name="drawer_open" msgid="4545466532430226949">"Prikaži osnovne elemente"</string>
-    <string name="drawer_close" msgid="7602734368552123318">"Sakrij osnovne elemente"</string>
-    <string name="save_error" msgid="6167009778003223664">"Čuvanje dokumenta nije uspelo"</string>
-    <string name="create_error" msgid="3735649141335444215">"Direktorijum nije napravljen"</string>
-    <string name="query_error" msgid="1222448261663503501">"Slanje upita za dokumente nije uspelo"</string>
-    <string name="root_recent" msgid="4470053704320518133">"Nedavno"</string>
-    <string name="root_available_bytes" msgid="8568452858617033281">"Slobodno je <xliff:g id="SIZE">%1$s</xliff:g>"</string>
-    <string name="root_type_service" msgid="2178854894416775409">"Usluge skladištenja"</string>
-    <string name="root_type_shortcut" msgid="3318760609471618093">"Prečice"</string>
-    <string name="root_type_device" msgid="7121342474653483538">"Uređaji"</string>
-    <string name="root_type_apps" msgid="8838065367985945189">"Još aplikacija"</string>
-    <string name="empty" msgid="7858882803708117596">"Nema stavki"</string>
-    <string name="toast_no_application" msgid="1339885974067891667">"Nije moguće otvoriti datoteku"</string>
-    <string name="toast_failed_delete" msgid="2180678019407244069">"Nije moguće izbrisati neke dokumente"</string>
-    <string name="share_via" msgid="8966594246261344259">"Delite preko"</string>
-    <string name="copy_notification_title" msgid="6374299806748219777">"Kopiranje datoteka"</string>
-    <string name="move_notification_title" msgid="6193835179777284805">"Datoteke se premeštaju"</string>
-    <string name="copy_remaining" msgid="6283790937387975095">"Još <xliff:g id="DURATION">%s</xliff:g>"</string>
-    <plurals name="copy_begin" formatted="false" msgid="9071199452634086365">
-      <item quantity="one">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
-      <item quantity="few">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
-      <item quantity="other">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
+    <string name="app_label" msgid="3303317181319900082">"Datoteke"</string>
+    <string name="title_open" msgid="3165686459158020921">"Otvori iz"</string>
+    <string name="title_save" msgid="4384490653102710025">"Sačuvaj u"</string>
+    <string name="menu_create_dir" msgid="2413624798689091042">"Novi direktorijum"</string>
+    <string name="menu_grid" msgid="1453636521731880680">"Prikaz mreže"</string>
+    <string name="menu_list" msgid="6714267452146410402">"Prikaz liste"</string>
+    <string name="menu_search" msgid="1876699106790719849">"Pretraži"</string>
+    <string name="menu_settings" msgid="6520844520117939047">"Podešavanja memorije"</string>
+    <string name="menu_open" msgid="9092138100049759315">"Otvori"</string>
+    <string name="menu_open_with" msgid="5507647065467520229">"Otvori pomoću"</string>
+    <string name="menu_open_in_new_window" msgid="6686563636123311276">"Otvori u novom prozoru"</string>
+    <string name="menu_save" msgid="5195367497138965168">"Sačuvaj"</string>
+    <string name="menu_share" msgid="4307140947108068356">"Deli"</string>
+    <string name="menu_delete" msgid="1022254131543256626">"Izbriši"</string>
+    <string name="menu_select_all" msgid="7600576812185570403">"Izaberi sve"</string>
+    <string name="menu_copy" msgid="7404820171352314754">"Kopiraj u…"</string>
+    <string name="menu_move" msgid="2310760789561129882">"Premesti u…"</string>
+    <string name="menu_rename" msgid="1883113442688817554">"Preimenuj"</string>
+    <string name="menu_new_window" msgid="2947837751796109126">"Novi prozor"</string>
+    <string name="menu_cut_to_clipboard" msgid="2878752142015026229">"Iseci"</string>
+    <string name="menu_copy_to_clipboard" msgid="5064081159073330776">"Kopiraj"</string>
+    <string name="menu_paste_from_clipboard" msgid="360947260414135827">"Nalepi"</string>
+    <string name="menu_paste_into_folder" msgid="8000644546983240101">"Nalepi u direktorijum"</string>
+    <string name="menu_advanced_show" msgid="7558626506462906726">"Prikaži internu memoriju"</string>
+    <string name="menu_advanced_hide" msgid="6488381508009246334">"Sakrij internu memoriju"</string>
+    <string name="button_select" msgid="240863497069321364">"Izaberi"</string>
+    <string name="button_copy" msgid="8219059853840996027">"Kopiraj"</string>
+    <string name="button_move" msgid="8596460499325291272">"Premesti"</string>
+    <string name="button_dismiss" msgid="7235249361023803349">"Odbaci"</string>
+    <string name="button_retry" msgid="4011461781916631389">"Probaj ponovo"</string>
+    <string name="not_sorted" msgid="7813496644889115530">"Nisu sortirani"</string>
+    <string name="sort_dimension_name" msgid="6325591541414177579">"Naziv"</string>
+    <string name="sort_dimension_summary" msgid="7724534446881397860">"Rezime"</string>
+    <string name="sort_dimension_date" msgid="4231005651895254033">"Izmenjeno"</string>
+    <string name="sort_dimension_size" msgid="2190547351159472884">"Veličina"</string>
+    <string name="sort_direction_ascending" msgid="5882787683763248102">"Rastuće"</string>
+    <string name="sort_direction_descending" msgid="1729187589765894076">"Opadajuće"</string>
+    <string name="drawer_open" msgid="8071673398187261741">"Prikaži osnovne direktorijume"</string>
+    <string name="drawer_close" msgid="4263880768630848848">"Sakrij osnovne direktorijume"</string>
+    <string name="save_error" msgid="8631128801982095782">"Čuvanje dokumenta nije uspelo"</string>
+    <string name="create_error" msgid="3092144450044861994">"Pravljenje direktorijuma nije uspelo"</string>
+    <string name="query_error" msgid="6625421453613879336">"Učitavanje sadržaja trenutno nije moguće"</string>
+    <string name="root_recent" msgid="1080156975424341623">"Nedavno"</string>
+    <string name="root_available_bytes" msgid="8269870862691408864">"Slobodno je <xliff:g id="SIZE">%1$s</xliff:g>"</string>
+    <string name="root_type_service" msgid="6521366147466512289">"Usluge čuvanja"</string>
+    <string name="root_type_shortcut" msgid="6059343175525442279">"Prečice"</string>
+    <string name="root_type_device" msgid="1713604128005476585">"Uređaji"</string>
+    <string name="root_type_apps" msgid="8646073235029886342">"Još aplikacija"</string>
+    <string name="empty" msgid="5300254272613103004">"Nema stavki"</string>
+    <string name="no_results" msgid="2371026325236359209">"Nema podudaranja u %1$s"</string>
+    <string name="toast_no_application" msgid="7555319548595113121">"Otvaranje datoteke nije uspelo"</string>
+    <string name="toast_failed_delete" msgid="3453846588205817591">"Nije moguće izbrisati neke dokumente"</string>
+    <string name="share_via" msgid="8725082736005677161">"Deljenje preko"</string>
+    <string name="copy_notification_title" msgid="52256435625098456">"Kopiramo datoteke"</string>
+    <string name="move_notification_title" msgid="3173424987049347605">"Premeštamo datoteke"</string>
+    <string name="delete_notification_title" msgid="2512757431856830792">"Brišemo datoteke"</string>
+    <string name="copy_remaining" msgid="5390517377265177727">"Još <xliff:g id="DURATION">%s</xliff:g>"</string>
+    <plurals name="copy_begin" formatted="false" msgid="3926184736640418042">
+      <item quantity="one">Kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku.</item>
+      <item quantity="few">Kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
+      <item quantity="other">Kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
     </plurals>
-    <plurals name="move_begin" formatted="false" msgid="8430330882138871643">
-      <item quantity="one">Premešta se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
-      <item quantity="few">Premeštaju se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
-      <item quantity="other">Premešta se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
+    <plurals name="move_begin" formatted="false" msgid="6049210105852581598">
+      <item quantity="one">Premeštamo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku.</item>
+      <item quantity="few">Premeštamo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
+      <item quantity="other">Premeštamo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
     </plurals>
-    <plurals name="deleting" formatted="false" msgid="5054338566802559411">
-      <item quantity="one">Briše se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
-      <item quantity="few">Brišu se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
-      <item quantity="other">Briše se <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
+    <plurals name="deleting" formatted="false" msgid="1347549374456757591">
+      <item quantity="one">Brišemo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku.</item>
+      <item quantity="few">Brišemo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke.</item>
+      <item quantity="other">Brišemo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka.</item>
     </plurals>
-    <string name="undo" msgid="7905788502491742328">"Opozovi"</string>
-    <string name="copy_preparing" msgid="3896202461003039386">"Priprema se kopiranje…"</string>
-    <string name="move_preparing" msgid="2772219441375531410">"Priprema se premeštanje..."</string>
-    <plurals name="copy_error_notification_title" formatted="false" msgid="5267616889076217261">
-      <item quantity="one">Nismo uspeli da kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku</item>
-      <item quantity="few">Nismo uspeli da kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke</item>
-      <item quantity="other">Nismo uspeli da kopiramo <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka</item>
+    <string name="undo" msgid="2902438994196400565">"Opozovi"</string>
+    <string name="copy_preparing" msgid="5326063807006898223">"Pripremamo kopiranje…"</string>
+    <string name="move_preparing" msgid="8742573245485449429">"Pripremamo premeštanje…"</string>
+    <string name="delete_preparing" msgid="6513863752916028147">"Pripremamo brisanje…"</string>
+    <string name="delete_progress" msgid="2627631054702306423">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="TOTALCOUNT">%2$d</xliff:g>"</string>
+    <plurals name="copy_error_notification_title" formatted="false" msgid="7406207967429915000">
+      <item quantity="one">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="few">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="other">Kopiranje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka nije uspelo</item>
     </plurals>
-    <plurals name="move_error_notification_title" formatted="false" msgid="2779299594174898891">
-      <item quantity="one">Nije uspelo premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke</item>
-      <item quantity="few">Nije uspelo premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke</item>
-      <item quantity="other">Nije uspelo premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka</item>
+    <plurals name="move_error_notification_title" formatted="false" msgid="7841920776201038994">
+      <item quantity="one">Premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="few">Premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="other">Premeštanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka nije uspelo</item>
     </plurals>
-    <string name="notification_touch_for_details" msgid="4483108577842961665">"Dodirnite da biste videli detalje"</string>
-    <string name="retry" msgid="7564024179122207376">"Pokušaj ponovo"</string>
-    <string name="copy_failure_alert_content" msgid="3715575000297709082">"Sledeće datoteke nisu kopirane: <xliff:g id="LIST">%1$s</xliff:g>"</string>
-    <string name="move_failure_alert_content" msgid="7151140279020481180">"Ove datoteke nisu premeštene: <xliff:g id="LIST">%1$s</xliff:g>"</string>
-    <plurals name="clipboard_files_clipped" formatted="false" msgid="855459017537058539">
+    <plurals name="delete_error_notification_title" formatted="false" msgid="4925525467677907298">
+      <item quantity="one">Brisanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="few">Brisanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke nije uspelo</item>
+      <item quantity="other">Brisanje <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka nije uspelo</item>
+    </plurals>
+    <string name="notification_touch_for_details" msgid="2385563502445129570">"Dodirnite da biste pregledali detalje"</string>
+    <string name="close" msgid="905969391788869975">"Zatvori"</string>
+    <string name="copy_failure_alert_content" msgid="161721873402101825">"Sledeće datoteke nisu kopirane: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="move_failure_alert_content" msgid="6586182218105304719">"Sledeće datoteke nisu premeštene: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="delete_failure_alert_content" msgid="7856890428816304981">"Sledeće datoteke nisu izbrisane: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="copy_converted_warning_content" msgid="6481928162944612074">"Ove datoteke su konvertovane u drugi format: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <plurals name="clipboard_files_clipped" formatted="false" msgid="139644798886220070">
       <item quantity="one">Kopirali ste <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku u privremenu memoriju.</item>
       <item quantity="few">Kopirali ste <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke u privremenu memoriju.</item>
       <item quantity="other">Kopirali ste <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka u privremenu memoriju.</item>
     </plurals>
-    <string name="clipboard_files_cannot_paste" msgid="2878324825602325706">"Izabrane datoteke ne mogu da se nalepe na ovoj lokaciji."</string>
+    <string name="clipboard_files_cannot_paste" msgid="5700451161181106925">"Izabrane datoteke ne mogu da se nalepe na ovoj lokaciji."</string>
+    <string name="rename_error" msgid="6700093173508118635">"Preimenovanje dokumenta nije uspelo"</string>
+    <string name="menu_eject_root" msgid="9215040039374893613">"Izbaci"</string>
+    <string name="notification_copy_files_converted_title" msgid="6916768494891833365">"Neke datoteke su konvertovane"</string>
+    <string name="open_external_dialog_request" msgid="8173558471322861268">"Želite li da dozvolite aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> da pristupa direktorijumu <xliff:g id="DIRECTORY"><i>^2</i></xliff:g> na memorijskom prostoru <xliff:g id="STORAGE"><i>^3</i></xliff:g>?"</string>
+    <string name="open_external_dialog_request_primary_volume" msgid="2240992164087948176">"Želite li da dozvolite aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> da pristupa direktorijumu <xliff:g id="DIRECTORY"><i>^2</i></xliff:g>?"</string>
+    <string name="open_external_dialog_root_request" msgid="6776729293982633">"Želite li da dozvolite aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> da pristupa podacima, uključujući slike i video snimke, na memorijskom prostoru <xliff:g id="STORAGE"><i>^2</i></xliff:g>?"</string>
+    <string name="never_ask_again" msgid="525908236522201138">"Ne pitaj ponovo"</string>
+    <string name="allow" msgid="1275746941353040309">"Dozvoli"</string>
+    <string name="deny" msgid="5127201668078153379">"Odbij"</string>
+    <plurals name="elements_selected" formatted="false" msgid="4448165978637163692">
+      <item quantity="one">Izabrana je <xliff:g id="COUNT_1">%1$d</xliff:g> stavka</item>
+      <item quantity="few">Izabrane su <xliff:g id="COUNT_1">%1$d</xliff:g> stavke</item>
+      <item quantity="other">Izabrano je <xliff:g id="COUNT_1">%1$d</xliff:g> stavki</item>
+    </plurals>
+    <plurals name="elements_dragged" formatted="false" msgid="5932571296037626279">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> stavka</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> stavke</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> stavki</item>
+    </plurals>
+    <string name="delete_filename_confirmation_message" msgid="8338069763240613258">"Želite li da izbrišete „<xliff:g id="NAME">%1$s</xliff:g>“?"</string>
+    <string name="delete_foldername_confirmation_message" msgid="9084085260877704140">"Želite li da izbrišete direktorijum „<xliff:g id="NAME">%1$s</xliff:g>“ i njegov sadržaj?"</string>
+    <plurals name="delete_files_confirmation_message" formatted="false" msgid="4866664063250034142">
+      <item quantity="one">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> datoteku?</item>
+      <item quantity="few">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> datoteke?</item>
+      <item quantity="other">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> datoteka?</item>
+    </plurals>
+    <plurals name="delete_folders_confirmation_message" formatted="false" msgid="1028946402799686388">
+      <item quantity="one">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> direktorijum i njihov sadržaj?</item>
+      <item quantity="few">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> direktorijuma i njihov sadržaj?</item>
+      <item quantity="other">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> direktorijuma i njihov sadržaj?</item>
+    </plurals>
+    <plurals name="delete_items_confirmation_message" formatted="false" msgid="7285090426511028179">
+      <item quantity="one">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> stavku?</item>
+      <item quantity="few">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> stavke?</item>
+      <item quantity="other">Želite li da izbrišete <xliff:g id="COUNT_1">%1$d</xliff:g> stavki?</item>
+    </plurals>
 </resources>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..9732fa8
--- /dev/null
+++ b/res/values-be-rBY/strings.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+     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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="3303317181319900082">"Файлы"</string>
+    <string name="title_open" msgid="3165686459158020921">"Адкрыць з"</string>
+    <string name="title_save" msgid="4384490653102710025">"Захаваць у"</string>
+    <string name="menu_create_dir" msgid="2413624798689091042">"Новая папка"</string>
+    <string name="menu_grid" msgid="1453636521731880680">"У выглядзе табліцы"</string>
+    <string name="menu_list" msgid="6714267452146410402">"У выглядзе спіса"</string>
+    <string name="menu_search" msgid="1876699106790719849">"Пошук"</string>
+    <string name="menu_settings" msgid="6520844520117939047">"Налады сховішча"</string>
+    <string name="menu_open" msgid="9092138100049759315">"Адкрыць"</string>
+    <string name="menu_open_with" msgid="5507647065467520229">"Адкрыць з дапамогай"</string>
+    <string name="menu_open_in_new_window" msgid="6686563636123311276">"Адкрыць у новым акне"</string>
+    <string name="menu_save" msgid="5195367497138965168">"Захаваць"</string>
+    <string name="menu_share" msgid="4307140947108068356">"Абагуліць"</string>
+    <string name="menu_delete" msgid="1022254131543256626">"Выдаліць"</string>
+    <string name="menu_select_all" msgid="7600576812185570403">"Выбраць усе"</string>
+    <string name="menu_copy" msgid="7404820171352314754">"Капіраваць у…"</string>
+    <string name="menu_move" msgid="2310760789561129882">"Перамясціць у…"</string>
+    <string name="menu_rename" msgid="1883113442688817554">"Перайменаваць"</string>
+    <string name="menu_new_window" msgid="2947837751796109126">"Новае акно"</string>
+    <string name="menu_cut_to_clipboard" msgid="2878752142015026229">"Выразаць"</string>
+    <string name="menu_copy_to_clipboard" msgid="5064081159073330776">"Капіраваць"</string>
+    <string name="menu_paste_from_clipboard" msgid="360947260414135827">"Уставіць"</string>
+    <string name="menu_paste_into_folder" msgid="8000644546983240101">"Уставіць у папку"</string>
+    <string name="menu_advanced_show" msgid="7558626506462906726">"Паказаць унутр. сховішча"</string>
+    <string name="menu_advanced_hide" msgid="6488381508009246334">"Схаваць унутр. сховішча"</string>
+    <string name="button_select" msgid="240863497069321364">"Выбраць"</string>
+    <string name="button_copy" msgid="8219059853840996027">"Капіраваць"</string>
+    <string name="button_move" msgid="8596460499325291272">"Перамясціць"</string>
+    <string name="button_dismiss" msgid="7235249361023803349">"Адхіліць"</string>
+    <string name="button_retry" msgid="4011461781916631389">"Паўтарыць спробу"</string>
+    <string name="not_sorted" msgid="7813496644889115530">"Не адсартаваны"</string>
+    <string name="sort_dimension_name" msgid="6325591541414177579">"Назва"</string>
+    <string name="sort_dimension_summary" msgid="7724534446881397860">"Кароткае апісанне"</string>
+    <string name="sort_dimension_date" msgid="4231005651895254033">"Зменены"</string>
+    <string name="sort_dimension_size" msgid="2190547351159472884">"Памер"</string>
+    <string name="sort_direction_ascending" msgid="5882787683763248102">"Па ўзрастанні"</string>
+    <string name="sort_direction_descending" msgid="1729187589765894076">"Па ўбыванні"</string>
+    <string name="drawer_open" msgid="8071673398187261741">"Паказаць каранёвыя папкі"</string>
+    <string name="drawer_close" msgid="4263880768630848848">"Схаваць каранёвыя папкі"</string>
+    <string name="save_error" msgid="8631128801982095782">"Не атрымалася захаваць дакумент"</string>
+    <string name="create_error" msgid="3092144450044861994">"Не атрымалася стварыць папку"</string>
+    <string name="query_error" msgid="6625421453613879336">"Зараз немагчыма загрузіць змесціва"</string>
+    <string name="root_recent" msgid="1080156975424341623">"Апошнія"</string>
+    <string name="root_available_bytes" msgid="8269870862691408864">"<xliff:g id="SIZE">%1$s</xliff:g> свабодна"</string>
+    <string name="root_type_service" msgid="6521366147466512289">"Службы захоўвання"</string>
+    <string name="root_type_shortcut" msgid="6059343175525442279">"Ярлыкі"</string>
+    <string name="root_type_device" msgid="1713604128005476585">"Прылады"</string>
+    <string name="root_type_apps" msgid="8646073235029886342">"Іншыя праграмы"</string>
+    <string name="empty" msgid="5300254272613103004">"Няма элементаў"</string>
+    <string name="no_results" msgid="2371026325236359209">"Няма супадзенняў у %1$s"</string>
+    <string name="toast_no_application" msgid="7555319548595113121">"Немагчыма адкрыць файл"</string>
+    <string name="toast_failed_delete" msgid="3453846588205817591">"Немагчыма выдаліць некаторыя дакументы"</string>
+    <string name="share_via" msgid="8725082736005677161">"Абагуліць праз"</string>
+    <string name="copy_notification_title" msgid="52256435625098456">"Капіраванне файлаў"</string>
+    <string name="move_notification_title" msgid="3173424987049347605">"Перамяшчэнне файлаў"</string>
+    <string name="delete_notification_title" msgid="2512757431856830792">"Выдаленне файлаў"</string>
+    <string name="copy_remaining" msgid="5390517377265177727">"Засталося <xliff:g id="DURATION">%s</xliff:g>"</string>
+    <plurals name="copy_begin" formatted="false" msgid="3926184736640418042">
+      <item quantity="one">Ідзе капіраванне <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+      <item quantity="few">Ідзе капіраванне <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="many">Ідзе капіраванне <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="other">Ідзе капіраванне <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+    </plurals>
+    <plurals name="move_begin" formatted="false" msgid="6049210105852581598">
+      <item quantity="one">Перамяшчаецца <xliff:g id="COUNT_1">%1$d</xliff:g> файл.</item>
+      <item quantity="few">Перамяшчаюцца <xliff:g id="COUNT_1">%1$d</xliff:g> файлы.</item>
+      <item quantity="many">Перамяшчаюцца <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="other">Перамяшчаецца <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+    </plurals>
+    <plurals name="deleting" formatted="false" msgid="1347549374456757591">
+      <item quantity="one">Ідзе выдаленне <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+      <item quantity="few">Ідзе выдаленне <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="many">Ідзе выдаленне <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="other">Ідзе выдаленне <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+    </plurals>
+    <string name="undo" msgid="2902438994196400565">"Адрабіць"</string>
+    <string name="copy_preparing" msgid="5326063807006898223">"Ідзе падрыхтоўка да капіравання…"</string>
+    <string name="move_preparing" msgid="8742573245485449429">"Падрыхтоўваецца перамяшчэнне…"</string>
+    <string name="delete_preparing" msgid="6513863752916028147">"Ідзе падрыхтоўка да выдалення…"</string>
+    <string name="delete_progress" msgid="2627631054702306423">"<xliff:g id="COUNT_0">%1$d</xliff:g> / <xliff:g id="TOTALCOUNT">%2$d</xliff:g>"</string>
+    <plurals name="copy_error_notification_title" formatted="false" msgid="7406207967429915000">
+      <item quantity="one">Не атрымалася скапіраваць <xliff:g id="COUNT_1">%1$d</xliff:g> файл</item>
+      <item quantity="few">Не атрымалася скапіраваць <xliff:g id="COUNT_1">%1$d</xliff:g> файлы</item>
+      <item quantity="many">Не атрымалася скапіраваць <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў</item>
+      <item quantity="other">Не атрымалася скапіраваць <xliff:g id="COUNT_1">%1$d</xliff:g> файла</item>
+    </plurals>
+    <plurals name="move_error_notification_title" formatted="false" msgid="7841920776201038994">
+      <item quantity="one">Не атрымалася перамясціць <xliff:g id="COUNT_1">%1$d</xliff:g> файл</item>
+      <item quantity="few">Не атрымалася перамясціць <xliff:g id="COUNT_1">%1$d</xliff:g> файлы</item>
+      <item quantity="many">Не атрымалася перамясціць <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў</item>
+      <item quantity="other">Не атрымалася перамясціць <xliff:g id="COUNT_1">%1$d</xliff:g> файла</item>
+    </plurals>
+    <plurals name="delete_error_notification_title" formatted="false" msgid="4925525467677907298">
+      <item quantity="one">Не атрымалася выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файл</item>
+      <item quantity="few">Не атрымалася выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файлы</item>
+      <item quantity="many">Не атрымалася выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў</item>
+      <item quantity="other">Не атрымалася выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файла</item>
+    </plurals>
+    <string name="notification_touch_for_details" msgid="2385563502445129570">"Дакраніцеся, каб праглядзець больш падрабязна"</string>
+    <string name="close" msgid="905969391788869975">"Закрыць"</string>
+    <string name="copy_failure_alert_content" msgid="161721873402101825">"Не былі скапіраваны наступныя файлы: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="move_failure_alert_content" msgid="6586182218105304719">"Не былі перамешчаны наступныя файлы: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="delete_failure_alert_content" msgid="7856890428816304981">"Не былі выдалены наступныя файлы: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="copy_converted_warning_content" msgid="6481928162944612074">"Гэтыя файлы былі пераўтвораны ў іншы фармат: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <plurals name="clipboard_files_clipped" formatted="false" msgid="139644798886220070">
+      <item quantity="one">У буфер абмену скапіраваны <xliff:g id="COUNT_1">%1$d</xliff:g> файл.</item>
+      <item quantity="few">У буфер абмену скапіраваны <xliff:g id="COUNT_1">%1$d</xliff:g> файлы.</item>
+      <item quantity="many">У буфер абмену скапіраваны <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў.</item>
+      <item quantity="other">У буфер абмену скапіравана <xliff:g id="COUNT_1">%1$d</xliff:g> файла.</item>
+    </plurals>
+    <string name="clipboard_files_cannot_paste" msgid="5700451161181106925">"Немагчыма ўставіць выбраныя файлы ў гэта месца."</string>
+    <string name="rename_error" msgid="6700093173508118635">"Не атрымалася перайменаваць дакумент"</string>
+    <string name="menu_eject_root" msgid="9215040039374893613">"Выняць"</string>
+    <string name="notification_copy_files_converted_title" msgid="6916768494891833365">"Некаторыя файлы былі пераўтвораны"</string>
+    <string name="open_external_dialog_request" msgid="8173558471322861268">"Даць праграме <xliff:g id="APPNAME"><b>^1</b></xliff:g> доступ да каталога <xliff:g id="DIRECTORY"><i>^2</i></xliff:g> у <xliff:g id="STORAGE"><i>^3</i></xliff:g>?"</string>
+    <string name="open_external_dialog_request_primary_volume" msgid="2240992164087948176">"Даць праграме <xliff:g id="APPNAME"><b>^1</b></xliff:g> доступ да каталога <xliff:g id="DIRECTORY"><i>^2</i></xliff:g>?"</string>
+    <string name="open_external_dialog_root_request" msgid="6776729293982633">"Даць праграме <xliff:g id="APPNAME"><b>^1</b></xliff:g> доступ да вашых даных, у тым ліку фатаграфій і відэа, у <xliff:g id="STORAGE"><i>^2</i></xliff:g>?"</string>
+    <string name="never_ask_again" msgid="525908236522201138">"Больш не пытацца"</string>
+    <string name="allow" msgid="1275746941353040309">"Дазволіць"</string>
+    <string name="deny" msgid="5127201668078153379">"Адмовіць"</string>
+    <plurals name="elements_selected" formatted="false" msgid="4448165978637163692">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> выбраны</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> выбраны</item>
+      <item quantity="many"><xliff:g id="COUNT_1">%1$d</xliff:g> выбрана</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> выбрана</item>
+    </plurals>
+    <plurals name="elements_dragged" formatted="false" msgid="5932571296037626279">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> элемент</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> элементы</item>
+      <item quantity="many"><xliff:g id="COUNT_1">%1$d</xliff:g> элементаў</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> элемента</item>
+    </plurals>
+    <string name="delete_filename_confirmation_message" msgid="8338069763240613258">"Выдаліць \"<xliff:g id="NAME">%1$s</xliff:g>\"?"</string>
+    <string name="delete_foldername_confirmation_message" msgid="9084085260877704140">"Выдаліць папку \"<xliff:g id="NAME">%1$s</xliff:g>\" і яе змесціва?"</string>
+    <plurals name="delete_files_confirmation_message" formatted="false" msgid="4866664063250034142">
+      <item quantity="one">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файл?</item>
+      <item quantity="few">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файлы?</item>
+      <item quantity="many">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файлаў?</item>
+      <item quantity="other">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> файла?</item>
+    </plurals>
+    <plurals name="delete_folders_confirmation_message" formatted="false" msgid="1028946402799686388">
+      <item quantity="one">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> папку і іх змесціва?</item>
+      <item quantity="few">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> папкі і іх змесціва?</item>
+      <item quantity="many">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> папак і іх змесціва?</item>
+      <item quantity="other">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> папкі і іх змесціва?</item>
+    </plurals>
+    <plurals name="delete_items_confirmation_message" formatted="false" msgid="7285090426511028179">
+      <item quantity="one">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> элемент?</item>
+      <item quantity="few">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> элементы?</item>
+      <item quantity="many">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> элементаў?</item>
+      <item quantity="other">Выдаліць <xliff:g id="COUNT_1">%1$d</xliff:g> элемента?</item>
+    </plurals>
+</resources>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..daa541e
--- /dev/null
+++ b/res/values-bs-rBA/strings.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+     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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="3303317181319900082">"Fajlovi"</string>
+    <string name="title_open" msgid="3165686459158020921">"Otvori iz"</string>
+    <string name="title_save" msgid="4384490653102710025">"Sačuvaj u"</string>
+    <string name="menu_create_dir" msgid="2413624798689091042">"Nova fascikla"</string>
+    <string name="menu_grid" msgid="1453636521731880680">"Prikaz u vidu mreže"</string>
+    <string name="menu_list" msgid="6714267452146410402">"Prikaz u vidu liste"</string>
+    <string name="menu_search" msgid="1876699106790719849">"Pretraži"</string>
+    <string name="menu_settings" msgid="6520844520117939047">"Postavke pohrane"</string>
+    <string name="menu_open" msgid="9092138100049759315">"Otvori"</string>
+    <string name="menu_open_with" msgid="5507647065467520229">"Otvori koristeći"</string>
+    <string name="menu_open_in_new_window" msgid="6686563636123311276">"Otvori u novom prozoru"</string>
+    <string name="menu_save" msgid="5195367497138965168">"Sačuvaj"</string>
+    <string name="menu_share" msgid="4307140947108068356">"Podijeli"</string>
+    <string name="menu_delete" msgid="1022254131543256626">"Izbriši"</string>
+    <string name="menu_select_all" msgid="7600576812185570403">"Odaberi sve"</string>
+    <string name="menu_copy" msgid="7404820171352314754">"Kopiraj na…"</string>
+    <string name="menu_move" msgid="2310760789561129882">"Premjesti u…"</string>
+    <string name="menu_rename" msgid="1883113442688817554">"Preimenuj"</string>
+    <string name="menu_new_window" msgid="2947837751796109126">"Novi prozor"</string>
+    <string name="menu_cut_to_clipboard" msgid="2878752142015026229">"Izreži"</string>
+    <string name="menu_copy_to_clipboard" msgid="5064081159073330776">"Kopiraj"</string>
+    <string name="menu_paste_from_clipboard" msgid="360947260414135827">"Zalijepi"</string>
+    <string name="menu_paste_into_folder" msgid="8000644546983240101">"Zalijepi u folder"</string>
+    <string name="menu_advanced_show" msgid="7558626506462906726">"Prikaži unutrašnju mem."</string>
+    <string name="menu_advanced_hide" msgid="6488381508009246334">"Sakrij unutrašnju mem."</string>
+    <string name="button_select" msgid="240863497069321364">"Odaberi"</string>
+    <string name="button_copy" msgid="8219059853840996027">"Kopiraj"</string>
+    <string name="button_move" msgid="8596460499325291272">"Premjesti"</string>
+    <string name="button_dismiss" msgid="7235249361023803349">"Odbaci"</string>
+    <string name="button_retry" msgid="4011461781916631389">"Pokušajte ponovo"</string>
+    <string name="not_sorted" msgid="7813496644889115530">"Nije poredano"</string>
+    <string name="sort_dimension_name" msgid="6325591541414177579">"Ime"</string>
+    <string name="sort_dimension_summary" msgid="7724534446881397860">"Sažetak"</string>
+    <string name="sort_dimension_date" msgid="4231005651895254033">"Izmijenjeno"</string>
+    <string name="sort_dimension_size" msgid="2190547351159472884">"Veličina"</string>
+    <string name="sort_direction_ascending" msgid="5882787683763248102">"Rastuće"</string>
+    <string name="sort_direction_descending" msgid="1729187589765894076">"Opadajuće"</string>
+    <string name="drawer_open" msgid="8071673398187261741">"Prikaži korijenske fordere"</string>
+    <string name="drawer_close" msgid="4263880768630848848">"Sakrij korijenske foldere"</string>
+    <string name="save_error" msgid="8631128801982095782">"Dokument nije pohranjen"</string>
+    <string name="create_error" msgid="3092144450044861994">"Mapa nije kreirana"</string>
+    <string name="query_error" msgid="6625421453613879336">"Trenutno nije moguće učitati sadržaj"</string>
+    <string name="root_recent" msgid="1080156975424341623">"Nedavno"</string>
+    <string name="root_available_bytes" msgid="8269870862691408864">"<xliff:g id="SIZE">%1$s</xliff:g> slobodno"</string>
+    <string name="root_type_service" msgid="6521366147466512289">"Usluge pohranjivanja"</string>
+    <string name="root_type_shortcut" msgid="6059343175525442279">"Prečice"</string>
+    <string name="root_type_device" msgid="1713604128005476585">"Uređaji"</string>
+    <string name="root_type_apps" msgid="8646073235029886342">"Više aplikacija"</string>
+    <string name="empty" msgid="5300254272613103004">"Nema stavki"</string>
+    <string name="no_results" msgid="2371026325236359209">"Nema podudarnih rezultata u %1$s"</string>
+    <string name="toast_no_application" msgid="7555319548595113121">"Nije moguće otvoriti fajl"</string>
+    <string name="toast_failed_delete" msgid="3453846588205817591">"Nije moguće izbrisati neke dokumente"</string>
+    <string name="share_via" msgid="8725082736005677161">"Podijeli koristeći aplikaciju"</string>
+    <string name="copy_notification_title" msgid="52256435625098456">"Kopiraju se fajlovi"</string>
+    <string name="move_notification_title" msgid="3173424987049347605">"Premještanje fajlova"</string>
+    <string name="delete_notification_title" msgid="2512757431856830792">"Brisanje fajlova"</string>
+    <string name="copy_remaining" msgid="5390517377265177727">"Još <xliff:g id="DURATION">%s</xliff:g>"</string>
+    <plurals name="copy_begin" formatted="false" msgid="3926184736640418042">
+      <item quantity="one">Kopira se <xliff:g id="COUNT_1">%1$d</xliff:g> fajl.</item>
+      <item quantity="few">Kopiraju se <xliff:g id="COUNT_1">%1$d</xliff:g> fajla.</item>
+      <item quantity="other">Kopira se <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova.</item>
+    </plurals>
+    <plurals name="move_begin" formatted="false" msgid="6049210105852581598">
+      <item quantity="one">Premješta se <xliff:g id="COUNT_1">%1$d</xliff:g> fajl.</item>
+      <item quantity="few">Premještaju se <xliff:g id="COUNT_1">%1$d</xliff:g> fajla.</item>
+      <item quantity="other">Premješta se <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova.</item>
+    </plurals>
+    <plurals name="deleting" formatted="false" msgid="1347549374456757591">
+      <item quantity="one">Briše se <xliff:g id="COUNT_1">%1$d</xliff:g> fajl.</item>
+      <item quantity="few">Brišu se <xliff:g id="COUNT_1">%1$d</xliff:g> fajla.</item>
+      <item quantity="other">Briše se <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova.</item>
+    </plurals>
+    <string name="undo" msgid="2902438994196400565">"Opozovi radnju"</string>
+    <string name="copy_preparing" msgid="5326063807006898223">"Priprema se kopiranje…"</string>
+    <string name="move_preparing" msgid="8742573245485449429">"Priprema za premještanje…"</string>
+    <string name="delete_preparing" msgid="6513863752916028147">"Priprema za brisanje…"</string>
+    <string name="delete_progress" msgid="2627631054702306423">"<xliff:g id="COUNT_0">%1$d</xliff:g> / <xliff:g id="TOTALCOUNT">%2$d</xliff:g>"</string>
+    <plurals name="copy_error_notification_title" formatted="false" msgid="7406207967429915000">
+      <item quantity="one">Nije moguće kopirati <xliff:g id="COUNT_1">%1$d</xliff:g> fajl</item>
+      <item quantity="few">Nije moguće kopirati <xliff:g id="COUNT_1">%1$d</xliff:g> fajla</item>
+      <item quantity="other">Nije moguće kopirati <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova</item>
+    </plurals>
+    <plurals name="move_error_notification_title" formatted="false" msgid="7841920776201038994">
+      <item quantity="one">Nije moguće premjestiti <xliff:g id="COUNT_1">%1$d</xliff:g> fajl</item>
+      <item quantity="few">Nije moguće premjestiti <xliff:g id="COUNT_1">%1$d</xliff:g> fajla</item>
+      <item quantity="other">Nije moguće premjestiti <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova</item>
+    </plurals>
+    <plurals name="delete_error_notification_title" formatted="false" msgid="4925525467677907298">
+      <item quantity="one">Nije bilo moguće izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajl</item>
+      <item quantity="few">Nije bilo moguće izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajla</item>
+      <item quantity="other">Nije bilo moguće izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova</item>
+    </plurals>
+    <string name="notification_touch_for_details" msgid="2385563502445129570">"Dodirnite za prikaz detalja"</string>
+    <string name="close" msgid="905969391788869975">"Zatvori"</string>
+    <string name="copy_failure_alert_content" msgid="161721873402101825">"Nisu kopirani sljedeći fajlovi: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="move_failure_alert_content" msgid="6586182218105304719">"Nisu premješteni sljedeći fajlovi: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="delete_failure_alert_content" msgid="7856890428816304981">"Nisu izbrisani sljedeći fajlovi: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <string name="copy_converted_warning_content" msgid="6481928162944612074">"Ovi fajlovi su pretvoreni u drugi format: <xliff:g id="LIST">%1$s</xliff:g>"</string>
+    <plurals name="clipboard_files_clipped" formatted="false" msgid="139644798886220070">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> fajl je kopiran u međuspremnik.</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> fajla su kopirana u međuspremnik.</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> fajlova je kopirano u međuspremnik.</item>
+    </plurals>
+    <string name="clipboard_files_cannot_paste" msgid="5700451161181106925">"Odabrane fajlove nije moguće zalijepiti na ovu lokaciju."</string>
+    <string name="rename_error" msgid="6700093173508118635">"Dokument nije preimenovan"</string>
+    <string name="menu_eject_root" msgid="9215040039374893613">"Izbaci"</string>
+    <string name="notification_copy_files_converted_title" msgid="6916768494891833365">"Neki fajlovi su pretvoreni u drugi format"</string>
+    <string name="open_external_dialog_request" msgid="8173558471322861268">"Želite li aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> odobriti pristup direktoriju <xliff:g id="DIRECTORY"><i>^2</i></xliff:g> na pohrani <xliff:g id="STORAGE"><i>^3</i></xliff:g>?"</string>
+    <string name="open_external_dialog_request_primary_volume" msgid="2240992164087948176">"Želite li aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> odobriti pristup direktoriju <xliff:g id="DIRECTORY"><i>^2</i></xliff:g>?"</string>
+    <string name="open_external_dialog_root_request" msgid="6776729293982633">"Želite li aplikaciji <xliff:g id="APPNAME"><b>^1</b></xliff:g> odobriti pristup svojim podacima, uključujući fotografije i videozapise, na pohrani: <xliff:g id="STORAGE"><i>^2</i></xliff:g>?"</string>
+    <string name="never_ask_again" msgid="525908236522201138">"Ne pitaj ponovo"</string>
+    <string name="allow" msgid="1275746941353040309">"Dozvoli"</string>
+    <string name="deny" msgid="5127201668078153379">"Odbij"</string>
+    <plurals name="elements_selected" formatted="false" msgid="4448165978637163692">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> odabran</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> odabrana</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> odabranih</item>
+    </plurals>
+    <plurals name="elements_dragged" formatted="false" msgid="5932571296037626279">
+      <item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> stavka</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%1$d</xliff:g> stavke</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> stavki</item>
+    </plurals>
+    <string name="delete_filename_confirmation_message" msgid="8338069763240613258">"Želite li izbrisati \"<xliff:g id="NAME">%1$s</xliff:g>\"?"</string>
+    <string name="delete_foldername_confirmation_message" msgid="9084085260877704140">"Želite li izbrisati folder \"<xliff:g id="NAME">%1$s</xliff:g>\" i njegov sadržaj?"</string>
+    <plurals name="delete_files_confirmation_message" formatted="false" msgid="4866664063250034142">
+      <item quantity="one">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajl?</item>
+      <item quantity="few">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajla?</item>
+      <item quantity="other">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> fajlova.</item>
+    </plurals>
+    <plurals name="delete_folders_confirmation_message" formatted="false" msgid="1028946402799686388">
+      <item quantity="one">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> folder i njihov sadržaj?</item>
+      <item quantity="few">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> foldera i njihov sadržaj?</item>
+      <item quantity="other">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> foldera i njihov sadržaj?</item>
+    </plurals>
+    <plurals name="delete_items_confirmation_message" formatted="false" msgid="7285090426511028179">
+      <item quantity="one">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> stavku?</item>
+      <item quantity="few">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> stavke?</item>
+      <item quantity="other">Želite li izbrisati <xliff:g id="COUNT_1">%1$d</xliff:g> stavki?</item>
+    </plurals>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 46ca582..a5b1280 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -17,4 +17,8 @@
     <declare-styleable name="HighlightedItemView">
         <attr name="state_highlighted" format="boolean"/>
     </declare-styleable>
+    <declare-styleable name="DroppableItemView">
+        <attr name="state_droppable" format="boolean"/>
+        <attr name="state_drop_hovered" format="boolean"/>
+    </declare-styleable>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index cb4b04c..a51652a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -24,7 +24,7 @@
     <color name="menu_search_background">@android:color/transparent</color>
 
     <color name="primary_dark">@*android:color/primary_dark_device_default_settings</color>
-    <color name="primary">@*android:color/primary_text_default_material_light</color>
+    <color name="primary">@*android:color/primary_device_default_settings</color>
     <color name="accent">@*android:color/accent_device_default_light</color>
     <color name="accent_dark">@*android:color/accent_device_default_dark</color>
 
@@ -34,12 +34,14 @@
     <color name="root_activated_color">@*android:color/accent_device_default_700</color>
 
     <!-- TODO: Would be nice to move this to a color-set, but not sure how to support animation -->
-    <color name="item_doc_background">#ffffffff</color>
+    <color name="item_doc_title">@*android:color/primary_text_default_material_light</color>
+    <color name="item_doc_background">@*android:color/white</color>
     <color name="item_doc_background_disabled">#fff4f4f4</color>
     <color name="item_doc_background_selected">@*android:color/accent_device_default_50</color>
     <color name="item_breadcrumb_background_hovered">#1affffff</color>
+    <color name="item_doc_droppable_background">#ffe8f0fb</color>
+    <color name="item_doc_not_droppable_background">#ffe0e0e0</color>
 
-    <color name="item_drag_shadow_background">#fffafafa</color>
-    <color name="item_drag_shadow_background_no_drop">#ffffb6c1</color>
+    <color name="item_drag_shadow_background">@*android:color/white</color>
 
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ecd598e..2c4ff99 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -51,4 +51,7 @@
 
     <dimen name="dropdown_sort_widget_margin">20dp</dimen>
     <dimen name="dropdown_sort_widget_icon_size">30dp</dimen>
+
+    <dimen name="drop_icon_height">15dp</dimen>
+    <dimen name="drop_icon_width">15dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ebb92f3..52a3fd0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -202,8 +202,8 @@
         <item quantity="one">Copied <xliff:g id="count" example="1">%1$d</xliff:g> file to clipboard.</item>
         <item quantity="other">Copied <xliff:g id="count" example="3">%1$d</xliff:g> files to clipboard.</item>
     </plurals>
-    <!-- Toast shown when a user tries to paste files into an unsupported location. -->
-    <string name="clipboard_files_cannot_paste">Cannot paste the selected files in this location.</string>
+    <!-- Toast shown when the file operation is not supported [CHAR LIMIT=48] -->
+    <string name="file_operation_error">File operation is not supported.</string>
     <!-- Toast shown when renaming document failed with an error [CHAR LIMIT=48] -->
     <string name="rename_error">Failed to rename document</string>
     <!-- Context Menu item that ejects the root selected [CHAR LIMIT=24] -->
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 86093f9..586b769 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -35,8 +35,8 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
-import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.AnimationView;
+import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.FocusHandler;
 import com.android.documentsui.files.LauncherActivity;
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index d92ebbb..5f0449c 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -26,6 +26,7 @@
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.Model;
 
 public interface ActionHandler {
 
@@ -82,4 +83,10 @@
      * app.
      */
     void initLocation(Intent intent);
+
+    /**
+     * Allow action handler to be initialized in a new scope.
+     * @return
+     */
+    <T extends ActionHandler> T reset(Model model, boolean searchMode);
 }
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index e9360ab..947ba9c 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -37,48 +37,39 @@
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
-import android.support.v7.widget.RecyclerView;
 import android.util.Log;
-import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 
 import com.android.documentsui.AbstractActionHandler.CommonAddons;
-import com.android.documentsui.MenuManager.SelectionDetails;
+import com.android.documentsui.Injector.Injected;
 import com.android.documentsui.NavigationViewManager.Breadcrumb;
+import com.android.documentsui.archives.ArchivesProvider;
 import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.EventHandler;
-import com.android.documentsui.base.Events;
 import com.android.documentsui.base.LocalPreferences;
 import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.base.ScopedPreferences;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.base.State.ViewMode;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.DocumentsAdapter;
-import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsCache;
 import com.android.documentsui.selection.Selection;
-import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.sorting.SortController;
 import com.android.documentsui.sorting.SortModel;
-import com.android.documentsui.ui.DialogController;
-import com.android.documentsui.ui.MessageBuilder;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-public abstract class BaseActivity<T extends ActionHandler>
+public abstract class BaseActivity
         extends Activity implements CommonAddons, NavigationViewManager.Environment {
 
     private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
@@ -86,16 +77,16 @@
     protected SearchViewManager mSearchManager;
     protected State mState;
 
+    @Injected
+    protected Injector<?> mInjector;
+
     protected @Nullable RetainedState mRetainedState;
     protected RootsCache mRoots;
     protected DocumentsAccess mDocs;
-    protected MessageBuilder mMessages;
     protected DrawerController mDrawer;
-    protected NavigationViewManager mNavigator;
-    protected FocusManager mFocusManager;
-    protected SortController mSortController;
 
-    protected T mActions;
+    protected NavigationViewManager mNavigator;
+    protected SortController mSortController;
 
     private final List<EventListener> mEventListeners = new ArrayList<>();
     private final String mTag;
@@ -103,9 +94,8 @@
     @LayoutRes
     private int mLayoutId;
 
-    private RootsMonitor<BaseActivity<?>> mRootsMonitor;
+    private RootsMonitor<BaseActivity> mRootsMonitor;
 
-    private boolean mNavDrawerHasFocus;
     private long mStartTime;
 
     public BaseActivity(@LayoutRes int layoutId, String tag) {
@@ -119,61 +109,7 @@
     protected abstract void includeState(State initialState);
     protected abstract void onDirectoryCreated(DocumentInfo doc);
 
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment.
-     */
-    public abstract ActivityConfig getActivityConfig();
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment.
-     */
-    public abstract ScopedPreferences getScopedPreferences();
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment.
-     */
-    public abstract SelectionManager getSelectionManager(
-            DocumentsAdapter adapter, SelectionPredicate canSetState);
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment hosted menus.
-     */
-    public abstract MenuManager getMenuManager();
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment.
-     */
-    public abstract DialogController getDialogController();
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * fragment actions.
-     *
-     * Args can be null when called from a context lacking fragment, such as RootsFragment.
-     */
-    public abstract ActionHandler getActionHandler(@Nullable Model model, boolean searchMode);
-
-    /**
-     * Provides Activity a means of injection into and specialization of
-     * DirectoryFragment.
-     */
-    public abstract ActionModeController getActionModeController(
-            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view);
-
-    public final FocusManager getFocusManager(RecyclerView view, Model model) {
-        assert(mFocusManager != null);
-        return mFocusManager.reset(view, model);
-    }
-
-    public final MessageBuilder getMessages() {
-        assert(mMessages != null);
-        return mMessages;
-    }
+    public abstract Injector<?> getInjector();
 
     @CallSuper
     @Override
@@ -189,9 +125,9 @@
 
         setContentView(mLayoutId);
 
+        mInjector = getInjector();
         mState = getState(icicle);
-        mFocusManager = new FocusManager(getColor(R.color.accent_dark));
-        mDrawer = DrawerController.create(this, getActivityConfig());
+        mDrawer = DrawerController.create(this, mInjector.config);
         Metrics.logActivityLaunch(this, mState, intent);
 
         // we're really interested in retainining state in our very complex
@@ -200,7 +136,6 @@
         mRetainedState = (RetainedState) getLastNonConfigurationInstance();
         mRoots = DocumentsApplication.getRootsCache(this);
         mDocs = DocumentsAccess.create(this);
-        mMessages = new MessageBuilder(this);
 
         DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
         setActionBar(toolbar);
@@ -247,7 +182,7 @@
 
         mRootsMonitor = new RootsMonitor<>(
                 this,
-                mActions,
+                mInjector.actions,
                 mRoots,
                 mDocs,
                 mState,
@@ -299,8 +234,8 @@
 
         includeState(state);
 
-        state.showAdvanced =
-                Shared.mustShowDeviceRoot(intent) || getScopedPreferences().getShowDeviceRoot();
+        state.showAdvanced = Shared.mustShowDeviceRoot(intent)
+                || mInjector.prefs.getShowDeviceRoot();
 
         // Only show the toggle if advanced isn't forced enabled.
         state.showDeviceStorageOption = !Shared.mustShowDeviceRoot(intent);
@@ -346,7 +281,7 @@
             new GetRootDocumentTask(
                     root,
                     this,
-                    mActions::openContainerDocument)
+                    mInjector.actions::openContainerDocument)
                     .executeOnExecutor(getExecutorForCurrentDirectory());
         }
     }
@@ -478,8 +413,8 @@
         return (root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0;
     }
 
-    public static BaseActivity<?> get(Fragment fragment) {
-        return (BaseActivity<?>) fragment.getActivity();
+    public static BaseActivity get(Fragment fragment) {
+        return (BaseActivity) fragment.getActivity();
     }
 
     public State getDisplayState() {
@@ -498,7 +433,7 @@
         Metrics.logUserAction(this,
                 display ? Metrics.USER_ACTION_SHOW_ADVANCED : Metrics.USER_ACTION_HIDE_ADVANCED);
 
-        getScopedPreferences().setShowDeviceRoot(display);
+        mInjector.prefs.setShowDeviceRoot(display);
         mState.showAdvanced = display;
         RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
         invalidateOptionsMenu();
@@ -611,37 +546,6 @@
         super.onBackPressed();
     }
 
-    /**
-     * Declare a global key handler to route key events when there isn't a specific focus view. This
-     * covers the scenario where a user opens DocumentsUI and just starts typing.
-     *
-     * @param keyCode
-     * @param event
-     * @return
-     */
-    @CallSuper
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (Events.isNavigationKeyCode(keyCode)) {
-            // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
-            // stray navigation keystrokes focus the content pane, which is probably what the user
-            // is trying to do.
-            DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
-            if (df != null) {
-                df.requestFocus();
-                return true;
-            }
-        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
-            // Tab toggles focus on the navigation drawer.
-            toggleNavDrawerFocus();
-            return true;
-        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
-            popDir();
-            return true;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
     @VisibleForTesting
     public void addEventListener(EventListener listener) {
         mEventListeners.add(listener);
@@ -668,34 +572,12 @@
     }
 
     /**
-     * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
-     * locked, open/close it as appropriate.
-     */
-    void toggleNavDrawerFocus() {
-        boolean toogleHappened = false;
-        if (mNavDrawerHasFocus) {
-            mDrawer.setOpen(false);
-            DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
-            assert (df != null);
-            toogleHappened = df.requestFocus();
-        } else {
-            mDrawer.setOpen(true);
-            RootsFragment rf = RootsFragment.get(getFragmentManager());
-            assert (rf != null);
-            toogleHappened = rf.requestFocus();
-        }
-        if (toogleHappened) {
-            mNavDrawerHasFocus = !mNavDrawerHasFocus;
-        }
-    }
-
-    /**
      * Pops the top entry off the directory stack, and returns the user to the previous directory.
      * If the directory stack only contains one item, this method does nothing.
      *
      * @return Whether the stack was popped.
      */
-    private boolean popDir() {
+    protected boolean popDir() {
         if (mState.stack.size() > 1) {
             mState.stack.pop();
             refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
@@ -704,6 +586,12 @@
         return false;
     }
 
+    protected boolean focusSidebar() {
+        RootsFragment rf = RootsFragment.get(getFragmentManager());
+        assert (rf != null);
+        return rf.requestFocus();
+    }
+
     /**
      * Closes the activity when it's idle.
      */
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 8af1ab3..9cdc160 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -103,7 +103,7 @@
             cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
 
             if (mSearchMode && !Shared.ENABLE_OMC_API_FEATURES) {
-                // There is no findPath API. Enable filtering on folders in search mode.
+                // There is no findDocumentPath API. Enable filtering on folders in search mode.
                 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
             }
 
diff --git a/src/com/android/documentsui/DirectoryReloadLock.java b/src/com/android/documentsui/DirectoryReloadLock.java
index b44a963..4299914 100644
--- a/src/com/android/documentsui/DirectoryReloadLock.java
+++ b/src/com/android/documentsui/DirectoryReloadLock.java
@@ -16,7 +16,7 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.annotation.MainThread;
 import android.annotation.Nullable;
@@ -42,7 +42,7 @@
     public void block() {
         Shared.checkMainLoop();
         mPauseCount++;
-        if (DEBUG) Log.v(TAG, "Block count increments to " + mPauseCount + ".");
+        if (VERBOSE) Log.v(TAG, "Block count increments to " + mPauseCount + ".");
     }
 
     /**
@@ -54,7 +54,7 @@
         Shared.checkMainLoop();
         assert(mPauseCount > 0);
         mPauseCount--;
-        if (DEBUG) Log.v(TAG, "Block count decrements to " + mPauseCount + ".");
+        if (VERBOSE) Log.v(TAG, "Block count decrements to " + mPauseCount + ".");
         if (mPauseCount == 0 && mCallback != null) {
             mCallback.run();
             mCallback = null;
@@ -72,4 +72,4 @@
             mCallback = update;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java
index 80d4589..63f235c 100644
--- a/src/com/android/documentsui/DocumentsAccess.java
+++ b/src/com/android/documentsui/DocumentsAccess.java
@@ -46,7 +46,7 @@
     @Nullable DocumentInfo getArchiveDocument(Uri uri);
 
     boolean isDocumentUri(Uri uri);
-    @Nullable Path findPath(Uri uri) throws RemoteException;
+    @Nullable Path findDocumentPath(Uri uri) throws RemoteException;
 
     List<DocumentInfo> getDocuments(String authority, List<String> docIds) throws RemoteException;
 
@@ -116,11 +116,11 @@
         }
 
         @Override
-        public Path findPath(Uri docUri) throws RemoteException {
+        public Path findDocumentPath(Uri docUri) throws RemoteException {
             final ContentResolver resolver = mContext.getContentResolver();
             try (final ContentProviderClient client = DocumentsApplication
                     .acquireUnstableProviderOrThrow(resolver, docUri.getAuthority())) {
-                return DocumentsContract.findPath(client, docUri);
+                return DocumentsContract.findDocumentPath(client, docUri);
             }
         }
     }
diff --git a/src/com/android/documentsui/DragShadowBuilder.java b/src/com/android/documentsui/DragShadowBuilder.java
index d6877ec..24e037d 100644
--- a/src/com/android/documentsui/DragShadowBuilder.java
+++ b/src/com/android/documentsui/DragShadowBuilder.java
@@ -23,7 +23,6 @@
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.documentsui.base.DocumentInfo;
@@ -39,11 +38,9 @@
 
     private final View mShadowView;
     private final TextView mTitle;
-    private final ImageView mIcon;
+    private final DropBadgeView mIcon;
     private final int mWidth;
     private final int mHeight;
-    private final Drawable mDefaultBackground;
-    private final Drawable mNoDropBackground;
 
     public DragShadowBuilder(Context context) {
         mWidth = context.getResources().getDimensionPixelSize(R.dimen.drag_shadow_width);
@@ -51,12 +48,11 @@
 
         mShadowView = LayoutInflater.from(context).inflate(R.layout.drag_shadow_layout, null);
         mTitle = (TextView) mShadowView.findViewById(android.R.id.title);
-        mIcon = (ImageView) mShadowView.findViewById(android.R.id.icon);
+        mIcon = (DropBadgeView) mShadowView.findViewById(android.R.id.icon);
 
-        mDefaultBackground = context.getResources().getDrawable(R.drawable.drag_shadow_background,
-                null);
-        mNoDropBackground = context.getResources()
-                .getDrawable(R.drawable.drag_shadow_background_no_drop, null);
+        mShadowView
+                .setBackground(context.getResources().getDrawable(R.drawable.drag_shadow_background,
+                        null));
     }
 
     @Override
@@ -82,15 +78,17 @@
     }
 
     public void updateIcon(Drawable icon) {
-        mIcon.setImageDrawable(icon);
+        mIcon.updateIcon(icon);
     }
 
     public void resetBackground() {
-        mShadowView.setBackground(mDefaultBackground);
+        mIcon.setDropHovered(false);
+        mIcon.setEnabled(false);
     }
 
-    public void setNoDropBackground() {
-        mShadowView.setBackground(mNoDropBackground);
+    public void setAppearDroppable(boolean droppable) {
+        mIcon.setDropHovered(true);
+        mIcon.setDroppable(droppable);
     }
 
     /**
diff --git a/src/com/android/documentsui/DrawerController.java b/src/com/android/documentsui/DrawerController.java
index f8062a7..12f00ee 100644
--- a/src/com/android/documentsui/DrawerController.java
+++ b/src/com/android/documentsui/DrawerController.java
@@ -156,7 +156,7 @@
         }
 
         @Override
-        public void setDropTargetHighlight(View v, boolean highlight) {
+        public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
         }
 
         @Override
diff --git a/src/com/android/documentsui/DropBadgeView.java b/src/com/android/documentsui/DropBadgeView.java
new file mode 100644
index 0000000..8ed6701
--- /dev/null
+++ b/src/com/android/documentsui/DropBadgeView.java
@@ -0,0 +1,86 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.ImageView;
+
+/**
+ * Provides a way to encapsulate droppable badge toggling logic into a single class.
+ */
+public final class DropBadgeView extends ImageView {
+    private static final int[] STATE_DROPPABLE = {R.attr.state_droppable};
+    private static final int[] STATE_DROP_HOVERED = {R.attr.state_drop_hovered};
+
+    private boolean mDroppable = false;
+    private boolean mDropHovered = false;
+    private LayerDrawable mBackground;
+
+    public DropBadgeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final int badgeHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.drop_icon_height);
+        final int badgeWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.drop_icon_width);
+
+        Drawable okBadge = context.getResources().getDrawable(R.drawable.drop_badge_states, null);
+        Drawable defaultIcon = context.getResources()
+                .getDrawable(com.android.internal.R.drawable.ic_doc_generic, null);
+
+        Drawable[] list = {defaultIcon, okBadge};
+        mBackground = new LayerDrawable(list);
+
+        mBackground.setLayerGravity(1, Gravity.BOTTOM | Gravity.RIGHT);
+        mBackground.setLayerSize(1, badgeWidth, badgeHeight);
+
+        setBackground(mBackground);
+    }
+
+    @Override
+    public int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
+
+        if (mDroppable) {
+            mergeDrawableStates(drawableState, STATE_DROPPABLE);
+        }
+
+        if (mDropHovered) {
+            mergeDrawableStates(drawableState, STATE_DROP_HOVERED);
+        }
+
+        return drawableState;
+    }
+
+    public void setDroppable(boolean droppable) {
+        mDroppable = droppable;
+        refreshDrawableState();
+    }
+
+    public void setDropHovered(boolean hovered) {
+        mDropHovered = hovered;
+        refreshDrawableState();
+    }
+
+    public void updateIcon(Drawable icon) {
+        mBackground.setDrawable(0, icon);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index 8f5d388..1b8567e 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -17,6 +17,7 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
+import static com.android.documentsui.base.Shared.DEBUG;
 
 import android.annotation.ColorRes;
 import android.annotation.Nullable;
@@ -40,31 +41,64 @@
 
 import com.android.documentsui.base.EventListener;
 import com.android.documentsui.base.Events;
+import com.android.documentsui.base.Procedure;
 import com.android.documentsui.dirlist.DocumentHolder;
 import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.FocusHandler;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.Model.Update;
+import com.android.documentsui.selection.SelectionManager;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Timer;
 import java.util.TimerTask;
 
-/**
- * A class that handles navigation and focus within the DirectoryFragment.
- */
 public final class FocusManager implements FocusHandler {
     private static final String TAG = "FocusManager";
 
     private final ContentScope mScope = new ContentScope();
+
+    private final SelectionManager mSelectionMgr;
+    private final DrawerController mDrawer;
+    private final Procedure mRootsFocuser;
     private final TitleSearchHelper mSearchHelper;
 
-    public FocusManager(@ColorRes int color) {
+    private boolean mNavDrawerHasFocus;
+
+    public FocusManager(
+            SelectionManager selectionMgr,
+            DrawerController drawer,
+            Procedure rootsFocuser,
+            @ColorRes int color) {
+
+        mSelectionMgr = selectionMgr;
+        mDrawer = drawer;
+        mRootsFocuser = rootsFocuser;
+
         mSearchHelper = new TitleSearchHelper(color);
     }
 
     @Override
+    public boolean advanceFocusArea() {
+        boolean focusChanged = false;
+        if (mNavDrawerHasFocus) {
+            mDrawer.setOpen(false);
+            focusDirectoryList();
+        } else {
+            mDrawer.setOpen(true);
+            focusChanged = mRootsFocuser.run();
+        }
+
+        if (focusChanged) {
+            mNavDrawerHasFocus = !mNavDrawerHasFocus;
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
     public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
         // Search helper gets first crack, for doing type-to-focus.
         if (mSearchHelper.handleKey(doc, keyCode, event)) {
@@ -94,19 +128,26 @@
     }
 
     @Override
-    public void restoreLastFocus() {
+    public boolean focusDirectoryList() {
         if (mScope.adapter.getItemCount() == 0) {
-            // Nothing to focus.
-            return;
+            if (DEBUG) Log.v(TAG, "Nothing to focus.");
+            return false;
         }
 
-        if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
-            // The system takes care of situations when a view is no longer on screen, etc,
-            focusItem(mScope.lastFocusPosition);
-        } else {
-            // Focus the first visible item
-            focusItem(mScope.layout.findFirstVisibleItemPosition());
+        // If there's a selection going on, we don't want to grant user the ability to focus
+        // on any individfocusSomethingual item to prevent ambiguity in operations (Cut selection
+        // vs. Cut focused
+        // item)
+        if (mSelectionMgr.hasSelection()) {
+            if (DEBUG) Log.v(TAG, "Existing selection found. No focus will be done.");
+            return false;
         }
+
+        final int focusPos = (mScope.lastFocusPosition != RecyclerView.NO_POSITION)
+                ? mScope.lastFocusPosition
+                : mScope.layout.findFirstVisibleItemPosition();
+        focusItem(focusPos);
+        return true;
     }
 
     /*
@@ -128,8 +169,8 @@
 
     /*
      * Attempts to put focus on the document associated with the given modelId. If item does not
-     * exist yet in the layout, this sets a pending modelId to be used when
-     * {@code #applyPendingFocus()} is called next time.
+     * exist yet in the layout, this sets a pending modelId to be used when {@code
+     * #applyPendingFocus()} is called next time.
      */
     @Override
     public void focusDocument(String modelId) {
@@ -147,6 +188,11 @@
     }
 
     @Override
+    public boolean hasFocusedItem() {
+        return mScope.lastFocusPosition != RecyclerView.NO_POSITION;
+    }
+
+    @Override
     public @Nullable String getFocusModelId() {
         if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
             DocumentHolder holder = (DocumentHolder) mScope.view
@@ -230,13 +276,14 @@
     }
 
     /**
-     * Given a PgUp/PgDn event and the current view, find the position of the target view.
-     * This returns:
-     * <li>The position of the topmost (or bottom-most) visible item, if the current item is not
-     *     the top- or bottom-most visible item.
+     * Given a PgUp/PgDn event and the current view, find the position of the target view. This
+     * returns:
+     * <li>The position of the topmost (or bottom-most) visible item, if the current item is not the
+     * top- or bottom-most visible item.
      * <li>The position of an item that is one page's worth of items up (or down) if the current
-     *      item is the top- or bottom-most visible item.
+     * item is the top- or bottom-most visible item.
      * <li>The first (or last) item, if paging up (or down) would go past those limits.
+     *
      * @param view The view that received the key event.
      * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
      * @param event
@@ -311,8 +358,8 @@
                         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);
+                                RecyclerView.ViewHolder vh = view
+                                        .findViewHolderForAdapterPosition(pos);
                                 if (vh != null) {
                                     if (vh.itemView.requestFocus() && callback != null) {
                                         callback.onFocus(vh.itemView);
@@ -331,9 +378,7 @@
         }
     }
 
-    /**
-     * @return Whether the layout manager is currently in a grid-configuration.
-     */
+    /** @return Whether the layout manager is currently in a grid-configuration. */
     private boolean inGridMode() {
         return mScope.layout.getSpanCount() > 1;
     }
@@ -350,7 +395,7 @@
      * highlights instances of the search term found in the view.
      */
     private class TitleSearchHelper {
-        static private final int SEARCH_TIMEOUT = 500;  // ms
+        private static final int SEARCH_TIMEOUT = 500; // ms
 
         private final KeyListener mTextListener = new TextKeyListener(Capitalize.NONE, false);
         private final Editable mSearchString = Editable.Factory.getInstance().newEditable("");
@@ -456,25 +501,28 @@
             for (int pos = 0; pos < mIndex.size(); pos++) {
                 String title = mIndex.get(pos);
                 if (title != null && title.startsWith(searchString)) {
-                    focusItem(pos, new FocusCallback() {
-                        @Override
-                        public void onFocus(View view) {
-                            mHighlighter.applyHighlight(view);
-                            // Using a timer repeat period of SEARCH_TIMEOUT/2 means the amount of
-                            // time between the last keystroke and a search expiring is actually
-                            // between 500 and 750 ms. A smaller timer period results in less
-                            // variability but does more polling.
-                            mTimer.schedule(new TimeoutTask(), 0, SEARCH_TIMEOUT / 2);
-                        }
-                    });
+                    focusItem(
+                            pos,
+                            new FocusCallback() {
+                                @Override
+                                public void onFocus(View view) {
+                                    mHighlighter.applyHighlight(view);
+                                    // Using a timer repeat period of SEARCH_TIMEOUT/2 means the
+                                    // amount of
+                                    // time between the last keystroke and a search expiring is
+                                    // actually
+                                    // between 500 and 750 ms. A smaller timer period results in
+                                    // less
+                                    // variability but does more polling.
+                                    mTimer.schedule(new TimeoutTask(), 0, SEARCH_TIMEOUT / 2);
+                                }
+                            });
                     break;
                 }
             }
         }
 
-        /**
-         * Ends the current search (see {@link #search()}.
-         */
+        /** Ends the current search (see {@link #search()}. */
         private void endSearch() {
             if (mActive) {
                 mScope.model.removeUpdateListener(mModelListener);
@@ -524,12 +572,13 @@
                 long now = SystemClock.uptimeMillis();
                 if ((now - last) > SEARCH_TIMEOUT) {
                     // endSearch must run on the main thread because it does UI work
-                    mUiRunner.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            endSearch();
-                        }
-                    });
+                    mUiRunner.post(
+                            new Runnable() {
+                                @Override
+                                public void run() {
+                                    endSearch();
+                                }
+                            });
                 }
             }
         };
@@ -538,8 +587,8 @@
             private Spannable mCurrentHighlight;
 
             /**
-             * Applies title highlights to the given view. The view must have a title field that is a
-             * spannable text field.  If this condition is not met, this function does nothing.
+             * Applies title highlights to the given view. The view must have a title field that is
+             * a spannable text field. If this condition is not met, this function does nothing.
              *
              * @param view
              */
@@ -561,8 +610,8 @@
             }
 
             /**
-             * Removes title highlights from the given view. The view must have a title field that is a
-             * spannable text field.  If this condition is not met, this function does nothing.
+             * Removes title highlights from the given view. The view must have a title field that
+             * is a spannable text field. If this condition is not met, this function does nothing.
              *
              * @param view
              */
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index 240a0d3..598decc 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -118,7 +118,7 @@
     }
 
     @Override
-    public void setDropTargetHighlight(View v, boolean highlight) {
+    public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
         RecyclerView.ViewHolder vh = getChildViewHolder(v);
         if (vh instanceof BreadcrumbHolder) {
             ((BreadcrumbHolder) vh).setHighlighted(highlight);
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
new file mode 100644
index 0000000..4c767ae
--- /dev/null
+++ b/src/com/android/documentsui/Injector.java
@@ -0,0 +1,122 @@
+/*
+ * 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.documentsui.MenuManager.SelectionDetails;
+import com.android.documentsui.base.EventHandler;
+import com.android.documentsui.base.ScopedPreferences;
+import com.android.documentsui.dirlist.DocumentsAdapter;
+import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
+import com.android.documentsui.ui.DialogController;
+import com.android.documentsui.ui.MessageBuilder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Provides access to runtime dependencies.
+ */
+public class Injector<T extends ActionHandler> {
+
+    public final ActivityConfig config;
+    public final ScopedPreferences prefs;
+    public final MessageBuilder messages;
+
+    public MenuManager menuManager;
+    public DialogController dialogs;
+
+    @ContentScoped
+    public ActionModeController actionModeController;
+
+    @ContentScoped
+    public T actions;
+
+    @ContentScoped
+    public FocusManager focusManager;
+
+    @ContentScoped
+    public SelectionManager selectionMgr;
+
+    // must be initialized before calling super.onCreate because prefs
+    // are used in State initialization.
+    public Injector(
+            ActivityConfig config,
+            ScopedPreferences prefs,
+            MessageBuilder messages,
+            DialogController dialogs) {
+
+        this.config = config;
+        this.prefs = prefs;
+        this.messages = messages;
+        this.dialogs = dialogs;
+    }
+
+    public FocusManager getFocusManager(RecyclerView view, Model model) {
+        assert (focusManager != null);
+        return focusManager.reset(view, model);
+    }
+
+    public SelectionManager getSelectionManager(
+            DocumentsAdapter adapter, SelectionPredicate canSetState) {
+        return selectionMgr.reset(adapter, canSetState);
+    }
+
+    public final ActionModeController getActionModeController(
+            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view) {
+        return actionModeController.reset(selectionDetails, menuItemClicker, view);
+    }
+
+    public T getActionHandler(
+            @Nullable Model model, boolean searchMode) {
+
+        // provide our friend, RootsFragment, early access to this special feature!
+        if (model == null) {
+            return actions;
+        }
+
+        return actions.reset(model, searchMode);
+    }
+
+    /**
+     * Decorates a field that that is injected.
+     */
+    @Retention(SOURCE)
+    @Target(FIELD)
+    public @interface Injected {
+
+    }
+
+    /**
+     * Decorates a field that holds an object that must be reset in the current content scope
+     * (i.e. DirectoryFragment). Fields decorated with this must have an associated
+     * accessor on Injector that, when call, reset the object for the calling context.
+     */
+    @Retention(SOURCE)
+    @Target(FIELD)
+    public @interface ContentScoped {
+
+    }
+}
diff --git a/src/com/android/documentsui/ItemDragListener.java b/src/com/android/documentsui/ItemDragListener.java
index 79f71d9..87c1274 100644
--- a/src/com/android/documentsui/ItemDragListener.java
+++ b/src/com/android/documentsui/ItemDragListener.java
@@ -68,7 +68,7 @@
                 return true;
             case DragEvent.ACTION_DRAG_EXITED:
             case DragEvent.ACTION_DRAG_ENDED:
-                handleExitedEndedEvent(v);
+                handleExitedEndedEvent(v, event);
                 return true;
             case DragEvent.ACTION_DROP:
                 return handleDropEvent(v, event);
@@ -80,10 +80,10 @@
     private void handleEnteredEvent(View v, DragEvent event) {
         mDragHost.onDragEntered(v, event.getLocalState());
         @Nullable TimerTask task = createOpenTask(v, event);
+        mDragHost.setDropTargetHighlight(v, event.getLocalState(), true);
         if (task == null) {
             return;
         }
-        mDragHost.setDropTargetHighlight(v, true);
         v.setTag(R.id.drag_hovering_tag, task);
         mHoverTimer.schedule(task, SPRING_TIMEOUT);
     }
@@ -95,8 +95,8 @@
         }
     }
 
-    private void handleExitedEndedEvent(View v) {
-        mDragHost.setDropTargetHighlight(v, false);
+    private void handleExitedEndedEvent(View v, DragEvent event) {
+        mDragHost.setDropTargetHighlight(v, event.getLocalState(), false);
 
         TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag);
         if (task != null) {
@@ -154,9 +154,10 @@
         /**
          * Highlights/unhighlights the view to visually indicate this view is being hovered.
          * @param v the view being hovered
+         * @param localState the Local state object  given by DragEvent
          * @param highlight true if highlight the view; false if unhighlight it
          */
-        void setDropTargetHighlight(View v, boolean highlight);
+        void setDropTargetHighlight(View v, Object localState, boolean highlight);
 
         /**
          * Notifies hovering timeout has elapsed
diff --git a/src/com/android/documentsui/LoadDocStackTask.java b/src/com/android/documentsui/LoadDocStackTask.java
index a73945c..712f5e1 100644
--- a/src/com/android/documentsui/LoadDocStackTask.java
+++ b/src/com/android/documentsui/LoadDocStackTask.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.net.Uri;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Path;
 import android.util.Log;
 
@@ -60,14 +61,23 @@
 
     @Override
     public @Nullable DocumentStack run(Uri... uris) {
-        final Uri docUri = uris[0];
-        if (Shared.ENABLE_OMC_API_FEATURES && mDocs.isDocumentUri(docUri)) {
+        if (Shared.ENABLE_OMC_API_FEATURES && mDocs.isDocumentUri(uris[0])) {
+            final Uri docUri;
+            if (DocumentsContract.isTreeUri(uris[0])) {
+                // Reconstruct tree URI into a plain document URI so that we can get the full path
+                // to the root.
+                final String docId = DocumentsContract.getDocumentId(uris[0]);
+                docUri = DocumentsContract.buildDocumentUri(uris[0].getAuthority(), docId);
+            } else {
+                docUri = uris[0];
+            }
+
             try {
-                final Path path = mDocs.findPath(docUri);
+                final Path path = mDocs.findDocumentPath(docUri);
                 if (path != null) {
                     return buildStack(docUri.getAuthority(), path);
                 } else {
-                    Log.i(TAG, "Remote provider doesn't support findPath.");
+                    Log.i(TAG, "Remote provider doesn't support findDocumentPath.");
                 }
             } catch (Exception e) {
                 Log.e(TAG, "Failed to build document stack for uri: " + docUri, e);
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index a3732e3..b12353d 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -17,6 +17,7 @@
 package com.android.documentsui;
 
 import android.app.Fragment;
+import android.view.KeyboardShortcutGroup;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -27,10 +28,14 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.files.FilesActivity;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.List;
+import java.util.function.IntFunction;
+
 public abstract class MenuManager {
 
     final protected SearchViewManager mSearchManager;
@@ -196,6 +201,9 @@
         updateSettings(settings, root);
     }
 
+    public abstract void updateKeyboardShortcutsMenu(
+            List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier);
+
     protected void updateModePicker(MenuItem grid, MenuItem list) {
         grid.setVisible(mState.derivedMode != State.MODE_GRID);
         list.setVisible(mState.derivedMode != State.MODE_LIST);
@@ -296,9 +304,9 @@
     }
 
     public static class DirectoryDetails {
-        private final BaseActivity<?> mActivity;
+        private final BaseActivity mActivity;
 
-        public DirectoryDetails(BaseActivity<?> activity) {
+        public DirectoryDetails(BaseActivity activity) {
             mActivity = activity;
         }
 
diff --git a/src/com/android/documentsui/Metrics.java b/src/com/android/documentsui/Metrics.java
index 15cf902..268a7bf 100644
--- a/src/com/android/documentsui/Metrics.java
+++ b/src/com/android/documentsui/Metrics.java
@@ -39,7 +39,7 @@
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java
index 65f9000..07e3824 100644
--- a/src/com/android/documentsui/NavigationViewManager.java
+++ b/src/com/android/documentsui/NavigationViewManager.java
@@ -16,7 +16,7 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
@@ -99,7 +99,7 @@
         if (mState.stack.size() <= 1) {
             mBreadcrumb.show(false);
             String title = mEnv.getCurrentRoot().title;
-            if (DEBUG) Log.v(TAG, "New toolbar title is: " + title);
+            if (VERBOSE) Log.v(TAG, "New toolbar title is: " + title);
             mToolbar.setTitle(title);
         } else {
             mBreadcrumb.show(true);
@@ -107,7 +107,7 @@
             mBreadcrumb.postUpdate();
         }
 
-        if (DEBUG) Log.v(TAG, "Final toolbar title is: " + mToolbar.getTitle());
+        if (VERBOSE) Log.v(TAG, "Final toolbar title is: " + mToolbar.getTitle());
     }
 
     // Hamburger if drawer is present, else sad nullness.
diff --git a/src/com/android/documentsui/SharedInputHandler.java b/src/com/android/documentsui/SharedInputHandler.java
new file mode 100644
index 0000000..e14084f
--- /dev/null
+++ b/src/com/android/documentsui/SharedInputHandler.java
@@ -0,0 +1,51 @@
+/*
+ * 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.view.KeyEvent;
+
+import com.android.documentsui.base.Events;
+import com.android.documentsui.base.Procedure;
+
+public class SharedInputHandler {
+
+    private final FocusManager mFocusManager;
+    private Procedure mDirPopper;
+
+    public SharedInputHandler(FocusManager focusManager, Procedure dirPopper) {
+        mFocusManager = focusManager;
+        mDirPopper = dirPopper;
+    }
+
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (Events.isNavigationKeyCode(keyCode)) {
+            // Forward all unclaimed navigation keystrokes to the directory list.
+            // This causes any stray navigation keystrokes to focus the content pane,
+            // which is probably what the user is trying to do.
+            mFocusManager.focusDirectoryList();
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+            // Tab toggles focus on the navigation drawer.
+            mFocusManager.advanceFocusArea();
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+            mDirPopper.run();
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/documentsui/archives/Archive.java b/src/com/android/documentsui/archives/Archive.java
index 8706b25..410ec6a 100644
--- a/src/com/android/documentsui/archives/Archive.java
+++ b/src/com/android/documentsui/archives/Archive.java
@@ -36,6 +36,7 @@
 import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.jar.StrictJarFile;
 import android.webkit.MimeTypeMap;
 
 import com.android.internal.util.Preconditions;
@@ -44,6 +45,7 @@
 
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -51,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -58,7 +61,6 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Provides basic implementation for creating, extracting and accessing
@@ -69,10 +71,6 @@
 public class Archive implements Closeable {
     private static final String TAG = "Archive";
 
-    // Stores file representations of file descriptors. Used to open pipes
-    // by path.
-    private static final String PROC_FD_PATH = "/proc/self/fd/";
-
     public static final String[] DEFAULT_PROJECTION = new String[] {
             Document.COLUMN_DOCUMENT_ID,
             Document.COLUMN_DISPLAY_NAME,
@@ -84,21 +82,24 @@
     private final Context mContext;
     private final Uri mArchiveUri;
     private final Uri mNotificationUri;
-    private final ZipFile mZipFile;
+    private final StrictJarFile mZipFile;
     private final ExecutorService mExecutor;
     private final Map<String, ZipEntry> mEntries;
     private final Map<String, List<ZipEntry>> mTree;
 
     private Archive(
             Context context,
-            File file,
+            @Nullable File file,
+            @Nullable FileDescriptor fd,
             Uri archiveUri,
             @Nullable Uri notificationUri)
             throws IOException {
         mContext = context;
         mArchiveUri = archiveUri;
         mNotificationUri = notificationUri;
-        mZipFile = new ZipFile(file);
+        mZipFile = file != null ?
+                new StrictJarFile(file.getPath(), false /* verify */, false /* signatures */) :
+                new StrictJarFile(fd, false /* verify */, false /* signatures */);
         mExecutor = Executors.newSingleThreadExecutor();
 
         // Build the tree structure in memory.
@@ -106,11 +107,11 @@
 
         mEntries = new HashMap<>();
         ZipEntry entry;
-        final List<? extends ZipEntry> entries = Collections.list(mZipFile.entries());
-        final Stack<ZipEntry> stack = new Stack<>();
         String entryPath;
-        for (int i = entries.size() - 1; i >= 0; i--) {
-            entry = entries.get(i);
+        final Iterator<ZipEntry> it = mZipFile.iterator();
+        final Stack<ZipEntry> stack = new Stack<>();
+        while (it.hasNext()) {
+            entry = it.next();
             if (entry.isDirectory() != entry.getName().endsWith("/")) {
                 throw new IOException(
                         "Directories must have a trailing slash, and files must not.");
@@ -207,12 +208,10 @@
             Context context, ParcelFileDescriptor descriptor, Uri archiveUri,
             @Nullable Uri notificationUri)
             throws IOException {
-        // TODO: Temporarily disable non-snapshot code path, as /proc/self/fd/* files
-        // are not openable across processes. b/32228589
-        // if (canSeek(descriptor)) {
-        //     return new Archive(context, new File(PROC_FD_PATH + descriptor.getFd()),
-        //             archiveUri, notificationUri);
-        // }
+        if (canSeek(descriptor)) {
+            return new Archive(context, null, descriptor.getFileDescriptor(), archiveUri,
+                    notificationUri);
+        }
 
         // Fallback for non-seekable file descriptors.
         File snapshotFile = null;
@@ -237,9 +236,9 @@
                     outputStream.write(buffer, 0, bytes);
                 }
                 outputStream.flush();
-                return new Archive(context, snapshotFile, archiveUri,
-                        notificationUri);
             }
+            return new Archive(context, snapshotFile, null, archiveUri,
+                    notificationUri);
         } finally {
             // On UNIX the file will be still available for processes which opened it, even
             // after deleting it. Remove it ASAP, as it won't be used by anyone else.
@@ -490,7 +489,11 @@
         mExecutor.execute(new Runnable() {
             @Override
             public void run() {
-                IoUtils.closeQuietly(mZipFile);
+                try {
+                    mZipFile.close();
+                } catch (IOException e) {
+                    // Silent close.
+                }
             }
         });
         mExecutor.shutdown();
diff --git a/src/com/android/documentsui/base/Procedure.java b/src/com/android/documentsui/base/Procedure.java
new file mode 100644
index 0000000..b28d16d
--- /dev/null
+++ b/src/com/android/documentsui/base/Procedure.java
@@ -0,0 +1,26 @@
+/*
+ * 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.base;
+
+/**
+ * Functional interface like a {@link Runnable}, but returning a boolean value
+ * indicating if the Procedure succeeded.
+ */
+@FunctionalInterface
+public interface Procedure {
+
+    boolean run();
+}
diff --git a/src/com/android/documentsui/base/RootInfo.java b/src/com/android/documentsui/base/RootInfo.java
index 50b9603..0b46417 100644
--- a/src/com/android/documentsui/base/RootInfo.java
+++ b/src/com/android/documentsui/base/RootInfo.java
@@ -19,7 +19,7 @@
 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
 import static com.android.documentsui.base.DocumentInfo.getCursorLong;
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
-import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 import static com.android.documentsui.base.Shared.compareToIgnoreCaseNullable;
 
 import android.annotation.IntDef;
@@ -233,7 +233,7 @@
             derivedType = TYPE_OTHER;
         }
 
-        if (DEBUG) Log.v(TAG, "Deriving fields: " + this);
+        if (VERBOSE) Log.v(TAG, "Derived fields: " + this);
     }
 
     public Uri getUri() {
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 31a9451..df36cec 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -47,6 +47,7 @@
     public static final String TAG = "Documents";
 
     public static final boolean DEBUG = true;
+    public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
 
     public static final boolean ENABLE_OMC_API_FEATURES = true;
 
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 59b58f1..d44c2f7 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -19,6 +19,7 @@
 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 import static com.android.documentsui.base.State.MODE_GRID;
 import static com.android.documentsui.base.State.MODE_LIST;
 
@@ -65,7 +66,6 @@
 
 import com.android.documentsui.ActionHandler;
 import com.android.documentsui.ActionModeController;
-import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.BaseActivity.RetainedState;
 import com.android.documentsui.DirectoryLoader;
@@ -73,8 +73,10 @@
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.FocusManager;
+import com.android.documentsui.Injector;
+import com.android.documentsui.Injector.ContentScoped;
+import com.android.documentsui.Injector.Injected;
 import com.android.documentsui.ItemDragListener;
-import com.android.documentsui.MenuManager;
 import com.android.documentsui.MessageBar;
 import com.android.documentsui.Metrics;
 import com.android.documentsui.R;
@@ -107,7 +109,6 @@
 import com.android.documentsui.sorting.SortDimension;
 import com.android.documentsui.sorting.SortModel;
 import com.android.documentsui.ui.DialogController;
-import com.android.documentsui.ui.Snackbars;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -145,32 +146,34 @@
     private static final int CACHE_EVICT_LIMIT = 100;
     private static final int REFRESH_SPINNER_DISMISS_DELAY = 500;
 
-    private BaseActivity<?> mActivity;
+    private BaseActivity mActivity;
+
     private State mState;
     private final Model mModel = new Model();
     private final EventListener<Model.Update> mModelUpdateListener = new ModelUpdateListener();
     private final DocumentsAdapter.Environment mAdapterEnv = new AdapterEnvironment();
     private final LoaderCallbacks<DirectoryResult> mLoaderCallbacks = new LoaderBindings();
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
-    private ActivityConfig mActivityConfig;
+    @Injected
+    @ContentScoped
+    private Injector<?> mInjector;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
+    @Injected
+    @ContentScoped
     private SelectionManager mSelectionMgr;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
+    @Injected
+    @ContentScoped
     private FocusManager mFocusManager;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
+    @Injected
+    @ContentScoped
     private ActionHandler mActions;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
-    private MenuManager mMenuManager;
-
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
-    private DialogController mDialogs;
-
+    @Injected
+    @ContentScoped
     private ActionModeController mActionModeController;
+
     private SelectionMetadata mSelectionMetadata;
     private UserInputHandler<InputEvent> mInputHandler;
     private @Nullable BandController mBandController;
@@ -210,7 +213,7 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
-        BaseActivity<?> activity = (BaseActivity<?>) getActivity();
+        BaseActivity activity = (BaseActivity) getActivity();
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
         mMessageBar = MessageBar.create(getChildFragmentManager());
@@ -232,8 +235,8 @@
         mRecView.setItemAnimator(new DirectoryItemAnimator(activity));
         mFileList = view.findViewById(R.id.file_list);
 
-        mActivityConfig = activity.getActivityConfig();
-        mDragHoverListener = mActivityConfig.dragAndDropEnabled()
+        mInjector = activity.getInjector();
+        mDragHoverListener = mInjector.config.dragAndDropEnabled()
                 ? DragHoverListener.create(new DirectoryDragListener(this), mRecView)
                 : null;
 
@@ -306,11 +309,9 @@
         mModel.addUpdateListener(mAdapter.getModelUpdateListener());
         mModel.addUpdateListener(mModelUpdateListener);
 
-        mSelectionMgr = mActivity.getSelectionManager(mAdapter, this::canSetSelectionState);
-        mFocusManager = mActivity.getFocusManager(mRecView, mModel);
-        mActions = mActivity.getActionHandler(mModel, mLocalState.mSearchMode);
-        mMenuManager = mActivity.getMenuManager();
-        mDialogs = mActivity.getDialogController();
+        mSelectionMgr = mInjector.getSelectionManager(mAdapter, this::canSetSelectionState);
+        mFocusManager = mInjector.getFocusManager(mRecView, mModel);
+        mActions = mInjector.getActionHandler(mModel, mLocalState.mSearchMode);
 
         mSelectionMetadata = new SelectionMetadata(mModel::getItem);
         mSelectionMgr.addItemCallback(mSelectionMetadata);
@@ -331,7 +332,7 @@
                     });
         }
 
-        DragStartListener mDragStartListener = mActivityConfig.dragAndDropEnabled()
+        DragStartListener mDragStartListener = mInjector.config.dragAndDropEnabled()
                 ? DragStartListener.create(
                         mIconHelper,
                         mActivity,
@@ -369,9 +370,7 @@
                 mBandController,
                 this::scaleLayout);
 
-        mMenuManager = mActivity.getMenuManager();
-
-        mActionModeController = mActivity.getActionModeController(
+        mActionModeController = mInjector.getActionModeController(
                 mSelectionMetadata,
                 this::handleMenuItemClick,
                 mRecView);
@@ -441,9 +440,9 @@
             // TODO: inject DirectoryDetails into MenuManager constructor
             // Since both classes are supplied by Activity and created
             // at the same time.
-            mMenuManager.inflateContextMenuForContainer(menu, inflater);
+            mInjector.menuManager.inflateContextMenuForContainer(menu, inflater);
         } else {
-            mMenuManager.inflateContextMenuForDocs(menu, inflater, mSelectionMetadata);
+            mInjector.menuManager.inflateContextMenuForDocs(menu, inflater, mSelectionMetadata);
         }
     }
 
@@ -464,8 +463,10 @@
         }
 
         operation.setDestination(data.getParcelableExtra(Shared.EXTRA_STACK));
-
-        FileOperations.start(mActivity, operation, mDialogs::showFileOperationFailures);
+        FileOperations.start(
+                mActivity,
+                operation,
+                mInjector.dialogs::showFileOperationStatus);
     }
 
     protected boolean onContextMenuClick(InputEvent e) {
@@ -485,7 +486,7 @@
             y = e.getY();
         }
 
-        mMenuManager.showContextMenu(this, v, x, y);
+        mInjector.menuManager.showContextMenu(this, v, x, y);
 
         return true;
     }
@@ -526,14 +527,15 @@
      */
     private void scaleLayout(float scale) {
         assert(Build.IS_DEBUGGABLE);
-        if (DEBUG) Log.v(TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale);
+        if (VERBOSE) Log.v(
+                TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale);
 
         if (mMode == MODE_GRID) {
             float minScale = getFraction(R.fraction.grid_scale_min);
             float maxScale = getFraction(R.fraction.grid_scale_max);
             float nextScale = mLiveScale * scale;
 
-            if (DEBUG) Log.v(TAG,
+            if (VERBOSE) Log.v(TAG,
                     "Next scale " + nextScale + ", Min/max scale " + minScale + "/" + maxScale);
 
             if (nextScale > minScale && nextScale < maxScale) {
@@ -792,7 +794,7 @@
     }
 
     private boolean isDocumentEnabled(String mimeType, int flags) {
-        return mActivityConfig.isDocumentEnabled(mimeType, flags, mState);
+        return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
     }
 
     private void showEmptyDirectory() {
@@ -836,7 +838,9 @@
         BaseActivity activity = (BaseActivity) getActivity();
         DocumentInfo destination = activity.getCurrentDirectory();
         mClipper.copyFromClipboard(
-                destination, mState.stack, mDialogs::showFileOperationFailures);
+                destination,
+                mState.stack,
+                mInjector.dialogs::showFileOperationStatus);
         getActivity().invalidateOptionsMenu();
     }
 
@@ -852,7 +856,9 @@
         BaseActivity activity = mActivity;
         DocumentInfo destination = DocumentInfo.fromDirectoryCursor(dstCursor);
         mClipper.copyFromClipboard(
-                destination, mState.stack, mDialogs::showFileOperationFailures);
+                destination,
+                mState.stack,
+                mInjector.dialogs::showFileOperationStatus);
         getActivity().invalidateOptionsMenu();
     }
 
@@ -881,17 +887,6 @@
         }
     }
 
-    /**
-     * Attempts to restore focus on the directory listing.
-     */
-    public boolean requestFocus() {
-        if (mSelectionMgr.hasSelection()) {
-            return false;
-        }
-        mFocusManager.restoreLastFocus();
-        return true;
-    }
-
     private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
         final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
         if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
@@ -905,6 +900,12 @@
         // For now, just always reset drag shadow when drag exits
         mActivity.getShadowBuilder().resetBackground();
         v.updateDragShadow(mActivity.getShadowBuilder());
+        if (v.getParent() == mRecView) {
+            DocumentHolder holder = getDocumentHolder(v);
+            if (holder != null) {
+                holder.resetDropHighlight();
+            }
+        }
     }
 
     void dragStopped(boolean result) {
@@ -922,16 +923,13 @@
      * {@inheritDoc}
      *
      * In DirectoryFragment, we close the roots drawer right away.
+     * We also want to update the Drag Shadow to indicate whether the
+     * item is droppable or not.
      */
     @Override
     public void onDragEntered(View v, Object localState) {
-    mActivity.setRootsDrawerOpen(false);
-
-        if (canCopyTo(localState, v)) {
-            mActivity.getShadowBuilder().resetBackground();
-        } else {
-            mActivity.getShadowBuilder().setNoDropBackground();
-        }
+        mActivity.setRootsDrawerOpen(false);
+        mActivity.getShadowBuilder().setAppearDroppable(canCopyTo(localState, v));
         v.updateDragShadow(mActivity.getShadowBuilder());
     }
 
@@ -973,7 +971,10 @@
 
         DocumentInfo dst = getDestination(v);
         mClipper.copyFromClipData(
-                dst, mState.stack, clipData, mDialogs::showFileOperationFailures);
+                dst,
+                mState.stack,
+                clipData,
+                mInjector.dialogs::showFileOperationStatus);
         return true;
     }
 
@@ -1013,13 +1014,17 @@
     }
 
     @Override
-    public void setDropTargetHighlight(View v, boolean highlight) {
+    public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
         // Note: use exact comparison - this code is searching for views which are children of
         // the RecyclerView instance in the UI.
         if (v.getParent() == mRecView) {
-            RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(v);
-            if (vh instanceof DocumentHolder) {
-                ((DocumentHolder) vh).setDroppableHighlight(highlight);
+            DocumentHolder holder = getDocumentHolder(v);
+            if (holder != null) {
+                if (!highlight) {
+                    holder.resetDropHighlight();
+                } else {
+                    holder.setDroppableHighlight(canCopyTo(localState, v));
+                }
             }
         }
     }
@@ -1041,6 +1046,14 @@
         return null;
     }
 
+    private @Nullable DocumentHolder getDocumentHolder(View v) {
+        RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(v);
+        if (vh instanceof DocumentHolder) {
+            return (DocumentHolder) vh;
+        }
+        return null;
+    }
+
     // TODO: Move to activities when Model becomes activity level object.
     private boolean canSelect(DocumentDetails doc) {
         return canSetSelectionState(doc.getModelId(), true);
@@ -1058,7 +1071,7 @@
 
             final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
             final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-            return mActivityConfig.canSelectType(docMimeType, docFlags, mState);
+            return mInjector.config.canSelectType(docMimeType, docFlags, mState);
         } else {
             // Right now all selected items can be deselected.
             return true;
@@ -1219,7 +1232,7 @@
 
         @Override
         public boolean isDocumentEnabled(String mimeType, int flags) {
-            return mActivityConfig.isDocumentEnabled(mimeType, flags, mState);
+            return mInjector.config.isDocumentEnabled(mimeType, flags, mState);
         }
 
         @Override
@@ -1252,7 +1265,7 @@
                                     mLocalState.mDocument.authority,
                                     mLocalState.mDocument.documentId);
 
-                    if (mActivityConfig.managedModeEnabled(mState.stack)) {
+                    if (mInjector.config.managedModeEnabled(mState.stack)) {
                         contentsUri = DocumentsContract.setManageMode(contentsUri);
                     }
 
diff --git a/src/com/android/documentsui/dirlist/DocumentHolder.java b/src/com/android/documentsui/dirlist/DocumentHolder.java
index b5c1174..3015d42 100644
--- a/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -49,6 +49,8 @@
     protected final Context mContext;
     protected final @ColorInt int mDefaultBgColor;
     protected final @ColorInt int mSelectedBgColor;
+    protected final @ColorInt int mDroppableBgColor;
+    protected final @ColorInt int mNotDroppableBgColor;
 
     protected @Nullable String mModelId;
 
@@ -72,6 +74,9 @@
 
         mDefaultBgColor = context.getColor(R.color.item_doc_background);
         mSelectedBgColor = context.getColor(R.color.item_doc_background_selected);
+        mDroppableBgColor = context.getColor(R.color.item_doc_droppable_background);
+        mNotDroppableBgColor = context.getColor(R.color.item_doc_not_droppable_background);
+
         mSelectionHotspot = itemView.findViewById(R.id.icon_check);
 
         mDebugContainer = (FrameLayout) itemView.findViewById(R.id.debug_info);
@@ -118,13 +123,24 @@
      * Highlights the associated item view to indicate it's droppable.
      * @param highlighted
      */
-    public void setDroppableHighlight(boolean highlighted) {
-        // If item is already selected, its droppable highlight should not be changed.
+    public void setDroppableHighlight(boolean droppable) {
+        // If item is already selected, its highlight should not be changed.
         if (itemView.isActivated()) {
             return;
         }
 
-        itemView.setBackgroundColor(highlighted ? mSelectedBgColor : mDefaultBgColor);
+        itemView.setBackgroundColor(droppable ? mDroppableBgColor : mNotDroppableBgColor);
+    }
+
+    /**
+     * Reset the associated item view's droppable background highlight.
+     */
+    public void resetDropHighlight() {
+        if (itemView.isActivated()) {
+            return;
+        }
+
+        itemView.setBackgroundColor(mDefaultBgColor);
     }
 
     public void setEnabled(boolean enabled) {
diff --git a/src/com/android/documentsui/dirlist/FocusHandler.java b/src/com/android/documentsui/dirlist/FocusHandler.java
index 1cbb8a9..ad8ae67 100644
--- a/src/com/android/documentsui/dirlist/FocusHandler.java
+++ b/src/com/android/documentsui/dirlist/FocusHandler.java
@@ -21,7 +21,7 @@
 import android.view.View;
 
 /**
- * A class that handles navigation and focus within the DirectoryFragment.
+ * A class that manages focus and keyboard driven navigation in the activity.
  */
 public interface FocusHandler extends View.OnFocusChangeListener {
 
@@ -44,9 +44,20 @@
     void focusDocument(String modelId);
 
     /**
-     * Requests focus on the item that last had focus. Scrolls to that item if necessary.
+     * Requests focus on the the directory list. Will specifically
+     * attempt to focus the item in the directory list that last had focus.
+     * Scrolls to that item if necessary.
+     *
+     * <p>If focus is unsuccessful, return false.
      */
-    void restoreLastFocus();
+    boolean focusDirectoryList();
+
+    /**
+     * Attempts to advance the focus to the next available focus area
+     * in the app. As of this writing, known focus areas are the sidebar
+     * and the directory list (specifically an item in the directory list).
+     */
+    boolean advanceFocusArea();
 
     /**
      * @return The adapter position of the last focused item.
@@ -54,6 +65,11 @@
     int getFocusPosition();
 
     /**
+     * @return True if there is currently an item in focus, false otherwise.
+     */
+    boolean hasFocusedItem();
+
+    /**
      * @return The modelId of the last focused item. If no item is focused, this should return null.
      */
     @Nullable String getFocusModelId();
diff --git a/src/com/android/documentsui/dirlist/IconHelper.java b/src/com/android/documentsui/dirlist/IconHelper.java
index fa1c121..77a6466 100644
--- a/src/com/android/documentsui/dirlist/IconHelper.java
+++ b/src/com/android/documentsui/dirlist/IconHelper.java
@@ -16,7 +16,7 @@
 
 package com.android.documentsui.dirlist;
 
-import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 import static com.android.documentsui.base.State.MODE_GRID;
 import static com.android.documentsui.base.State.MODE_LIST;
 
@@ -160,12 +160,12 @@
             mImageAnimator = animator;
             mLastModified = lastModified;
             mSignal = new CancellationSignal();
-            if (DEBUG) Log.v(TAG, "Starting icon loader task for " + mUri);
+            if (VERBOSE) Log.v(TAG, "Starting icon loader task for " + mUri);
         }
 
         @Override
         public void preempt() {
-            if (DEBUG) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
+            if (VERBOSE) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
             cancel(false);
             mSignal.cancel();
         }
@@ -201,7 +201,7 @@
 
         @Override
         protected void onPostExecute(Bitmap result) {
-            if (DEBUG) Log.v(TAG, "Loader task for " + mUri + " completed");
+            if (VERBOSE) Log.v(TAG, "Loader task for " + mUri + " completed");
 
             if (mIconThumb.getTag() == this && result != null) {
                 mIconThumb.setTag(null);
@@ -282,7 +282,7 @@
             iconThumb.setImageBitmap(cachedThumbnail);
 
             boolean stale = (docLastModified > result.getLastModified());
-            if (DEBUG) Log.v(TAG,
+            if (VERBOSE) Log.v(TAG,
                     String.format("Load thumbnail for %s, got result %d and stale %b.",
                             uri.toString(), result.getStatus(), stale));
             if (!result.isExactHit() || stale) {
diff --git a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
index 17ac867..d2f6972 100644
--- a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
+++ b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -16,7 +16,7 @@
 
 package com.android.documentsui.dirlist;
 
-import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -82,7 +82,7 @@
                         new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                             @Override
                             public boolean onScale(ScaleGestureDetector detector) {
-                                if (DEBUG) Log.v(TAG,
+                                if (VERBOSE) Log.v(TAG,
                                         "Received scale event: " + detector.getScaleFactor());
                                 scaleHandler.accept(detector.getScaleFactor());
                                 return true;
@@ -176,4 +176,4 @@
         // Pass events to UserInputHandler.
         return onTouchEvent(event);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index 8bdf079..5094f41 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -18,6 +18,7 @@
 
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.annotation.IntDef;
 import android.database.Cursor;
@@ -241,7 +242,7 @@
         if (filter.test(cursor)) {
             docs.add(DocumentInfo.fromDirectoryCursor(cursor));
         } else {
-            if (DEBUG) Log.v(TAG, "Filtered document from results: " + modelId);
+            if (VERBOSE) Log.v(TAG, "Filtered document from results: " + modelId);
         }
     }
 
diff --git a/src/com/android/documentsui/dirlist/RenameDocumentFragment.java b/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
index 0af937e..ec91fe6 100644
--- a/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
+++ b/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
@@ -231,8 +231,7 @@
             if (result != null) {
                 Metrics.logRenameFileOperation(getContext());
             } else {
-                Snackbars.makeSnackbar(mActivity, R.string.rename_error, Snackbar.LENGTH_SHORT)
-                        .show();
+                Snackbars.showRenameFailed(mActivity);
                 Metrics.logRenameFileError(getContext());
             }
             mActivity.setPending(false);
diff --git a/src/com/android/documentsui/dirlist/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index 1e9cc39..00912cd 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -17,6 +17,7 @@
 package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
@@ -211,18 +212,18 @@
     private final class TouchInputDelegate {
 
         boolean onDown(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onDown event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onDown event.");
             return false;
         }
 
         // Don't consume so the RecyclerView will get the event and will get touch-based scrolling
         boolean onScroll(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onScroll event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onScroll event.");
             return false;
         }
 
         boolean onSingleTapUp(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onSingleTapUp event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onSingleTapUp event.");
             if (!event.isOverModelItem()) {
                 if (DEBUG) Log.d(TTAG, "Tap not associated w/ model item. Clearing selection.");
                 mSelectionMgr.clearSelection();
@@ -247,17 +248,17 @@
         }
 
         boolean onSingleTapConfirmed(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onSingleTapConfirmed event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onSingleTapConfirmed event.");
             return false;
         }
 
         boolean onDoubleTap(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onDoubleTap event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onDoubleTap event.");
             return false;
         }
 
         final void onLongPress(T event) {
-            if (DEBUG) Log.v(TTAG, "Delegated onLongPress event.");
+            if (VERBOSE) Log.v(TTAG, "Delegated onLongPress event.");
             if (!event.isOverModelItem()) {
                 if (DEBUG) Log.d(TTAG, "Ignoring LongPress on non-model-backed item.");
                 return;
@@ -291,7 +292,7 @@
         private boolean mHandledOnDown;
 
         boolean onDown(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onDown event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onDown event.");
             if (event.isSecondaryButtonPressed()
                     || (event.isAltKeyDown() && event.isPrimaryButtonPressed())) {
                 mHandledOnDown = true;
@@ -303,18 +304,18 @@
 
         // Don't scroll content window in response to mouse drag
         boolean onScroll(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onScroll event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onScroll event.");
             return true;
         }
 
         boolean onSingleTapUp(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onSingleTapUp event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onSingleTapUp event.");
 
             // See b/27377794. Since we don't get a button state back from UP events, we have to
             // explicitly save this state to know whether something was previously handled by
             // DOWN events or not.
             if (mHandledOnDown) {
-                if (DEBUG) Log.v(MTAG, "Ignoring onSingleTapUp, previously handled in onDown.");
+                if (VERBOSE) Log.v(MTAG, "Ignoring onSingleTapUp, previously handled in onDown.");
                 mHandledOnDown = false;
                 return false;
             }
@@ -348,9 +349,9 @@
         }
 
         boolean onSingleTapConfirmed(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onSingleTapConfirmed event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onSingleTapConfirmed event.");
             if (mHandledTapUp) {
-                if (DEBUG) Log.v(MTAG, "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
+                if (VERBOSE) Log.v(MTAG, "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
                 mHandledTapUp = false;
                 return false;
             }
@@ -375,11 +376,17 @@
                 return false;
             }
 
-            return selectDocument(doc);
+            if (mFocusHandler.hasFocusedItem() && event.isShiftKeyDown()) {
+                mSelectionMgr.formNewSelectionRange(mFocusHandler.getFocusPosition(),
+                        doc.getAdapterPosition());
+                return true;
+            } else {
+                return selectDocument(doc);
+            }
         }
 
         boolean onDoubleTap(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onDoubleTap event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onDoubleTap event.");
             mHandledTapUp = false;
 
             if (!event.isOverModelItem()) {
@@ -397,12 +404,12 @@
         }
 
         final void onLongPress(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onLongPress event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onLongPress event.");
             return;
         }
 
         private boolean onRightClick(T event) {
-            if (DEBUG) Log.v(MTAG, "Delegated onRightClick event.");
+            if (VERBOSE) Log.v(MTAG, "Delegated onRightClick event.");
             if (event.isOverModelItem()) {
                 DocumentDetails doc = event.getDocumentDetails();
                 if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 09f5ce1..7c62126 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -116,7 +116,7 @@
                 mActivity,
                 mActivity::isDestroyed,
                 (DocumentInfo doc) -> mClipper.copyFromClipData(
-                        root, doc, data, mDialogs::showFileOperationFailures)
+                        root, doc, data, mDialogs::showFileOperationStatus)
         ).executeOnExecutor(mExecutors.lookup(root.authority));
         return true;
     }
@@ -151,7 +151,7 @@
     private void pasteIntoFolder(RootInfo root, DocumentInfo doc) {
         DocumentClipper clipper = DocumentsApplication.getDocumentClipper(mActivity);
         DocumentStack stack = new DocumentStack(root, doc);
-        clipper.copyFromClipboard(doc, stack, mDialogs::showFileOperationFailures);
+        clipper.copyFromClipboard(doc, stack, mDialogs::showFileOperationStatus);
     }
 
     @Override
@@ -275,7 +275,7 @@
                     .withSrcParent(srcParent.derivedUri)
                     .build();
 
-            FileOperations.start(mActivity, operation, mDialogs::showFileOperationFailures);
+            FileOperations.start(mActivity, operation, mDialogs::showFileOperationStatus);
         };
 
         mDialogs.confirmDelete(docs, result);
@@ -566,7 +566,9 @@
         mScope.modelLoadObserved = true;
     }
 
-    ActionHandler<T> reset(Model model, boolean searchMode) {
+    @SuppressWarnings("unchecked")
+    @Override
+    public ActionHandler<T> reset(Model model, boolean searchMode) {
         assert(model != null);
 
         mScope.model = model;
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 0c67b20..85c140a 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -31,22 +31,21 @@
 import android.view.KeyboardShortcutGroup;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
 
 import com.android.documentsui.ActionModeController;
-import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.DragShadowBuilder;
+import com.android.documentsui.FocusManager;
+import com.android.documentsui.Injector;
 import com.android.documentsui.MenuManager.DirectoryDetails;
-import com.android.documentsui.MenuManager.SelectionDetails;
 import com.android.documentsui.OperationDialogFragment;
 import com.android.documentsui.OperationDialogFragment.DialogType;
 import com.android.documentsui.ProviderExecutor;
 import com.android.documentsui.R;
+import com.android.documentsui.SharedInputHandler;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.DocumentStack;
-import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.ScopedPreferences;
 import com.android.documentsui.base.Shared;
@@ -54,14 +53,11 @@
 import com.android.documentsui.clipping.DocumentClipper;
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.DocumentsAdapter;
-import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.selection.Selection;
 import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.ui.DialogController;
+import com.android.documentsui.ui.MessageBuilder;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -70,21 +66,14 @@
 /**
  * Standalone file management activity.
  */
-public class FilesActivity
-        extends BaseActivity<ActionHandler<FilesActivity>> implements ActionHandler.Addons {
+public class FilesActivity extends BaseActivity implements ActionHandler.Addons {
 
     private static final String TAG = "FilesActivity";
-    private static final String PREFERENCES_SCOPE = "files";
+    static final String PREFERENCES_SCOPE = "files";
 
-    private final Config mConfig = new Config();
-
-    private ScopedPreferences mPrefs;
-    private SelectionManager mSelectionMgr;
-    private MenuManager mMenuManager;
-    private DialogController mDialogs;
-    private DocumentClipper mClipper;
-    private ActionModeController mActionModeController;
+    private Injector<ActionHandler<FilesActivity>> mInjector;
     private ActivityInputHandler mActivityInputHandler;
+    private SharedInputHandler mSharedInputHandler;
     private DragShadowBuilder mShadowBuilder;
 
     public FilesActivity() {
@@ -94,54 +83,65 @@
     @Override
     public void onCreate(Bundle icicle) {
 
-        // must be initialized before calling super.onCreate because prefs
-        // are used in State initialization.
-        mPrefs = ScopedPreferences.create(this, PREFERENCES_SCOPE);
+        MessageBuilder messages = new MessageBuilder(this);
+        mInjector = new Injector<>(
+                new Config(),
+                ScopedPreferences.create(this, PREFERENCES_SCOPE),
+                messages,
+                DialogController.create(this, messages));
 
         super.onCreate(icicle);
 
-        mClipper = DocumentsApplication.getDocumentClipper(this);
-        mSelectionMgr = new SelectionManager(SelectionManager.MODE_MULTIPLE);
-        mMenuManager = new MenuManager(
+        DocumentClipper clipper = DocumentsApplication.getDocumentClipper(this);
+        mInjector.selectionMgr = new SelectionManager(SelectionManager.MODE_MULTIPLE);
+
+        mInjector.focusManager = new FocusManager(
+            mInjector.selectionMgr,
+            mDrawer,
+            this::focusSidebar,
+            getColor(R.color.accent_dark));
+
+        mInjector.menuManager = new MenuManager(
                 mSearchManager,
                 mState,
                 new DirectoryDetails(this) {
                     @Override
                     public boolean hasItemsToPaste() {
-                        return mClipper.hasItemsToPaste();
+                        return clipper.hasItemsToPaste();
                     }
                 });
-        mDialogs = DialogController.create(this, getMessages());
 
         mShadowBuilder = new DragShadowBuilder(this);
-        mActionModeController = new ActionModeController(
+        mInjector.actionModeController = new ActionModeController(
                 this,
-                mSelectionMgr,
-                mMenuManager,
-                getMessages());
+                mInjector.selectionMgr,
+                mInjector.menuManager,
+                mInjector.messages);
 
-        mActions = new ActionHandler<>(
+        mInjector.actions = new ActionHandler<>(
                 this,
                 mState,
                 mRoots,
                 mDocs,
-                mFocusManager,
-                mSelectionMgr,
+                mInjector.focusManager,
+                mInjector.selectionMgr,
                 mSearchManager,
                 ProviderExecutor::forAuthority,
-                mActionModeController,
-                mDialogs,
-                mConfig,
-                mClipper,
+                mInjector.actionModeController,
+                mInjector.dialogs,
+                mInjector.config,
+                clipper,
                 DocumentsApplication.getClipStore(this));
 
-        mActivityInputHandler = new ActivityInputHandler(mActions::deleteSelectedDocuments);
+        mActivityInputHandler =
+                new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments);
+        mSharedInputHandler = new SharedInputHandler(mInjector.focusManager, this::popDir);
 
         RootsFragment.show(getFragmentManager(), null);
 
         final Intent intent = getIntent();
 
-        mActions.initLocation(intent);
+        mInjector.actions.initLocation(intent);
         presentFileErrors(icicle, intent);
     }
 
@@ -223,7 +223,7 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
-        mMenuManager.updateOptionMenu(menu);
+        mInjector.menuManager.updateOptionMenu(menu);
         return true;
     }
 
@@ -235,7 +235,7 @@
                 showCreateDirectoryDialog();
                 break;
             case R.id.menu_new_window:
-                mActions.openInNewWindow(mState.stack);
+                mInjector.actions.openInNewWindow(mState.stack);
                 break;
             case R.id.menu_paste_from_clipboard:
                 DirectoryFragment dir = getDirectoryFragment();
@@ -244,7 +244,7 @@
                 }
                 break;
             case R.id.menu_settings:
-                mActions.openSettings(getCurrentRoot());
+                mInjector.actions.openSettings(getCurrentRoot());
                 break;
             default:
                 return super.onOptionsItemSelected(item);
@@ -255,7 +255,7 @@
     @Override
     public void onProvideKeyboardShortcuts(
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
-        getMenuManager().updateKeyboardShortcutsMenu(data, this::getString);
+        mInjector.menuManager.updateKeyboardShortcutsMenu(data, this::getString);
     }
 
     @Override
@@ -285,27 +285,28 @@
      */
     @Override
     public void onDocumentPicked(DocumentInfo doc) {
-        mActions.onDocumentPicked(doc);
+        mInjector.actions.onDocumentPicked(doc);
     }
 
     @Override
     public void onDirectoryCreated(DocumentInfo doc) {
         assert(doc.isDirectory());
-        mFocusManager.focusDocument(doc.documentId);
+        mInjector.focusManager.focusDocument(doc.documentId);
     }
 
     @Override
     public void springOpenDirectory(DocumentInfo doc) {
         assert(doc.isContainer());
         assert(!doc.isArchive());
-        mActions.openContainerDocument(doc);
+        mInjector.actions.openContainerDocument(doc);
     }
 
     @CallSuper
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        return mActivityInputHandler.onKeyDown(keyCode, event) ? true
-                : super.onKeyDown(keyCode, event);
+        return mActivityInputHandler.onKeyDown(keyCode, event)
+                || mSharedInputHandler.onKeyDown(keyCode, event)
+                || super.onKeyDown(keyCode, event);
     }
 
     @Override
@@ -326,10 +327,10 @@
                 }
                 return true;
             case KeyEvent.KEYCODE_X:
-                mActions.cutToClipboard();
+                mInjector.actions.cutToClipboard();
                 return true;
             case KeyEvent.KEYCODE_C:
-                mActions.copyToClipboard();
+                mInjector.actions.copyToClipboard();
                 return true;
             case KeyEvent.KEYCODE_V:
                 dir = getDirectoryFragment();
@@ -367,45 +368,7 @@
     }
 
     @Override
-    public ActivityConfig getActivityConfig() {
-        return mConfig;
-    }
-
-    @Override
-    public ScopedPreferences getScopedPreferences() {
-        return mPrefs;
-    }
-
-    @Override
-    public SelectionManager getSelectionManager(
-            DocumentsAdapter adapter, SelectionPredicate canSetState) {
-        return mSelectionMgr.reset(adapter, canSetState);
-    }
-
-    @Override
-    public MenuManager getMenuManager() {
-        return mMenuManager;
-    }
-
-    @Override
-    public final ActionModeController getActionModeController(
-            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view) {
-        return mActionModeController.reset(selectionDetails, menuItemClicker, view);
-    }
-
-    @Override
-    public ActionHandler<FilesActivity> getActionHandler(Model model, boolean searchMode) {
-
-        // provide our friend, RootsFragment, early access to this special feature!
-        if (model == null) {
-            return mActions;
-        }
-
-        return mActions.reset(model, searchMode);
-    }
-
-    @Override
-    public DialogController getDialogController() {
-        return mDialogs;
+    public Injector<ActionHandler<FilesActivity>> getInjector() {
+        return mInjector;
     }
 }
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 522f448..36542ca 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -51,9 +51,7 @@
         mSearchManager.updateMenu();
     }
 
-    /**
-     * @see FilesActivity#onProvideKeyboardShortcuts(List, Menu, int)
-     */
+    @Override
     public void updateKeyboardShortcutsMenu(
             List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier) {
         KeyboardShortcutGroup group = new KeyboardShortcutGroup(
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 1999381..2c0a227 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.provider.DocumentsContract;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -39,6 +40,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
+import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.FocusHandler;
 import com.android.documentsui.dirlist.Model;
@@ -121,7 +123,7 @@
     }
 
     private boolean launchToDocument(Intent intent) {
-        final Uri uri = intent.getData();
+        final Uri uri = intent.getParcelableExtra(DocumentsContract.EXTRA_INITIAL_URI);
         if (uri != null) {
             loadDocument(uri, this::onStackLoaded);
             return true;
@@ -138,6 +140,7 @@
                 stack.pop();
             }
             mState.stack.reset(stack);
+            mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
         } else {
             Log.w(TAG, "Failed to launch into the given uri. Load last accessed stack.");
             loadLastAccessedStack();
@@ -226,7 +229,9 @@
         mScope.modelLoadObserved = true;
     }
 
-    ActionHandler<T> reset(Model model, boolean searchMode) {
+    @SuppressWarnings("unchecked")
+    @Override
+    public ActionHandler<T> reset(Model model, boolean searchMode) {
         assert(model != null);
 
         mScope.model = model;
diff --git a/src/com/android/documentsui/picker/MenuManager.java b/src/com/android/documentsui/picker/MenuManager.java
index 8bdd150..9288717 100644
--- a/src/com/android/documentsui/picker/MenuManager.java
+++ b/src/com/android/documentsui/picker/MenuManager.java
@@ -22,12 +22,16 @@
 import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
 import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
 
+import android.view.KeyboardShortcutGroup;
 import android.view.Menu;
 import android.view.MenuItem;
 
 import com.android.documentsui.base.State;
 import com.android.documentsui.queries.SearchViewManager;
 
+import java.util.List;
+import java.util.function.IntFunction;
+
 public final class MenuManager extends com.android.documentsui.MenuManager {
 
     public MenuManager(SearchViewManager searchManager, State displayState, DirectoryDetails dirDetails) {
@@ -35,6 +39,12 @@
 
     }
 
+    @Override
+    public void updateKeyboardShortcutsMenu(
+            List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier) {
+        // None as of yet.
+    }
+
     private boolean picking() {
         return mState.action == ACTION_CREATE
                 || mState.action == ACTION_OPEN_TREE
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index eee92e5..ed89187 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -37,22 +37,22 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
+import android.support.annotation.CallSuper;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
 
 import com.android.documentsui.ActionModeController;
-import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.FocusManager;
+import com.android.documentsui.Injector;
 import com.android.documentsui.MenuManager.DirectoryDetails;
-import com.android.documentsui.MenuManager.SelectionDetails;
 import com.android.documentsui.ProviderExecutor;
 import com.android.documentsui.R;
+import com.android.documentsui.SharedInputHandler;
 import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.MimeTypes;
 import com.android.documentsui.base.PairedTask;
 import com.android.documentsui.base.RootInfo;
@@ -60,32 +60,26 @@
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.dirlist.DirectoryFragment;
-import com.android.documentsui.dirlist.DocumentsAdapter;
-import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.picker.LastAccessedProvider.Columns;
 import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.ui.DialogController;
+import com.android.documentsui.ui.MessageBuilder;
 import com.android.documentsui.ui.Snackbars;
 
 import java.util.Arrays;
 import java.util.List;
 
-public class PickActivity
-        extends BaseActivity<ActionHandler<PickActivity>> implements ActionHandler.Addons {
+public class PickActivity extends BaseActivity implements ActionHandler.Addons {
+
+    static final String PREFERENCES_SCOPE = "picker";
 
     private static final String TAG = "PickActivity";
-    private static final String PREFERENCES_SCOPE = "picker";
     private static final int CODE_FORWARD = 42;
 
-    private final Config mConfig = new Config();
-
-    private ScopedPreferences mPrefs;
-    private SelectionManager mSelectionMgr;
-    private MenuManager mMenuManager;
-    private ActionModeController mActionModeController;
+    private Injector<ActionHandler<PickActivity>> mInjector;
+    private SharedInputHandler mSharedInputHandler;
 
     public PickActivity() {
         super(R.layout.documents_activity, TAG);
@@ -94,38 +88,49 @@
     @Override
     public void onCreate(Bundle icicle) {
 
-        // must be initialized before calling super.onCreate because prefs
-        // are used in State initialization.
-        mPrefs = ScopedPreferences.create(this, PREFERENCES_SCOPE);
+        mInjector = new Injector<>(
+                new Config(),
+                ScopedPreferences.create(this, PREFERENCES_SCOPE),
+                new MessageBuilder(this),
+                DialogController.STUB);
 
         super.onCreate(icicle);
 
-        mSelectionMgr = new SelectionManager(
+        mInjector.selectionMgr = new SelectionManager(
                 mState.allowMultiple
                         ? SelectionManager.MODE_MULTIPLE
                         : SelectionManager.MODE_SINGLE);
-        mMenuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
-        mActions = new ActionHandler<>(
+
+        mInjector.focusManager = new FocusManager(
+                mInjector.selectionMgr,
+                mDrawer,
+                this::focusSidebar,
+                getColor(R.color.accent_dark));
+
+        mInjector.menuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
+        mInjector.actions = new ActionHandler<>(
                 this,
                 mState,
                 mRoots,
                 mDocs,
-                mFocusManager,
-                mSelectionMgr,
+                mInjector.focusManager,
+                mInjector.selectionMgr,
                 mSearchManager,
                 ProviderExecutor::forAuthority,
-                mConfig);
+                mInjector.config);
 
-        mActionModeController = new ActionModeController(
+        mInjector.actionModeController = new ActionModeController(
                 this,
-                mSelectionMgr,
-                mMenuManager,
-                getMessages());
+                mInjector.selectionMgr,
+                mInjector.menuManager,
+                mInjector.messages);
 
         Intent intent = getIntent();
 
+        mSharedInputHandler = new SharedInputHandler(mInjector.focusManager, this::popDir);
+
         setupLayout(intent);
-        mActions.initLocation(intent);
+        mInjector.actions.initLocation(intent);
     }
 
     private void setupLayout(Intent intent) {
@@ -250,7 +255,7 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
-        mMenuManager.updateOptionMenu(menu);
+        mInjector.menuManager.updateOptionMenu(menu);
 
         final DocumentInfo cwd = getCurrentDirectory();
 
@@ -272,7 +277,7 @@
             // No directory means recents
             if (mState.action == ACTION_CREATE ||
                 mState.action == ACTION_PICK_COPY_DESTINATION) {
-                mActions.loadRoot(Shared.getDefaultRootUri(this));
+                mInjector.actions.loadRoot(Shared.getDefaultRootUri(this));
             } else {
                 DirectoryFragment.showRecentsOpen(fm, anim);
 
@@ -322,7 +327,7 @@
     @Override
     protected void onDirectoryCreated(DocumentInfo doc) {
         assert(doc.isDirectory());
-        mActions.openContainerDocument(doc);
+        mInjector.actions.openContainerDocument(doc);
     }
 
     void onSaveRequested(String mimeType, String displayName) {
@@ -334,7 +339,7 @@
     public void onDocumentPicked(DocumentInfo doc) {
         final FragmentManager fm = getFragmentManager();
         if (doc.isContainer()) {
-            mActions.openContainerDocument(doc);
+            mInjector.actions.openContainerDocument(doc);
         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
             new ExistingFinishTask(this, doc.derivedUri)
@@ -415,53 +420,17 @@
         finish();
     }
 
+    @CallSuper
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mSharedInputHandler.onKeyDown(keyCode, event)
+                || super.onKeyDown(keyCode, event);
+    }
 
     public static PickActivity get(Fragment fragment) {
         return (PickActivity) fragment.getActivity();
     }
 
-    @Override
-    public ActivityConfig getActivityConfig() {
-        return mConfig;
-    }
-
-    @Override
-    public ScopedPreferences getScopedPreferences() {
-        return mPrefs;
-    }
-
-    public SelectionManager getSelectionManager(
-            DocumentsAdapter adapter, SelectionPredicate canSetState) {
-        return mSelectionMgr.reset(adapter, canSetState);
-    }
-
-    @Override
-    public MenuManager getMenuManager() {
-        return mMenuManager;
-    }
-
-    @Override
-    public ActionModeController getActionModeController(
-            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view) {
-        return mActionModeController.reset(selectionDetails, menuItemClicker, view);
-    }
-
-    @Override
-    public ActionHandler<PickActivity> getActionHandler(Model model, boolean searchMode) {
-
-        // provide our friend, RootsFragment, early access to this special feature!
-        if (model == null) {
-            return mActions;
-        }
-
-        return mActions.reset(model, searchMode);
-    }
-
-    @Override
-    public DialogController getDialogController() {
-        return DialogController.STUB;
-    }
-
     private static final class PickFinishTask extends PairedTask<PickActivity, Void, Void> {
         private final Uri mUri;
 
@@ -557,4 +526,9 @@
             mOwner.setPending(false);
         }
     }
+
+    @Override
+    public Injector<ActionHandler<PickActivity>> getInjector() {
+        return mInjector;
+    }
 }
diff --git a/src/com/android/documentsui/roots/RootsAccess.java b/src/com/android/documentsui/roots/RootsAccess.java
index 784ccfb..e721905 100644
--- a/src/com/android/documentsui/roots/RootsAccess.java
+++ b/src/com/android/documentsui/roots/RootsAccess.java
@@ -17,6 +17,7 @@
 package com.android.documentsui.roots;
 
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.net.Uri;
 import android.util.Log;
@@ -63,49 +64,49 @@
         final List<RootInfo> matching = new ArrayList<>();
         for (RootInfo root : roots) {
 
-            if (DEBUG) Log.v(tag, "Evaluationg root: " + root);
+            if (VERBOSE) Log.v(tag, "Evaluationg root: " + root);
 
             if (state.action == State.ACTION_CREATE && !root.supportsCreate()) {
-                if (DEBUG) Log.v(tag, "Excluding read-only root because: ACTION_CREATE.");
+                if (VERBOSE) Log.v(tag, "Excluding read-only root because: ACTION_CREATE.");
                 continue;
             }
 
             if (state.action == State.ACTION_PICK_COPY_DESTINATION
                     && !root.supportsCreate()) {
-                if (DEBUG) Log.v(
+                if (VERBOSE) Log.v(
                         tag, "Excluding read-only root because: ACTION_PICK_COPY_DESTINATION.");
                 continue;
             }
 
             if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) {
-                if (DEBUG) Log.v(
+                if (VERBOSE) Log.v(
                         tag, "Excluding root !supportsChildren because: ACTION_OPEN_TREE.");
                 continue;
             }
 
             if (!state.showAdvanced && root.isAdvanced()) {
-                if (DEBUG) Log.v(tag, "Excluding root because: unwanted advanced device.");
+                if (VERBOSE) Log.v(tag, "Excluding root because: unwanted advanced device.");
                 continue;
             }
 
             if (state.localOnly && !root.isLocalOnly()) {
-                if (DEBUG) Log.v(tag, "Excluding root because: unwanted non-local device.");
+                if (VERBOSE) Log.v(tag, "Excluding root because: unwanted non-local device.");
                 continue;
             }
 
             if (state.directoryCopy && root.isDownloads()) {
-                if (DEBUG) Log.v(
+                if (VERBOSE) Log.v(
                         tag, "Excluding downloads root because: unsupported directory copy.");
                 continue;
             }
 
             if (state.action == State.ACTION_OPEN && root.isEmpty()) {
-                if (DEBUG) Log.v(tag, "Excluding empty root because: ACTION_OPEN.");
+                if (VERBOSE) Log.v(tag, "Excluding empty root because: ACTION_OPEN.");
                 continue;
             }
 
             if (state.action == State.ACTION_GET_CONTENT && root.isEmpty()) {
-                if (DEBUG) Log.v(tag, "Excluding empty root because: ACTION_GET_CONTENT.");
+                if (VERBOSE) Log.v(tag, "Excluding empty root because: ACTION_GET_CONTENT.");
                 continue;
             }
 
@@ -113,14 +114,14 @@
                     MimeTypes.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
                     MimeTypes.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
             if (!overlap) {
-                if (DEBUG) Log.v(
+                if (VERBOSE) Log.v(
                         tag, "Excluding root because: unsupported content types > "
                         + Arrays.toString(state.acceptMimes));
                 continue;
             }
 
             if (state.excludedAuthorities.contains(root.authority)) {
-                if (DEBUG) Log.v(tag, "Excluding root because: owned by calling package.");
+                if (VERBOSE) Log.v(tag, "Excluding root because: owned by calling package.");
                 continue;
             }
 
diff --git a/src/com/android/documentsui/roots/RootsCache.java b/src/com/android/documentsui/roots/RootsCache.java
index 4111a95..43ef61a 100644
--- a/src/com/android/documentsui/roots/RootsCache.java
+++ b/src/com/android/documentsui/roots/RootsCache.java
@@ -17,6 +17,7 @@
 package com.android.documentsui.roots;
 
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.content.BroadcastReceiver.PendingResult;
 import android.content.ContentProviderClient;
@@ -211,7 +212,7 @@
      */
     private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority,
             boolean forceRefresh) {
-        if (DEBUG) Log.v(TAG, "Loading roots for " + authority);
+        if (VERBOSE) Log.v(TAG, "Loading roots for " + authority);
 
         synchronized (mObservedAuthorities) {
             if (mObservedAuthorities.add(authority)) {
@@ -227,7 +228,7 @@
             // long-lived system process.
             final Bundle systemCache = resolver.getCache(rootsUri);
             if (systemCache != null) {
-                if (DEBUG) Log.v(TAG, "System cache hit for " + authority);
+                if (VERBOSE) Log.v(TAG, "System cache hit for " + authority);
                 return systemCache.getParcelableArrayList(TAG);
             }
         }
@@ -378,7 +379,7 @@
             }
 
             final long delta = SystemClock.elapsedRealtime() - start;
-            if (DEBUG) Log.v(TAG,
+            if (VERBOSE) Log.v(TAG,
                     "Update found " + mTaskRoots.size() + " roots in " + delta + "ms");
             synchronized (mLock) {
                 mFirstLoadDone = true;
@@ -398,7 +399,7 @@
             // Ignore stopped packages for now; we might query them
             // later during UI interaction.
             if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
-                if (DEBUG) Log.v(TAG, "Ignoring stopped authority " + info.authority);
+                if (VERBOSE) Log.v(TAG, "Ignoring stopped authority " + info.authority);
                 mTaskStoppedAuthorities.add(info.authority);
                 return;
             }
diff --git a/src/com/android/documentsui/selection/Range.java b/src/com/android/documentsui/selection/Range.java
index cd04f90..c7db84c 100644
--- a/src/com/android/documentsui/selection/Range.java
+++ b/src/com/android/documentsui/selection/Range.java
@@ -16,6 +16,7 @@
 package com.android.documentsui.selection;
 
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -71,7 +72,7 @@
         assert(mBegin != mEnd);
 
         if (position == mEnd) {
-            if (DEBUG) Log.v(SelectionManager.TAG, "Ignoring no-op revision for range: " + this);
+            if (VERBOSE) Log.v(SelectionManager.TAG, "Ignoring no-op revision for range: " + this);
         }
 
         if (mEnd > mBegin) {
@@ -148,4 +149,4 @@
     interface RangeUpdater {
         void updateForRange(int begin, int end, boolean selected, @RangeType int type);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/selection/SelectionManager.java b/src/com/android/documentsui/selection/SelectionManager.java
index 103ef8f..d6fcaef 100644
--- a/src/com/android/documentsui/selection/SelectionManager.java
+++ b/src/com/android/documentsui/selection/SelectionManager.java
@@ -298,6 +298,16 @@
         snapRangeSelection(pos, RANGE_PROVISIONAL);
     }
 
+    /*
+     * Starts and extends range selection in one go. This assumes item at startPos is not selected
+     * beforehand.
+     */
+    public void formNewSelectionRange(int startPos, int endPos) {
+        assert(!mSelection.contains(mAdapter.getModelId(startPos)));
+        startRangeSelection(startPos);
+        snapRangeSelection(endPos);
+    }
+
     /**
      * Sets the end point for the current range selection, started by a call to
      * {@link #startRangeSelection(int)}. This function should only be called when a range selection
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 88c98a4..71c66b7 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -17,6 +17,7 @@
 package com.android.documentsui.sidebar;
 
 import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.documentsui.base.Shared.VERBOSE;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -47,9 +48,10 @@
 import android.widget.ListView;
 
 import com.android.documentsui.ActionHandler;
-import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.Injector;
+import com.android.documentsui.Injector.Injected;
 import com.android.documentsui.ItemDragListener;
 import com.android.documentsui.R;
 import com.android.documentsui.base.BooleanConsumer;
@@ -102,11 +104,11 @@
     private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
     private @Nullable OnDragListener mDragListener;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
-    private ActionHandler mActionHandler;
+    @Injected
+    private Injector<?> mInjector;
 
-    // This dependency is informally "injected" from the owning Activity in our onCreate method.
-    private ActivityConfig mActivityConfig;
+    @Injected
+    private ActionHandler mActionHandler;
 
     public static RootsFragment show(FragmentManager fm, Intent includeApps) {
         final Bundle args = new Bundle();
@@ -130,6 +132,8 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
+        mInjector = getBaseActivity().getInjector();
+
         final View view = inflater.inflate(R.layout.fragment_roots, container, false);
         mList = (ListView) view.findViewById(R.id.roots_list);
         mList.setOnItemClickListener(mItemListener);
@@ -148,8 +152,8 @@
                             int x = (int) event.getX();
                             int y = (int) event.getY();
                             return onRightClick(v, x, y, () -> {
-                                getBaseActivity().getMenuManager()
-                                        .showContextMenu(RootsFragment.this, v, x, y);
+                                mInjector.menuManager.showContextMenu(
+                                        RootsFragment.this, v, x, y);
                             });
                         }
                         return false;
@@ -196,10 +200,9 @@
         final RootsCache roots = DocumentsApplication.getRootsCache(activity);
         final State state = activity.getDisplayState();
 
-        mActionHandler = activity.getActionHandler(null, false);
-        mActivityConfig = activity.getActivityConfig();
+        mActionHandler = mInjector.actions;
 
-        if (mActivityConfig.dragAndDropEnabled()) {
+        if (mInjector.config.dragAndDropEnabled()) {
             mDragListener = new ItemDragListener<RootsFragment>(this) {
                 @Override
                 public boolean handleDropEventChecked(View v, DragEvent event) {
@@ -213,7 +216,6 @@
             };
         }
 
-
         mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
             @Override
             public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
@@ -272,14 +274,14 @@
         Collections.sort(libraries, comp);
         Collections.sort(others, comp);
 
-        if (DEBUG) Log.v(TAG, "Adding library roots: " + libraries);
+        if (VERBOSE) Log.v(TAG, "Adding library roots: " + libraries);
         result.addAll(libraries);
         // Only add the spacer if it is actually separating something.
         if (!libraries.isEmpty() && !others.isEmpty()) {
             result.add(new SpacerItem());
         }
 
-        if (DEBUG) Log.v(TAG, "Adding plain roots: " + libraries);
+        if (VERBOSE) Log.v(TAG, "Adding plain roots: " + libraries);
         result.addAll(others);
 
         // Include apps that can handle this intent too.
@@ -295,7 +297,7 @@
      * special section at bottom).
      */
     private void includeHandlerApps(Intent handlerAppIntent, List<Item> result) {
-        if (DEBUG) Log.v(TAG, "Adding handler apps for intent: " + handlerAppIntent);
+        if (VERBOSE) Log.v(TAG, "Adding handler apps for intent: " + handlerAppIntent);
         Context context = getContext();
         final PackageManager pm = context.getPackageManager();
         final List<ResolveInfo> infos = pm.queryIntentActivities(
@@ -361,8 +363,8 @@
         return mList.requestFocus();
     }
 
-    private BaseActivity<?> getBaseActivity() {
-        return (BaseActivity<?>) getActivity();
+    private BaseActivity getBaseActivity() {
+        return (BaseActivity) getActivity();
     }
 
     @Override
@@ -396,7 +398,7 @@
     }
 
     @Override
-    public void setDropTargetHighlight(View v, boolean highlight) {
+    public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
         // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
         RootItemView itemView = (RootItemView) v;
         itemView.setHighlight(highlight);
@@ -409,8 +411,8 @@
         AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
         final Item item = mAdapter.getItem(adapterMenuInfo.position);
 
-        BaseActivity<?> activity = getBaseActivity();
-        item.createContextMenu(menu, activity.getMenuInflater(), activity.getMenuManager());
+        BaseActivity activity = getBaseActivity();
+        item.createContextMenu(menu, activity.getMenuInflater(), mInjector.menuManager);
     }
 
     @Override
diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java
index 39bebfe..c94a16f 100644
--- a/src/com/android/documentsui/ui/DialogController.java
+++ b/src/com/android/documentsui/ui/DialogController.java
@@ -42,7 +42,7 @@
         }
 
         @Override
-        public void showFileOperationFailures(int status, int opType, int docCount) {
+        public void showFileOperationStatus(int status, int opType, int docCount) {
             throw new UnsupportedOperationException();
         }
 
@@ -58,7 +58,7 @@
     };
 
     void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback);
-    void showFileOperationFailures(int status, int opType, int docCount);
+    void showFileOperationStatus(int status, int opType, int docCount);
     void showNoApplicationFound();
     void showDocumentsClipped(int size);
 
@@ -111,10 +111,10 @@
         }
 
         @Override
-        public void showFileOperationFailures(
+        public void showFileOperationStatus(
                 @Status int status, @OpType int opType, int docCount) {
             if (status == FileOperations.Callback.STATUS_REJECTED) {
-                Snackbars.showPasteFailed(mActivity);
+                Snackbars.showOperationFailed(mActivity);
                 return;
             }
 
@@ -131,7 +131,7 @@
                     Snackbars.showCopy(mActivity, docCount);
                     break;
                 case FileOperationService.OPERATION_DELETE:
-                    // We don't show anything for deletion.
+                    Snackbars.showDelete(mActivity, docCount);
                     break;
                 default:
                     throw new UnsupportedOperationException("Unsupported Operation: " + opType);
diff --git a/src/com/android/documentsui/ui/Snackbars.java b/src/com/android/documentsui/ui/Snackbars.java
index bab58f0..a3519e1 100644
--- a/src/com/android/documentsui/ui/Snackbars.java
+++ b/src/com/android/documentsui/ui/Snackbars.java
@@ -43,8 +43,17 @@
         makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
     }
 
-    public static final void showPasteFailed(Activity activity) {
-        makeSnackbar(activity, R.string.clipboard_files_cannot_paste, Snackbar.LENGTH_SHORT).show();
+    public static final void showDelete(Activity activity, int docCount) {
+        CharSequence message = Shared.getQuantityString(activity, R.plurals.deleting, docCount);
+        makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
+    }
+
+    public static final void showOperationFailed(Activity activity) {
+        makeSnackbar(activity, R.string.file_operation_error, Snackbar.LENGTH_SHORT).show();
+    }
+
+    public static final void showRenameFailed(Activity activity) {
+        makeSnackbar(activity, R.string.rename_error, Snackbar.LENGTH_SHORT).show();
     }
 
     public static final Snackbar makeSnackbar(Activity activity, @StringRes int messageId,
diff --git a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
index 76db4e1..d196c33 100644
--- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
+++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
@@ -25,6 +25,8 @@
 public final class TestFocusHandler implements FocusHandler {
 
     public boolean handleKey;
+    public int focusPos = 0;
+    public String focusModelId;
 
     @Override
     public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
@@ -36,17 +38,28 @@
     }
 
     @Override
-    public void restoreLastFocus() {
+    public boolean advanceFocusArea() {
+        return true;
+    }
+
+    @Override
+    public boolean focusDirectoryList() {
+        return true;
+    }
+
+    @Override
+    public boolean hasFocusedItem() {
+        return true;
     }
 
     @Override
     public int getFocusPosition() {
-        return 0;
+        return focusPos;
     }
 
     @Override
     public String getFocusModelId() {
-        return null;
+        return focusModelId;
     }
 
     @Override
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index bd8adac..1db67f9 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -19,9 +19,11 @@
 import android.content.Intent;
 
 import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.ActionHandler;
 import com.android.documentsui.TestActivity;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.Model;
 
 public class TestActionHandler extends AbstractActionHandler<TestActivity> {
 
@@ -75,4 +77,9 @@
     public void initLocation(Intent intent) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public <T extends ActionHandler> T reset(Model model, boolean searchMode) {
+        return null;
+    }
 }
diff --git a/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java b/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
index fb821ee..890af24 100644
--- a/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
+++ b/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
@@ -36,6 +36,8 @@
     public boolean nextIsDocumentsUri;
     public @Nullable Path nextPath;
 
+    public TestEventHandler<Uri> lastUri = new TestEventHandler<>();
+
     @Override
     public DocumentInfo getRootDocument(RootInfo root) {
         return nextRootDocument;
@@ -62,7 +64,8 @@
     }
 
     @Override
-    public Path findPath(Uri docUri) throws RemoteException {
+    public Path findDocumentPath(Uri docUri) throws RemoteException {
+        lastUri.accept(docUri);
         return nextPath;
     }
 }
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index 076e621..f61a19e 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -95,8 +95,7 @@
                         | Document.FLAG_PARTIAL);
         FILE_ARCHIVE = model.createFile(
                 "whatsinthere.zip",
-                Document.FLAG_ARCHIVE
-                        | Document.FLAG_SUPPORTS_DELETE);
+                Document.FLAG_SUPPORTS_DELETE);
         FILE_VIRTUAL = model.createDocument(
                 "virtualdoc.vnd",
                 "application/vnd.google-apps.document",
diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java
index e3135be..cd48737 100644
--- a/tests/common/com/android/documentsui/ui/TestDialogController.java
+++ b/tests/common/com/android/documentsui/ui/TestDialogController.java
@@ -41,7 +41,7 @@
     }
 
     @Override
-    public void showFileOperationFailures(int status, int opType, int docCount) {
+    public void showFileOperationStatus(int status, int opType, int docCount) {
         if (status == FileOperations.Callback.STATUS_REJECTED) {
             mFileOpFailed = true;
         }
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index b3379bf..7908c68 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -28,6 +28,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.files.LauncherActivity;
 import com.android.documentsui.testing.Roots;
 import com.android.documentsui.testing.TestEnv;
@@ -78,6 +79,11 @@
             public void initLocation(Intent intent) {
                 throw new UnsupportedOperationException();
             }
+
+            @Override
+            public <T extends ActionHandler> T reset(Model model, boolean searchMode) {
+                return null;
+            }
         };
     }
 
diff --git a/tests/unit/com/android/documentsui/FocusManagerTest.java b/tests/unit/com/android/documentsui/FocusManagerTest.java
index 898f479..d7fdedf 100644
--- a/tests/unit/com/android/documentsui/FocusManagerTest.java
+++ b/tests/unit/com/android/documentsui/FocusManagerTest.java
@@ -21,8 +21,11 @@
 
 import com.android.documentsui.dirlist.TestData;
 import com.android.documentsui.dirlist.TestModel;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.testing.SelectionManagers;
 import com.android.documentsui.testing.TestRecyclerView;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @SmallTest
@@ -34,11 +37,14 @@
 
     private FocusManager mManager;
     private TestRecyclerView mView;
+    private SelectionManager mSelectionMgr;
 
     @Override
     public void setUp() throws Exception {
         mView = TestRecyclerView.create(ITEMS);
-        mManager = new FocusManager(0).reset(mView, new TestModel(TEST_AUTHORITY));
+        mSelectionMgr = SelectionManagers.createTestInstance(ITEMS);
+        mManager = new FocusManager(mSelectionMgr, null, null, 0)
+                .reset(mView, new TestModel(TEST_AUTHORITY));
     }
 
     public void testFocus() {
@@ -54,4 +60,16 @@
        // Should only be called once
        mView.assertItemViewFocused(10);
     }
+
+    public void testFocusDirectoryList_noItemsToFocus() {
+        mView = TestRecyclerView.create(new ArrayList<>());
+        mManager = new FocusManager(SelectionManagers.createTestInstance(), null, null, 0)
+                .reset(mView, new TestModel(TEST_AUTHORITY));
+        assertFalse(mManager.focusDirectoryList());
+    }
+
+    public void testFocusDirectoryList_hasSelection() {
+        mSelectionMgr.toggleSelection("0");
+        assertFalse(mManager.focusDirectoryList());
+    }
 }
diff --git a/tests/unit/com/android/documentsui/ItemDragListenerTest.java b/tests/unit/com/android/documentsui/ItemDragListenerTest.java
index 865d8b6..71799ac 100644
--- a/tests/unit/com/android/documentsui/ItemDragListenerTest.java
+++ b/tests/unit/com/android/documentsui/ItemDragListenerTest.java
@@ -202,7 +202,7 @@
         private View mLastEnteredView;
 
         @Override
-        public void setDropTargetHighlight(View v, boolean highlight) {
+        public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
             mHighlightedView = highlight ? v : null;
         }
 
diff --git a/tests/unit/com/android/documentsui/archives/ArchiveTest.java b/tests/unit/com/android/documentsui/archives/ArchiveTest.java
index 4ea3a01..e69096c 100644
--- a/tests/unit/com/android/documentsui/archives/ArchiveTest.java
+++ b/tests/unit/com/android/documentsui/archives/ArchiveTest.java
@@ -157,6 +157,17 @@
                 new ArchiveId(ARCHIVE_URI, "/").toDocumentId(), null, null);
 
         assertTrue(cursor.moveToFirst());
+        assertEquals(
+                new ArchiveId(ARCHIVE_URI, "/file1.txt").toDocumentId(),
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("file1.txt",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals("text/plain",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(13,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+        assertTrue(cursor.moveToNext());
         assertEquals(new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(),
                 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
         assertEquals("dir1",
@@ -177,17 +188,6 @@
         assertEquals(0,
                 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
 
-        assertTrue(cursor.moveToNext());
-        assertEquals(
-                new ArchiveId(ARCHIVE_URI, "/file1.txt").toDocumentId(),
-                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
-        assertEquals("file1.txt",
-                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
-        assertEquals("text/plain",
-                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
-        assertEquals(13,
-                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
-
         assertFalse(cursor.moveToNext());
 
         // Check if querying children works too.
diff --git a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
index fc5933a..3a0cc4c 100644
--- a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
@@ -188,7 +188,7 @@
     private static class TestDragHost implements ItemDragListener.DragHost {
 
         @Override
-        public void setDropTargetHighlight(View v, boolean highlight) {
+        public void setDropTargetHighlight(View v, Object localState, boolean highlight) {
         }
 
         @Override
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index 41ed786..bf27f81 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -47,7 +47,9 @@
 
     private UserInputHandler<TestEvent> mInputHandler;
     private TestActionHandler mActionHandler;
+    private TestFocusHandler mFocusHandler;
     private SelectionProbe mSelection;
+    private SelectionManager mSelectionMgr;
     private TestPredicate<DocumentDetails> mCanSelect;
     private TestEventHandler<InputEvent> mContextMenuClickHandler;
     private TestEventHandler<InputEvent> mDragAndDropHandler;
@@ -58,19 +60,20 @@
     @Before
     public void setUp() {
 
-        SelectionManager selectionMgr = SelectionManagers.createTestInstance(ITEMS);
+        mSelectionMgr = SelectionManagers.createTestInstance(ITEMS);
         mActionHandler = new TestActionHandler();
 
-        mSelection = new SelectionProbe(selectionMgr);
+        mSelection = new SelectionProbe(mSelectionMgr);
         mCanSelect = new TestPredicate<>();
         mContextMenuClickHandler = new TestEventHandler<>();
         mDragAndDropHandler = new TestEventHandler<>();
         mGestureSelectHandler = new TestEventHandler<>();
+        mFocusHandler = new TestFocusHandler();
 
         mInputHandler = new UserInputHandler<>(
                 mActionHandler,
-                new TestFocusHandler(),
-                selectionMgr,
+                mFocusHandler,
+                mSelectionMgr,
                 (MotionEvent event) -> {
                     throw new UnsupportedOperationException("Not exercised in tests.");
                 },
@@ -130,6 +133,18 @@
     }
 
     @Test
+    public void testConfirmedShiftClick_ExtendsSelectionFromOriginFocus() {
+        mFocusHandler.focusPos = 7;
+        mFocusHandler.focusModelId = "7";
+        // This is a hack-y test, since the real FocusManager would've set range begin itself.
+        mSelectionMgr.setSelectionRangeBegin(7);
+        mSelection.assertNoSelection();
+
+        mInputHandler.onSingleTapConfirmed(mEvent.at(11).shift().build());
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+    }
+
+    @Test
     public void testUnconfirmedShiftClick_RotatesAroundOrigin() {
         mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
 
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 5e26cd8..da9a05c 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -24,6 +24,7 @@
 
 import android.content.Intent;
 import android.net.Uri;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Path;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -140,12 +141,40 @@
         mEnv.docs.nextDocuments =
                 Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1, TestEnv.FILE_GIF);
 
+        mActivity.refreshCurrentRootAndDirectory.assertNotCalled();
         Intent intent = mActivity.getIntent();
-        intent.setAction(Intent.ACTION_GET_CONTENT);
-        intent.setData(TestEnv.FILE_GIF.derivedUri);
+        intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, TestEnv.FILE_GIF.derivedUri);
         mHandler.initLocation(intent);
 
         assertStackEquals(TestRootsAccess.HOME, Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1));
+        mActivity.refreshCurrentRootAndDirectory.assertCalled();
+    }
+
+    @Test
+    public void testInitLocation_LaunchToDocuments_convertsTreeUriToDocumentUri() throws Exception {
+        mEnv.docs.nextIsDocumentsUri = true;
+        mEnv.docs.nextPath = new Path(
+                TestRootsAccess.HOME.rootId,
+                Arrays.asList(
+                        TestEnv.FOLDER_0.documentId,
+                        TestEnv.FOLDER_1.documentId,
+                        TestEnv.FILE_GIF.documentId));
+        mEnv.docs.nextDocuments =
+                Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1, TestEnv.FILE_GIF);
+
+        Intent intent = mActivity.getIntent();
+        intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+        final Uri treeBaseUri = DocumentsContract.buildTreeDocumentUri(
+                TestRootsAccess.HOME.authority, TestEnv.FOLDER_0.documentId);
+        final Uri treeDocUri = DocumentsContract.buildDocumentUriUsingTree(
+                treeBaseUri, TestEnv.FILE_GIF.documentId);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, treeDocUri);
+        mHandler.initLocation(intent);
+
+        assertStackEquals(TestRootsAccess.HOME, Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1));
+        mEnv.docs.lastUri.assertLastArgument(TestEnv.FILE_GIF.derivedUri);
+        mActivity.refreshCurrentRootAndDirectory.assertCalled();
     }
 
     @Test
diff --git a/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java b/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
index d25395a..3e1e7ed 100644
--- a/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
+++ b/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
@@ -202,7 +202,6 @@
         mManager.snapProvisionalRangeSelection(18);
         mSelection.assertRangeSelected(11, 18);
         mManager.endRangeSelection();
-
         mSelection.assertRangeSelected(13, 15);
         mSelection.assertRangeSelected(11, 11);
         mSelection.assertSelectionSize(4);