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);