summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flags.aconfig7
-rw-r--r--proguard.flags6
-rw-r--r--res/flag(!com.android.documentsui.flags.use_material3)/menu/activity.xml7
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/column_headers.xml4
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/item_doc_list.xml3
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/menu/action_mode_menu.xml3
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml7
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml1
-rw-r--r--res/values-af/strings.xml1
-rw-r--r--res/values-am/strings.xml1
-rw-r--r--res/values-ar/strings.xml1
-rw-r--r--res/values-as/strings.xml1
-rw-r--r--res/values-az/strings.xml1
-rw-r--r--res/values-b+sr+Latn/strings.xml1
-rw-r--r--res/values-be/strings.xml1
-rw-r--r--res/values-bg/strings.xml1
-rw-r--r--res/values-bn/strings.xml1
-rw-r--r--res/values-bs/strings.xml1
-rw-r--r--res/values-ca/strings.xml1
-rw-r--r--res/values-cs/strings.xml1
-rw-r--r--res/values-da/strings.xml1
-rw-r--r--res/values-de/strings.xml1
-rw-r--r--res/values-el/strings.xml1
-rw-r--r--res/values-en-rAU/strings.xml1
-rw-r--r--res/values-en-rCA/strings.xml1
-rw-r--r--res/values-en-rGB/strings.xml1
-rw-r--r--res/values-en-rIN/strings.xml1
-rw-r--r--res/values-es-rUS/strings.xml1
-rw-r--r--res/values-es/strings.xml1
-rw-r--r--res/values-et/strings.xml1
-rw-r--r--res/values-eu/strings.xml1
-rw-r--r--res/values-fa/strings.xml1
-rw-r--r--res/values-fi/strings.xml1
-rw-r--r--res/values-fr-rCA/strings.xml1
-rw-r--r--res/values-fr/strings.xml1
-rw-r--r--res/values-gl/strings.xml1
-rw-r--r--res/values-gu/strings.xml1
-rw-r--r--res/values-hi/strings.xml1
-rw-r--r--res/values-hr/strings.xml1
-rw-r--r--res/values-hu/strings.xml1
-rw-r--r--res/values-hy/strings.xml1
-rw-r--r--res/values-in/strings.xml1
-rw-r--r--res/values-is/strings.xml1
-rw-r--r--res/values-it/strings.xml1
-rw-r--r--res/values-iw/strings.xml1
-rw-r--r--res/values-ja/strings.xml1
-rw-r--r--res/values-ka/strings.xml1
-rw-r--r--res/values-kk/strings.xml1
-rw-r--r--res/values-km/strings.xml1
-rw-r--r--res/values-kn/strings.xml17
-rw-r--r--res/values-ko/strings.xml1
-rw-r--r--res/values-ky/strings.xml1
-rw-r--r--res/values-lo/strings.xml1
-rw-r--r--res/values-lt/strings.xml1
-rw-r--r--res/values-lv/strings.xml1
-rw-r--r--res/values-mk/strings.xml1
-rw-r--r--res/values-ml/strings.xml1
-rw-r--r--res/values-mn/strings.xml1
-rw-r--r--res/values-mr/strings.xml1
-rw-r--r--res/values-ms/strings.xml1
-rw-r--r--res/values-my/strings.xml1
-rw-r--r--res/values-nb/strings.xml1
-rw-r--r--res/values-ne/strings.xml1
-rw-r--r--res/values-nl/strings.xml1
-rw-r--r--res/values-or/strings.xml1
-rw-r--r--res/values-pa/strings.xml1
-rw-r--r--res/values-pl/strings.xml1
-rw-r--r--res/values-pt-rBR/strings.xml1
-rw-r--r--res/values-pt-rPT/strings.xml1
-rw-r--r--res/values-pt/strings.xml1
-rw-r--r--res/values-ro/strings.xml1
-rw-r--r--res/values-ru/strings.xml1
-rw-r--r--res/values-si/strings.xml1
-rw-r--r--res/values-sk/strings.xml1
-rw-r--r--res/values-sl/strings.xml1
-rw-r--r--res/values-sq/strings.xml1
-rw-r--r--res/values-sr/strings.xml1
-rw-r--r--res/values-sv/strings.xml1
-rw-r--r--res/values-sw/strings.xml1
-rw-r--r--res/values-ta/strings.xml1
-rw-r--r--res/values-te/strings.xml1
-rw-r--r--res/values-th/strings.xml1
-rw-r--r--res/values-tl/strings.xml1
-rw-r--r--res/values-tr/strings.xml1
-rw-r--r--res/values-uk/strings.xml1
-rw-r--r--res/values-ur/strings.xml1
-rw-r--r--res/values-uz/strings.xml1
-rw-r--r--res/values-vi/strings.xml1
-rw-r--r--res/values-zh-rCN/strings.xml1
-rw-r--r--res/values-zh-rHK/strings.xml1
-rw-r--r--res/values-zh-rTW/strings.xml1
-rw-r--r--res/values-zu/strings.xml1
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/com/android/documentsui/AbstractActionHandler.java88
-rw-r--r--src/com/android/documentsui/MenuManager.java20
-rw-r--r--src/com/android/documentsui/MultiRootDocumentsLoader.java2
-rw-r--r--src/com/android/documentsui/RecentsLoader.java6
-rw-r--r--src/com/android/documentsui/base/Menus.java4
-rw-r--r--src/com/android/documentsui/dirlist/DirectoryFragment.java4
-rw-r--r--src/com/android/documentsui/files/FilesActivity.java15
-rw-r--r--src/com/android/documentsui/files/MenuManager.java5
-rw-r--r--src/com/android/documentsui/loaders/BaseFileLoader.kt208
-rw-r--r--src/com/android/documentsui/loaders/FolderLoader.kt79
-rw-r--r--src/com/android/documentsui/loaders/QueryOptions.kt82
-rw-r--r--src/com/android/documentsui/loaders/SearchLoader.kt246
-rw-r--r--src/com/android/documentsui/sorting/HeaderCell.java24
-rw-r--r--src/com/android/documentsui/sorting/TableHeaderController.java38
-rw-r--r--tests/common/com/android/documentsui/bots/SortBot.java27
-rw-r--r--tests/common/com/android/documentsui/testing/TestDirectoryDetails.java6
-rw-r--r--tests/common/com/android/documentsui/testing/TestMenu.java6
-rw-r--r--tests/functional/com/android/documentsui/SortDocumentUiTest.java71
-rw-r--r--tests/unit/com/android/documentsui/files/MenuManagerTest.java4
-rw-r--r--tests/unit/com/android/documentsui/loaders/BaseLoaderTest.kt64
-rw-r--r--tests/unit/com/android/documentsui/loaders/FolderLoaderTest.kt55
-rw-r--r--tests/unit/com/android/documentsui/loaders/SearchLoaderTest.kt71
-rw-r--r--tests/unit/com/android/documentsui/picker/MenuManagerTest.java13
116 files changed, 1164 insertions, 114 deletions
diff --git a/flags.aconfig b/flags.aconfig
index 54403563a..5af4a2656 100644
--- a/flags.aconfig
+++ b/flags.aconfig
@@ -30,3 +30,10 @@ flag {
description: "Enables desktop file handling."
bug: "381778967"
}
+
+flag {
+ name: "visual_signals"
+ namespace: "documentsui"
+ description: "Enables in-app progress display of file operations"
+ bug: "378011512"
+}
diff --git a/proguard.flags b/proguard.flags
index a0f96ae09..e71b073c8 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -23,7 +23,10 @@
}
# To prevent class not found exception in org.brotli.dec.Dictionary
--keep final class org.brotli.dec.DictionaryData
+# TODO(b/373579455): Evaluate if <init> needs to be kept.
+-keep final class org.brotli.dec.DictionaryData {
+ void <init>();
+}
# keep rule generated after running trace references on the test app against DocumentsUIGoogle.jar
# TODO(b/339312616): Remove after a more permanent fix is available
@@ -103,6 +106,7 @@
int inspector_details_view;
int option_menu_create_dir;
int option_menu_debug;
+ int option_menu_extract_all;
int option_menu_inspect;
int option_menu_launcher;
int option_menu_new_window;
diff --git a/res/flag(!com.android.documentsui.flags.use_material3)/menu/activity.xml b/res/flag(!com.android.documentsui.flags.use_material3)/menu/activity.xml
index 39be106ca..9c3516aeb 100644
--- a/res/flag(!com.android.documentsui.flags.use_material3)/menu/activity.xml
+++ b/res/flag(!com.android.documentsui.flags.use_material3)/menu/activity.xml
@@ -67,6 +67,13 @@
android:visible="false"
app:showAsAction="never"/>
<item
+ android:id="@+id/option_menu_extract_all"
+ android:title="@string/menu_extract_all"
+ android:icon="@drawable/ic_menu_extract"
+ android:enabled="false"
+ android:visible="false"
+ app:showAsAction="always"/>
+ <item
android:id="@+id/option_menu_settings"
android:title="@string/menu_settings"
android:visible="false"
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/column_headers.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/column_headers.xml
index f24a28241..c21f39c72 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/column_headers.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/column_headers.xml
@@ -30,8 +30,8 @@
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="@dimen/list_item_height"
- android:paddingStart="@dimen/list_item_padding"
- android:paddingEnd="@dimen/list_item_width"
+ android:paddingStart="@dimen/list_item_padding_start"
+ android:paddingEnd="@dimen/list_item_padding_end"
android:orientation="horizontal">
<!-- Placeholder for icon -->
<View
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/item_doc_list.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/item_doc_list.xml
index b0db7ec38..5e1ca3438 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/item_doc_list.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/layout-w900dp/item_doc_list.xml
@@ -119,6 +119,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
+ android:textAlignment="viewStart"
style="@style/FileItemLabelStyle"/>
<TextView
@@ -126,6 +127,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
+ android:textAlignment="viewEnd"
style="@style/FileItemLabelStyle"/>
<TextView
@@ -133,6 +135,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
+ android:textAlignment="viewEnd"
style="@style/FileItemLabelStyle"/>
</LinearLayout>
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/menu/action_mode_menu.xml b/res/flag(com.android.documentsui.flags.use_material3)/menu/action_mode_menu.xml
index a87bee4ff..db4d581c1 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/menu/action_mode_menu.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/menu/action_mode_menu.xml
@@ -54,7 +54,8 @@
<item
android:id="@+id/action_menu_extract_to"
android:title="@string/menu_extract"
- android:showAsAction="never"
+ android:icon="@drawable/ic_menu_extract"
+ android:showAsAction="always"
android:visible="false" />
<item
android:id="@+id/action_menu_move_to"
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml b/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
index 16b0f82af..0e636a18c 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
@@ -77,6 +77,13 @@
android:visible="false"
app:showAsAction="never"/>
<item
+ android:id="@+id/option_menu_extract_all"
+ android:title="@string/menu_extract_all"
+ android:icon="@drawable/ic_menu_extract"
+ android:enabled="false"
+ android:visible="false"
+ app:showAsAction="always"/>
+ <item
android:id="@+id/option_menu_settings"
android:title="@string/menu_settings"
android:visible="false"
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
index 5fde94ab8..ce554c427 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
@@ -163,7 +163,6 @@
<item name="android:ellipsize">end</item>
<item name="android:minWidth">70dp</item>
<item name="android:singleLine">true</item>
- <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/FileItemLabelText</item>
</style>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 74f2c90eb..7ecedcd5d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Skuif na …"</string>
<string name="menu_compress" msgid="37539111904724188">"Pers saam"</string>
<string name="menu_extract" msgid="8171946945982532262">"Onttrek na …"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Uittreksel van alles …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Hernoem"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Kry inligting"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Wys versteekte lêers"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 428783c41..40cda4a01 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ውሰድ ወደ…"</string>
<string name="menu_compress" msgid="37539111904724188">"ጭመቅ"</string>
<string name="menu_extract" msgid="8171946945982532262">"አውጣ ወደ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ሁሉንም አውጣ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ዳግም ሰይም"</string>
<string name="menu_inspect" msgid="7279855349299446224">"መረጃ አግኝ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"የተደበቁ ፋይሎችን አሳይ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 84917168f..329b59df4 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"نقل إلى..."</string>
<string name="menu_compress" msgid="37539111904724188">"ضغط"</string>
<string name="menu_extract" msgid="8171946945982532262">"الاستخراج إلى…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"استخراج الكل…"</string>
<string name="menu_rename" msgid="1883113442688817554">"إعادة تسمية"</string>
<string name="menu_inspect" msgid="7279855349299446224">"الحصول على المعلومات"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"إظهار الملفات المخفية"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 285cd0bfe..72d34172e 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ইয়ালৈ স্থানান্তৰ কৰক…"</string>
<string name="menu_compress" msgid="37539111904724188">"সংকুচিত কৰক"</string>
<string name="menu_extract" msgid="8171946945982532262">"ইয়ালৈ আহৰণ কৰক…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"আটাইবোৰ আহৰণ কৰক…"</string>
<string name="menu_rename" msgid="1883113442688817554">"নতুন নাম দিয়ক"</string>
<string name="menu_inspect" msgid="7279855349299446224">"তথ্য পাওক"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"লুকুৱাই থোৱা ফাইল দেখুৱাওক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index dfba630de..4f26eb52e 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Daşıyın..."</string>
<string name="menu_compress" msgid="37539111904724188">"Sıxışdırın"</string>
<string name="menu_extract" msgid="8171946945982532262">"Çıxarın…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Hamısını çıxarın…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Adını dəyişdirin"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Məlumat əldə edin"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Gizli faylları göstərin"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index fca11a776..4399d8353 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Premesti u…"</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimuj"</string>
<string name="menu_extract" msgid="8171946945982532262">"Izdvoj u…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Izdvoji sve…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Preimenuj"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Prikaži informacije"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Prikazuj skrivene datoteke"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 4c2a98cec..723a8fbd6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Перамясціць у…"</string>
<string name="menu_compress" msgid="37539111904724188">"Сціснуць"</string>
<string name="menu_extract" msgid="8171946945982532262">"Выняць у…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Выняць усе…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Перайменаваць"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Атрымаць інфармацыю"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Паказваць схаваныя файлы"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index e1444b40b..7a00de0fb 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Преместване във…"</string>
<string name="menu_compress" msgid="37539111904724188">"Компресиране"</string>
<string name="menu_extract" msgid="8171946945982532262">"Извличане в/ъв…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Извличане на всички…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Преименуване"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Получаване на информация"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Показване на скрити файлове"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index f51afc50f..4b25ce368 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"এতে সরান…"</string>
<string name="menu_compress" msgid="37539111904724188">"সঙ্কুচিত করুন"</string>
<string name="menu_extract" msgid="8171946945982532262">"এখানে রাখুন…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"সব এক্সট্র্যাক্ট করুন…"</string>
<string name="menu_rename" msgid="1883113442688817554">"পুনঃনামকরণ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"তথ্য পান"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"লুকানো ফাইল দেখুন"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index bb5627dbe..2564464ff 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Premjesti u…"</string>
<string name="menu_compress" msgid="37539111904724188">"Kompresiraj"</string>
<string name="menu_extract" msgid="8171946945982532262">"Izdvoji u…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Izdvajanje svega…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Promijeni naziv"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Prikaži informacije"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Prikaži skrivene fajlove"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 76d717243..3367fc94b 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mou a…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimeix"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extreu a…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extreu-ho tot…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Canvia el nom"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obtén informació"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostra els fitxers amagats"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 44ee41907..d58addbf9 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Přesunout do…"</string>
<string name="menu_compress" msgid="37539111904724188">"Zkomprimovat"</string>
<string name="menu_extract" msgid="8171946945982532262">"Rozbalit do…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrahovat vše…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Přejmenovat"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Zobrazit informace"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Zobrazit skryté soubory"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index c0d5cf0c8..facc58ba5 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Flyt til…"</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimer"</string>
<string name="menu_extract" msgid="8171946945982532262">"Pak ud i…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Pak alle ud…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Omdøb"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Få oplysninger"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Vis skjulte filer"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 381f3b696..0b7943238 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Verschieben nach…"</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimieren"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrahieren nach…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Alle extrahieren…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Umbenennen"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Weitere Informationen"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Ausgeblendete Dateien anzeigen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 9233d5d20..706630dda 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Μετακίνηση σε…"</string>
<string name="menu_compress" msgid="37539111904724188">"Συμπίεση"</string>
<string name="menu_extract" msgid="8171946945982532262">"Εξαγωγή σε…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Εξαγωγή όλων…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Μετονομασία"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Λήψη πληροφοριών"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Εμφάνιση κρυφών αρχείων"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 5523ed3f1..ad4cbfeea 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Move to…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compress"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extract to…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extract all…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Rename"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Get info"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Show hidden files"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 0dcc1f5c1..df51b525d 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Move to…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compress"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extract to…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extract all…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Rename"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Get info"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Show hidden files"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 5523ed3f1..ad4cbfeea 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Move to…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compress"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extract to…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extract all…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Rename"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Get info"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Show hidden files"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 5523ed3f1..ad4cbfeea 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Move to…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compress"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extract to…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extract all…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Rename"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Get info"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Show hidden files"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index c7f1e717e..c5ce3eb95 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover a…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimir"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extraer en…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extraer todo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Cambiar nombre"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obtener información"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostrar archivos ocultos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 573a6768b..8850843c4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover a…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimir"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extraer a…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extraer todo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Cambiar nombre"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obtener información"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostrar archivos ocultos"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index e7dbe750b..21a9aaabc 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Teisalda asukohta …"</string>
<string name="menu_compress" msgid="37539111904724188">"Tihenda"</string>
<string name="menu_extract" msgid="8171946945982532262">"Ekstrakti …"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Ekstrakti kõik …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Nimeta ümber"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Hangi teavet"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Kuva peidetud failid"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index c8dbc0577..46a1e2bfb 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Eraman hona…"</string>
<string name="menu_compress" msgid="37539111904724188">"Konprimatu"</string>
<string name="menu_extract" msgid="8171946945982532262">"Atera hona…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Atera guztia…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Aldatu izena"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Lortu informazioa"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Erakutsi fitxategi ezkutuak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index f5a469861..1c9e0c1e5 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"انتقال به…"</string>
<string name="menu_compress" msgid="37539111904724188">"فشرده کردن"</string>
<string name="menu_extract" msgid="8171946945982532262">"استخراج در…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"استخراج همه…"</string>
<string name="menu_rename" msgid="1883113442688817554">"تغییر نام"</string>
<string name="menu_inspect" msgid="7279855349299446224">"دریافت اطلاعات"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"فایل‌های پنهان نشان داده شود"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 6fa10bf1a..6a8cb5633 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Siirrä kohteeseen…"</string>
<string name="menu_compress" msgid="37539111904724188">"Pakkaa"</string>
<string name="menu_extract" msgid="8171946945982532262">"Pura kohteeseen…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Pura kaikki…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Nimeä uudelleen"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Näytä tiedot"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Näytä piilotetut tiedostot"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index c61478375..ede583abb 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Déplacer dans…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compresser"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extraire vers…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Tout extraire…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Renommer"</string>
<string name="menu_inspect" msgid="7279855349299446224">"En savoir plus"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Afficher les fichiers masqués"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 85bdeb0fd..01935a805 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Déplacer vers…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compresser"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extraire sur…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Tout extraire…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Renommer"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obtenir les informations"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Afficher les fichiers masqués"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 1b9fe1d2f..db1aeccf5 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover a…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimir"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extraer en..."</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extraer todo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Cambiar nome"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obter información"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Amosar ficheiros ocultos"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 766540c51..e161c1656 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"આમાં ખસેડો…"</string>
<string name="menu_compress" msgid="37539111904724188">"સંકુચિત કરો"</string>
<string name="menu_extract" msgid="8171946945982532262">"આમાં એક્સટ્રેક્ટ કરો…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"બધું એક્સટ્રેક્ટ કરો…"</string>
<string name="menu_rename" msgid="1883113442688817554">"નામ બદલો"</string>
<string name="menu_inspect" msgid="7279855349299446224">"માહિતી મેળવો"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"છુપાવેલી ફાઇલો બતાવો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 3183bd297..96c4dcfc2 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"यहां ले जाएं…"</string>
<string name="menu_compress" msgid="37539111904724188">"कंप्रेस करें"</string>
<string name="menu_extract" msgid="8171946945982532262">"यहां निकालें…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"सभी को एक्सट्रैक्ट करें…"</string>
<string name="menu_rename" msgid="1883113442688817554">"नाम बदलें"</string>
<string name="menu_inspect" msgid="7279855349299446224">"जानकारी पाएं"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"छिपी हुई फ़ाइलें दिखाएं"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index e6cfbf934..f9536bc31 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Premjesti u…"</string>
<string name="menu_compress" msgid="37539111904724188">"Sažmi"</string>
<string name="menu_extract" msgid="8171946945982532262">"Izdvoji u…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Izdvoji sve…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Promijeni naziv"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Informacije"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Prikaži skrivene datoteke"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 9d7a2a675..8c40453d5 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Áthelyezés…"</string>
<string name="menu_compress" msgid="37539111904724188">"Tömörítés"</string>
<string name="menu_extract" msgid="8171946945982532262">"Kicsomagolás ide…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Összes kibontása"</string>
<string name="menu_rename" msgid="1883113442688817554">"Átnevezés"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Információ megjelenítése"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Rejtett fájlok megjelenítése"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index fb55c8bee..e848e0429 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Տեղափոխել…"</string>
<string name="menu_compress" msgid="37539111904724188">"Սեղմել"</string>
<string name="menu_extract" msgid="8171946945982532262">"Արտահանել…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Դուրս բերել բոլորը…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Վերանվանել"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Տեղեկություններ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Ցուցադրել թաքցված ֆայլերը"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index f89f539c3..5d79af348 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Pindahkan ke..."</string>
<string name="menu_compress" msgid="37539111904724188">"Kompresi"</string>
<string name="menu_extract" msgid="8171946945982532262">"Ekstrak ke…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Ekstrak semua…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Ganti nama"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Dapatkan info"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Tampilkan file tersembunyi"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 676c483d4..1601f65a5 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Færa í…"</string>
<string name="menu_compress" msgid="37539111904724188">"Þjappa"</string>
<string name="menu_extract" msgid="8171946945982532262">"Flytja út í…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Draga allt út …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Endurnefna"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Sækja upplýsingar"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Sýna faldar skrár"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 905815c87..f83c11d46 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Sposta in…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimi"</string>
<string name="menu_extract" msgid="8171946945982532262">"Estrai in…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Estrai tutto…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Rinomina"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Informazioni"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostra file nascosti"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 822177a1e..df5d6de4c 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"העברה אל…"</string>
<string name="menu_compress" msgid="37539111904724188">"דחיסה"</string>
<string name="menu_extract" msgid="8171946945982532262">"חילוץ לתיקייה…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"חילוץ הכול…"</string>
<string name="menu_rename" msgid="1883113442688817554">"שינוי שם"</string>
<string name="menu_inspect" msgid="7279855349299446224">"מידע על המסמך"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"הצגת קבצים מוסתרים"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 35685ca9a..e545ab1fa 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"移動…"</string>
<string name="menu_compress" msgid="37539111904724188">"圧縮"</string>
<string name="menu_extract" msgid="8171946945982532262">"次の場所に解凍…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"すべて抽出…"</string>
<string name="menu_rename" msgid="1883113442688817554">"名前を変更"</string>
<string name="menu_inspect" msgid="7279855349299446224">"詳細情報"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"非表示のファイルを表示"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 1a698668f..77c7cb2b1 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"გადაადგილება..."</string>
<string name="menu_compress" msgid="37539111904724188">"შეკუმშვა"</string>
<string name="menu_extract" msgid="8171946945982532262">"ამოღება…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ყველას ამოღება…"</string>
<string name="menu_rename" msgid="1883113442688817554">"გადარქმევა"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ინფორმაციის მიღება"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"გამოჩნდეს დამალული ფაილები"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 7d1f402e1..9bc4c9a51 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Тасымалдау…"</string>
<string name="menu_compress" msgid="37539111904724188">"Сығу"</string>
<string name="menu_extract" msgid="8171946945982532262">"Алынуда…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Барлығын шығарып алу…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Атын өзгерту"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Ақпарат алу"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Жасырын файлдарды көрсету"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 365efd1fe..87ac1cf9d 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ផ្លាស់ទីទៅ…"</string>
<string name="menu_compress" msgid="37539111904724188">"បង្ហាប់"</string>
<string name="menu_extract" msgid="8171946945982532262">"ស្រង់​ទៅ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ស្រង់ចេញទាំងអស់…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ប្ដូរឈ្មោះ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ទទួល​ព័ត៌មាន"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"បង្ហាញឯកសារ​ដែលបានលាក់"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1cf89f41f..bc89a9f08 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ಇದಕ್ಕೆ ಸರಿಸಿ…"</string>
<string name="menu_compress" msgid="37539111904724188">"ಕುಗ್ಗಿಸಿ"</string>
<string name="menu_extract" msgid="8171946945982532262">"ಇದಕ್ಕೆ ಬೇರ್ಪಡಿಸಲಾಗಿದೆ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ಎಲ್ಲವನ್ನೂ ಎಕ್ಸ್‌ಟ್ರ್ಯಾಕ್ಟ್ ಮಾಡಿ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ಮರುಹೆಸರಿಸು"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಿರಿ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"ಮರೆಮಾಡಿದ ಫೈಲ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
@@ -221,19 +222,19 @@
<item quantity="one"><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>
+ <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="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> ಫೈಲ್‌ಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
+ <item quantity="one"> <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="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> ಫೋಲ್ಡರ್‌ಗಳು ಮತ್ತು ಅವುಗಳ ವಿಷಯಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
+ <item quantity="one"> <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="other"> <xliff:g id="COUNT_1">%1$d</xliff:g> ಐಟಂಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
+ <item quantity="one"> <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="images_shortcut_label" msgid="2545168016070493574">"ಚಿತ್ರಗಳು"</string>
<string name="archive_loading_failed" msgid="7243436722828766996">"ಬ್ರೌಸಿಂಗ್‌ಗಾಗಿ ಆರ್ಕೈವ್ ಅನ್ನು ತೆರೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ. ಫೈಲ್ ದೋಷಪೂರಿತವಾಗಿದೆ ಅಥವಾ ಬೆಂಬಲಿಸದ ಸ್ವರೂಪದಲ್ಲಿರಬಹುದು."</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index bb313e892..374b95d93 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"다음으로 이동:"</string>
<string name="menu_compress" msgid="37539111904724188">"압축"</string>
<string name="menu_extract" msgid="8171946945982532262">"다음 위치에 추출..."</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"모두 추출…"</string>
<string name="menu_rename" msgid="1883113442688817554">"이름 바꾸기"</string>
<string name="menu_inspect" msgid="7279855349299446224">"정보 확인"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"숨겨진 파일 표시"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 83ac3fead..49ea76f6c 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Төмөнкүгө жылдыруу..."</string>
<string name="menu_compress" msgid="37539111904724188">"Кысуу"</string>
<string name="menu_extract" msgid="8171946945982532262">"Төмөнкүгө чыгаруу…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Баарын чыгаруу…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Аталышын өзгөртүү"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Маалымат алуу"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Жашырылган файлдар көрүнсүн"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 8794da0e8..32f741faa 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ຍ້າຍໄປໃສ່..."</string>
<string name="menu_compress" msgid="37539111904724188">"ບີບອັດ"</string>
<string name="menu_extract" msgid="8171946945982532262">"ແຕກໄຟລ໌ໄປ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ດຶງຂໍ້ມູນຈາກເອກະສານທັງໝົດ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ປ່ຽນຊື່"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ຂໍຂໍ້ມູນ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"ສະ​ແດງ​ໄຟ​ລ໌​ທີ່​ເຊື່ອງ​ໄວ້"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 88420bf91..9c3142dc4 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Perkelti į…"</string>
<string name="menu_compress" msgid="37539111904724188">"Suglaudinti"</string>
<string name="menu_extract" msgid="8171946945982532262">"Išskleisti į..."</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Išskleisti viską…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Pervardyti"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Gauti informacijos"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Rodyti paslėptus failus"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 384395b89..8afe403b6 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Pārvietot uz…"</string>
<string name="menu_compress" msgid="37539111904724188">"Saspiest"</string>
<string name="menu_extract" msgid="8171946945982532262">"Izvilkt..."</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Izgūt visu…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Pārdēvēt"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Iegūt informāciju"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Rādīt paslēptos failus"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 92d58f55a..94f840c94 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Премести во…"</string>
<string name="menu_compress" msgid="37539111904724188">"Компримирај"</string>
<string name="menu_extract" msgid="8171946945982532262">"Отпакувај во…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Извлечи сѐ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Преименувај"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Добијте информации"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Прикажи скриени датотеки"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 5de00993a..cb43ab59a 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ഇതിലേക്ക് നീക്കുക..."</string>
<string name="menu_compress" msgid="37539111904724188">"കംപ്രസ് ചെയ്യുക"</string>
<string name="menu_extract" msgid="8171946945982532262">"എക്സ്ട്രാക്റ്റുചെയ്യുക…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"എക്‌സ്ട്രാക്റ്റ് ചെയ്യൂ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"പേര് മാറ്റുക"</string>
<string name="menu_inspect" msgid="7279855349299446224">"വിവരങ്ങൾ നേടുക"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"മറച്ചിരിക്കുന്ന ഫയൽ കാണിക്കൂ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index cfbd53e1e..3172a07f4 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Дараахад зөөх"</string>
<string name="menu_compress" msgid="37539111904724188">"Шахах"</string>
<string name="menu_extract" msgid="8171946945982532262">"Дараахад задлах…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Бүгдийг задлах…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Нэр өөрчлөх"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Мэдээлэл авах"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Нуусан файлудыг харуул"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a98a5c279..4229508e2 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"यामध्ये हलवा…"</string>
<string name="menu_compress" msgid="37539111904724188">"कॉंप्रेस करा"</string>
<string name="menu_extract" msgid="8171946945982532262">"मध्ये काढा..."</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"सर्व काढा…"</string>
<string name="menu_rename" msgid="1883113442688817554">"नाव बदला"</string>
<string name="menu_inspect" msgid="7279855349299446224">"माहिती मिळवा"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"लपवलेल्या फाइल दाखवा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f72d99f10..7394358ca 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Alihkan ke…"</string>
<string name="menu_compress" msgid="37539111904724188">"Mampatkan"</string>
<string name="menu_extract" msgid="8171946945982532262">"Ekstrak ke…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Ekstrak semua…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Namakan semula"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Dapatkan maklumat"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Tunjukkan fail tersembunyi"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index e9c9f40ba..49d1278e6 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"အောက်ပါနေရာသို့ ရွှေ့ပါ…"</string>
<string name="menu_compress" msgid="37539111904724188">"ချုံ့ရန်"</string>
<string name="menu_extract" msgid="8171946945982532262">"ရွှေးချယ်ထည့်သွင်းရန်…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"အားလုံးထုတ်ယူရန်…"</string>
<string name="menu_rename" msgid="1883113442688817554">"အမည်ပြောင်းပါ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"အချက်အလက် ရယူရန်"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"ဝှက်ထားသည့်ဖိုင်များ ပြရန်"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 4fb2e0e36..4090122dd 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Flytt til"</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimer"</string>
<string name="menu_extract" msgid="8171946945982532262">"Pakk ut til …"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Pakk ut alt …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Gi nytt navn"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Hent informasjon"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Vis skjulte filer"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 550ae21e0..1e8dc9b36 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"निम्नमा सार्नुहोस्…"</string>
<string name="menu_compress" msgid="37539111904724188">"कम्प्रेस गर्नुहोस्"</string>
<string name="menu_extract" msgid="8171946945982532262">"यसमा एकस्ट्र्याक्ट गर्नुहोस्…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"सबै एक्स्ट्रयाक्ट गर्नुहोस्…"</string>
<string name="menu_rename" msgid="1883113442688817554">"पुनःनामाकरण गर्नुहोस्"</string>
<string name="menu_inspect" msgid="7279855349299446224">"जानकारी प्राप्त गर्नुहोस्"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"लुकाइएका फाइलहरू देखाउनुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index fc266ef30..fe37feb5b 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Verplaatsen naar…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimeren"</string>
<string name="menu_extract" msgid="8171946945982532262">"Uitpakken naar…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Alles uitpakken…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Naam wijzigen"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Informatie bekijken"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Verborgen bestanden tonen"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 4d3d59831..645c74bf8 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ଏଠାକୁ ନିଅନ୍ତୁ…"</string>
<string name="menu_compress" msgid="37539111904724188">"କମ୍ପ୍ରେସ୍ କରନ୍ତୁ"</string>
<string name="menu_extract" msgid="8171946945982532262">"ଏଠାକୁ ଏକ୍ସଟ୍ରାକ୍ଟ କରନ୍ତୁ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ସବୁ ଏକ୍ସଟ୍ରାକ୍ଟ କରନ୍ତୁ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ରିନେମ କରନ୍ତୁ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ସୂଚନା ପାଆନ୍ତୁ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"ଲୁକ୍କାୟିତ ଫାଇଲ ଦେଖାନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index f1390ce6e..4169a9e5c 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ਇਸ ਵਿੱਚ ਲਿਜਾਓ…"</string>
<string name="menu_compress" msgid="37539111904724188">"ਨਪੀੜੋ"</string>
<string name="menu_extract" msgid="8171946945982532262">"ਇਸ ਵਿੱਚ ਐਕਸਟ੍ਰੈਕਟ ਕਰੋ…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"ਸਭ ਐਕਸਟਰੈਕਟ ਕਰੋ…"</string>
<string name="menu_rename" msgid="1883113442688817554">"ਨਾਮ ਬਦਲੋ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"ਲੁਕਾਈਆਂ ਗਈਆਂ ਫ਼ਾਈਲਾਂ ਦਿਖਾਓ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 4a76fafb5..5c1360039 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Przenieś do…"</string>
<string name="menu_compress" msgid="37539111904724188">"Skompresuj"</string>
<string name="menu_extract" msgid="8171946945982532262">"Rozpakuj do…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Wyodrębnij wszystko…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Zmień nazwę"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Zobacz informacje"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Pokaż ukryte pliki"</string>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 8dd6249e8..3e492209d 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover para…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compactar"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrair para…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrair tudo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Renomear"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Ver informações"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostrar arquivos ocultos"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 03a36e95c..972ef954d 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover para..."</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimir"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrair para…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrair tudo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Mudar o nome"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Obter informações"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostrar ficheiros ocultos"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 8dd6249e8..3e492209d 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mover para…"</string>
<string name="menu_compress" msgid="37539111904724188">"Compactar"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrair para…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrair tudo…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Renomear"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Ver informações"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Mostrar arquivos ocultos"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index b3bb84146..d0a89f345 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Mută în…"</string>
<string name="menu_compress" msgid="37539111904724188">"Comprimă"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrage în…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrage tot…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Redenumește"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Vezi informațiile"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Afișează fișierele ascunse"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index cedb653d5..f345cef8d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Переместить в..."</string>
<string name="menu_compress" msgid="37539111904724188">"Сжать"</string>
<string name="menu_extract" msgid="8171946945982532262">"Извлечь"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Извлечь все…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Переименовать"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Сведения о файле"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Показывать скрытые файлы"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 3c2b2429e..48d8f5f75 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"වෙත ගෙනයන්න..."</string>
<string name="menu_compress" msgid="37539111904724188">"සම්පීඩනය කරන්න"</string>
<string name="menu_extract" msgid="8171946945982532262">"උපුටා ගන්න…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"සියල්ල උපුටා ගන්න…"</string>
<string name="menu_rename" msgid="1883113442688817554">"යළි නම් කරන්න"</string>
<string name="menu_inspect" msgid="7279855349299446224">"තොරතුරු ලබා ගන්න"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"සැඟවුණු ගොනු පෙන්වන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 8b5e87fbb..db6a892e8 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Presunúť do…"</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimovať"</string>
<string name="menu_extract" msgid="8171946945982532262">"Rozbaliť do…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrahovať všetko…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Premenovať"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Zobraziť informácie"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Zobraziť skryté súbory"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index e2faad032..d14c32517 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Premakni v ..."</string>
<string name="menu_compress" msgid="37539111904724188">"Stisni"</string>
<string name="menu_extract" msgid="8171946945982532262">"Razširi v …"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Razširi vse …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Preimenuj"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Prikaži informacije"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Prikaži skrite datoteke"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 5de2e9d3b..7f2ddfdd7 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Zhvendos te..."</string>
<string name="menu_compress" msgid="37539111904724188">"Ngjish"</string>
<string name="menu_extract" msgid="8171946945982532262">"Nxirre te…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Nxirri të gjitha…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Riemërto"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Merr informacione"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Shfaq skedarët e fshehur"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 2dcc91983..343e77050 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Премести у…"</string>
<string name="menu_compress" msgid="37539111904724188">"Компримуј"</string>
<string name="menu_extract" msgid="8171946945982532262">"Издвој у…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Издвоји све…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Преименуј"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Прикажи информације"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Приказуј скривене датотеке"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 281612761..1c6be4153 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Flytta till ..."</string>
<string name="menu_compress" msgid="37539111904724188">"Komprimera"</string>
<string name="menu_extract" msgid="8171946945982532262">"Extrahera till …"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Extrahera alla …"</string>
<string name="menu_rename" msgid="1883113442688817554">"Byt namn"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Hämta information"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Visa dolda filer"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index c09e09ff5..e030a1029 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Hamishia kwenye..."</string>
<string name="menu_compress" msgid="37539111904724188">"Bana"</string>
<string name="menu_extract" msgid="8171946945982532262">"Weka kwenye…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Dondoa zote…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Badilisha jina"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Pata maelezo zaidi"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Onyesha faili zilizofichwa"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 1e8b754d7..b8392fe0b 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"இங்கு நகர்த்து…"</string>
<string name="menu_compress" msgid="37539111904724188">"அளவைக் குறை"</string>
<string name="menu_extract" msgid="8171946945982532262">"இங்கு பிரி…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"அனைத்தையும் பிரித்தெடு…"</string>
<string name="menu_rename" msgid="1883113442688817554">"பெயர் மாற்று"</string>
<string name="menu_inspect" msgid="7279855349299446224">"தகவலைப் பெறு"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"மறைக்கப்பட்ட ஃபைல்களைக் காட்டு"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index aff5a7337..2c4d3d3d0 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ఇక్కడికి తరలించు..."</string>
<string name="menu_compress" msgid="37539111904724188">"కుదించు"</string>
<string name="menu_extract" msgid="8171946945982532262">"దీనిలోకి సంగ్రహించు…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"అన్నీ ఎక్స్‌ట్రాక్ట్ చేయండి…"</string>
<string name="menu_rename" msgid="1883113442688817554">"పేరు మార్చు"</string>
<string name="menu_inspect" msgid="7279855349299446224">"సమాచారాన్ని పొందండి"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"దాచబడిన ఫైళ్లను చూపించు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b33809db7..7c917a2b0 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"ย้ายไปที่…"</string>
<string name="menu_compress" msgid="37539111904724188">"บีบอัด"</string>
<string name="menu_extract" msgid="8171946945982532262">"แตกข้อมูลไปยัง…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"แตกเอกสารทั้งหมด…"</string>
<string name="menu_rename" msgid="1883113442688817554">"เปลี่ยนชื่อ"</string>
<string name="menu_inspect" msgid="7279855349299446224">"รับข้อมูล"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"แสดงไฟล์ที่ซ่อนไว้"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index dc7719470..f4c397dda 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Ilipat sa…"</string>
<string name="menu_compress" msgid="37539111904724188">"I-compress"</string>
<string name="menu_extract" msgid="8171946945982532262">"I-extract sa…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"I-extract lahat…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Palitan ang pangalan"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Kumuha ng impormasyon"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Ipakita ang hidden files"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index f8bbc893b..ea91bfdee 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Klasöre taşı..."</string>
<string name="menu_compress" msgid="37539111904724188">"Sıkıştır"</string>
<string name="menu_extract" msgid="8171946945982532262">"Şuraya çıkar:"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Tümünü çıkar…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Yeniden adlandır"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Bilgi al"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Gizli dosyaları göster"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 170e4fc11..f36363d34 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Перемістити в…"</string>
<string name="menu_compress" msgid="37539111904724188">"Стиснути"</string>
<string name="menu_extract" msgid="8171946945982532262">"Розпакувати…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Розархівувати все…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Перейменувати"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Переглянути інформацію"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Показувати приховані файли"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index b0ee0558b..6ef8b2787 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"اس میں منتقل کریں…"</string>
<string name="menu_compress" msgid="37539111904724188">"کمپریس کریں"</string>
<string name="menu_extract" msgid="8171946945982532262">"اس میں کھولیں۔۔۔"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"سبھی نکالیں…"</string>
<string name="menu_rename" msgid="1883113442688817554">"نام تبدیل کریں"</string>
<string name="menu_inspect" msgid="7279855349299446224">"معلومات حاصل کریں"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"پوشیدہ فائلز دکھائیں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 02bdfb3a2..703119c70 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Boshqa joyga olish…"</string>
<string name="menu_compress" msgid="37539111904724188">"Arxivlash"</string>
<string name="menu_extract" msgid="8171946945982532262">"Arxivdan chiqarish"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Barchasini ajratish…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Qayta nomlash"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Axborot olish"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Yashirin fayllarni chiqarish"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 7f922d0fc..f063ad569 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Chuyển tới..."</string>
<string name="menu_compress" msgid="37539111904724188">"Nén"</string>
<string name="menu_extract" msgid="8171946945982532262">"Trích xuất sang…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Trích xuất tất cả…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Đổi tên"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Xem thông tin"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Hiện các tệp bị ẩn"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 992c57151..402cb66f8 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"移至…"</string>
<string name="menu_compress" msgid="37539111904724188">"压缩"</string>
<string name="menu_extract" msgid="8171946945982532262">"解压到…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"提取全部…"</string>
<string name="menu_rename" msgid="1883113442688817554">"重命名"</string>
<string name="menu_inspect" msgid="7279855349299446224">"获取信息"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"显示隐藏的文件"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index eec2956d0..d69e372fd 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"移至…"</string>
<string name="menu_compress" msgid="37539111904724188">"壓縮"</string>
<string name="menu_extract" msgid="8171946945982532262">"壓縮至…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"擷取全部…"</string>
<string name="menu_rename" msgid="1883113442688817554">"重新命名"</string>
<string name="menu_inspect" msgid="7279855349299446224">"顯示資訊"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"顯示已隱藏的檔案"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 16d0a7305..28318cc89 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"移至…"</string>
<string name="menu_compress" msgid="37539111904724188">"壓縮"</string>
<string name="menu_extract" msgid="8171946945982532262">"解壓縮到…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"全部解壓縮…"</string>
<string name="menu_rename" msgid="1883113442688817554">"重新命名"</string>
<string name="menu_inspect" msgid="7279855349299446224">"取得資訊"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"顯示隱藏的檔案"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83a654282..1b85d7f00 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -45,6 +45,7 @@
<string name="menu_move" msgid="2310760789561129882">"Hambisa ku…"</string>
<string name="menu_compress" msgid="37539111904724188">"Cindezela"</string>
<string name="menu_extract" msgid="8171946945982532262">"Khiphela ku…"</string>
+ <string name="menu_extract_all" msgid="7335680068521252718">"Khipha konke…"</string>
<string name="menu_rename" msgid="1883113442688817554">"Qamba kabusha"</string>
<string name="menu_inspect" msgid="7279855349299446224">"Thola ulwazi"</string>
<string name="menu_show_hidden_files" msgid="5140676344684492769">"Bonisa amafayela afihliwe"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 89e40ac4c..5f78c93e7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -78,6 +78,8 @@
<string name="menu_compress">Compress</string>
<!-- Menu item title that extracts the selected documents [CHAR LIMIT=28] -->
<string name="menu_extract">Extract to\u2026</string>
+ <!-- Menu item title that extracts all the documents in the current directory [CHAR LIMIT=28] -->
+ <string name="menu_extract_all">Extract all\u2026</string>
<!-- Menu item that renames the selected document [CHAR LIMIT=28] -->
<string name="menu_rename">Rename</string>
<!-- Menu item that displays properties about the selected document [CHAR LIMIT=28] -->
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index da599d47f..8e1a51301 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -20,6 +20,7 @@ import static com.android.documentsui.base.DocumentInfo.getCursorInt;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.flags.Flags.desktopFileHandling;
+import static com.android.documentsui.flags.Flags.useSearchV2;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -38,6 +39,7 @@ import android.util.Log;
import android.util.Pair;
import android.view.DragEvent;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
@@ -63,6 +65,9 @@ import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.files.LauncherActivity;
import com.android.documentsui.files.QuickViewIntentBuilder;
+import com.android.documentsui.loaders.FolderLoader;
+import com.android.documentsui.loaders.QueryOptions;
+import com.android.documentsui.loaders.SearchLoader;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.GetRootDocumentTask;
import com.android.documentsui.roots.LoadFirstRootTask;
@@ -73,10 +78,14 @@ import com.android.documentsui.sorting.SortListFragment;
import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.Snackbars;
+import java.time.Duration;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
@@ -894,16 +903,28 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
private final class LoaderBindings implements LoaderCallbacks<DirectoryResult> {
+ private ExecutorService mExecutorService = null;
+ private static final long MAX_SEARCH_TIME_MS = 3000;
+ private static final int MAX_RESULTS = 500;
+
+ @NonNull
@Override
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
- Context context = mActivity;
-
// If document stack is not initialized, i.e. if the root is null, create "Recents" root
// with the selected user.
if (!mState.stack.isInitialized()) {
mState.stack.changeRoot(mActivity.getCurrentRoot());
}
+ if (useSearchV2()) {
+ return onCreateLoaderV2(id, args);
+ }
+ return onCreateLoaderV1(id, args);
+ }
+
+ private Loader<DirectoryResult> onCreateLoaderV1(int id, Bundle args) {
+ Context context = mActivity;
+
if (mState.stack.isRecents()) {
final LockingContentObserver observer = new LockingContentObserver(
mContentLock, AbstractActionHandler.this::loadDocumentsForCurrentStack);
@@ -980,6 +1001,69 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
}
}
+ private Loader<DirectoryResult> onCreateLoaderV2(int id, Bundle args) {
+ if (mExecutorService == null) {
+ // TODO(b:388130971): Fine tune the size of the thread pool.
+ mExecutorService = Executors.newFixedThreadPool(
+ GlobalSearchLoader.MAX_OUTSTANDING_TASK);
+ }
+ DocumentStack stack = mState.stack;
+ RootInfo root = stack.getRoot();
+ List<UserId> userIdList = DocumentsApplication.getUserIdManager(mActivity).getUserIds();
+
+ Duration lastModifiedDelta = stack.isRecents()
+ ? Duration.ofMillis(RecentsLoader.REJECT_OLDER_THAN)
+ : null;
+ int maxResults = (root == null || root.isRecents())
+ ? RecentsLoader.MAX_DOCS_FROM_ROOT : MAX_RESULTS;
+ QueryOptions options = new QueryOptions(
+ maxResults, lastModifiedDelta, Duration.ofMillis(MAX_SEARCH_TIME_MS),
+ mState.showHiddenFiles, mState.acceptMimes);
+
+ if (stack.isRecents() || mSearchMgr.isSearching()) {
+ Log.d(TAG, "Creating search loader V2");
+ // For search and recent we create an observer that restart the loader every time
+ // one of the searched content providers reports a change.
+ final LockingContentObserver observer = new LockingContentObserver(
+ mContentLock, AbstractActionHandler.this::loadDocumentsForCurrentStack);
+ Collection<RootInfo> rootList = new ArrayList<>();
+ if (root == null || root.isRecents()) {
+ // TODO(b:381346575): Pass roots based on user selection.
+ rootList.addAll(mProviders.getMatchingRootsBlocking(mState).stream().filter(
+ r -> r.supportsSearch() && r.authority != null
+ && r.rootId != null).toList());
+ } else {
+ rootList.add(root);
+ }
+ return new SearchLoader(
+ mActivity,
+ userIdList,
+ mInjector.fileTypeLookup,
+ observer,
+ rootList,
+ mSearchMgr.getCurrentSearch(),
+ options,
+ mState.sortModel,
+ mExecutorService
+ );
+ }
+ Log.d(TAG, "Creating folder loader V2");
+ // For folder scan we pass the content lock to the loader so that it can register
+ // an a callback to its internal method that forces a reload of the folder, every
+ // time the content provider reports a change.
+ return new FolderLoader(
+ mActivity,
+ userIdList,
+ mInjector.fileTypeLookup,
+ mContentLock,
+ root,
+ stack.peek(),
+ options,
+ mState.sortModel
+ );
+
+ }
+
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
if (DEBUG) {
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 2d1e2a59a..126a777da 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -27,11 +27,13 @@ import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
+import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Menus;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.flags.Flags;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.sidebar.RootsFragment;
@@ -91,6 +93,9 @@ public abstract class MenuManager {
return;
}
updateCreateDir(mOptionMenu.findItem(R.id.option_menu_create_dir));
+ if (Flags.zipNg()) {
+ updateExtractAll(mOptionMenu.findItem(R.id.option_menu_extract_all));
+ }
updateSettings(mOptionMenu.findItem(R.id.option_menu_settings));
updateSelectAll(mOptionMenu.findItem(R.id.option_menu_select_all));
updateNewWindow(mOptionMenu.findItem(R.id.option_menu_new_window));
@@ -229,10 +234,7 @@ public abstract class MenuManager {
Menus.setEnabledAndVisible(inspect, selectionDetails.size() == 1);
- final MenuItem compress = menu.findItem(R.id.dir_menu_compress);
- if (compress != null) {
- updateCompress(compress, selectionDetails);
- }
+ updateCompress(menu.findItem(R.id.dir_menu_compress), selectionDetails);
}
/**
@@ -397,6 +399,10 @@ public abstract class MenuManager {
Menus.setEnabledAndVisible(launcher, false);
}
+ protected void updateExtractAll(MenuItem it) {
+ Menus.setEnabledAndVisible(it, false);
+ }
+
protected abstract void updateSelectAll(MenuItem selectAll);
protected abstract void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails);
protected abstract void updateDeselectAll(
@@ -455,6 +461,12 @@ public abstract class MenuManager {
return mActivity.isInRecents();
}
+ /** Is the current directory showing the contents of an archive? */
+ public boolean isInArchive() {
+ final DocumentInfo dir = mActivity.getCurrentDirectory();
+ return dir != null && ArchivesProvider.AUTHORITY.equals(dir.authority);
+ }
+
public boolean canCreateDirectory() {
return mActivity.canCreateDirectory();
}
diff --git a/src/com/android/documentsui/MultiRootDocumentsLoader.java b/src/com/android/documentsui/MultiRootDocumentsLoader.java
index db78daa48..1213a6711 100644
--- a/src/com/android/documentsui/MultiRootDocumentsLoader.java
+++ b/src/com/android/documentsui/MultiRootDocumentsLoader.java
@@ -71,7 +71,7 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
// previously returned cursors for filtering/sorting; this currently races
// with the UI thread.
- private static final int MAX_OUTSTANDING_TASK = 4;
+ public static final int MAX_OUTSTANDING_TASK = 4;
private static final int MAX_OUTSTANDING_TASK_SVELTE = 2;
/**
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index b3cfa0180..9a3e06fba 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -37,13 +37,13 @@ public class RecentsLoader extends MultiRootDocumentsLoader {
private static final String TAG = "RecentsLoader";
/** Ignore documents older than this age. */
- private static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS;
+ public static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS;
- /** MIME types that should always be excluded from recents. */
+ /** MIME types that should always be excluded from the Recents view. */
private static final String[] REJECT_MIMES = new String[]{Document.MIME_TYPE_DIR};
/** Maximum documents from a single root. */
- private static final int MAX_DOCS_FROM_ROOT = 64;
+ public static final int MAX_DOCS_FROM_ROOT = 64;
private final UserId mUserId;
diff --git a/src/com/android/documentsui/base/Menus.java b/src/com/android/documentsui/base/Menus.java
index eba240c83..6ceea3269 100644
--- a/src/com/android/documentsui/base/Menus.java
+++ b/src/com/android/documentsui/base/Menus.java
@@ -19,6 +19,8 @@ package com.android.documentsui.base;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.NonNull;
+
public final class Menus {
private Menus() {}
@@ -41,7 +43,7 @@ public final class Menus {
}
/** Set enabled/disabled state of a menuItem, and updates its visibility. */
- public static void setEnabledAndVisible(MenuItem item, boolean enabled) {
+ public static void setEnabledAndVisible(@NonNull MenuItem item, boolean enabled) {
item.setEnabled(enabled);
item.setVisible(enabled);
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 43fc91e3a..b4b08ce1b 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -968,7 +968,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
return true;
// TODO: Implement extract (to the current directory).
- } else if (id == R.id.action_menu_extract_to) {
+ } else if (id == R.id.action_menu_extract_to || id == R.id.option_menu_extract_all) {
transferDocuments(selection, null, FileOperationService.OPERATION_EXTRACT);
// TODO: Only finish selection mode if compress-to is not canceled.
// Need to plum down into handling the way we do with deleteDocuments.
@@ -1198,7 +1198,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mode);
// This just identifies the type of request...we'll check it
- // when we reveive a response.
+ // when we receive a response.
startActivityForResult(intent, REQUEST_COPY_DESTINATION);
}
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 1da0d8f2b..cd744476e 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -17,6 +17,7 @@
package com.android.documentsui.files;
import static com.android.documentsui.OperationDialogFragment.DIALOG_TYPE_UNKNOWN;
+import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.flags.Flags.useMaterial3;
import android.app.ActivityManager.TaskDescription;
@@ -24,6 +25,7 @@ import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.Menu;
@@ -58,6 +60,7 @@ import com.android.documentsui.clipping.DocumentClipper;
import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.AppsRowManager;
import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.flags.Flags;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.sidebar.RootsFragment;
import com.android.documentsui.ui.DialogController;
@@ -338,12 +341,22 @@ public class FilesActivity extends BaseActivity implements AbstractActionHandler
mInjector.actions.openInNewWindow(mState.stack);
} else if (id == R.id.option_menu_settings) {
mInjector.actions.openSettings(getCurrentRoot());
+ } else if (id == R.id.option_menu_extract_all) {
+ if (!Flags.zipNg()) return false;
+ final DirectoryFragment dir = getDirectoryFragment();
+ if (dir == null) return false;
+ mInjector.actions.selectAllFiles();
+ return dir.onContextItemSelected(item);
} else if (id == R.id.option_menu_select_all) {
mInjector.actions.selectAllFiles();
} else if (id == R.id.option_menu_inspect) {
mInjector.actions.showInspector(getCurrentDirectory());
} else {
- return super.onOptionsItemSelected(item);
+ final boolean ok = super.onOptionsItemSelected(item);
+ if (DEBUG && !ok) {
+ Log.d(TAG, "Unhandled option item " + id);
+ }
+ return ok;
}
return true;
}
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 3e2e00feb..0fb7c4fa1 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -225,6 +225,11 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
}
@Override
+ protected void updateExtractAll(MenuItem it) {
+ Menus.setEnabledAndVisible(it, mDirDetails.isInArchive());
+ }
+
+ @Override
protected void updateSelectAll(MenuItem selectAll) {
Menus.setEnabledAndVisible(selectAll, true);
}
diff --git a/src/com/android/documentsui/loaders/BaseFileLoader.kt b/src/com/android/documentsui/loaders/BaseFileLoader.kt
new file mode 100644
index 000000000..dd76217ac
--- /dev/null
+++ b/src/com/android/documentsui/loaders/BaseFileLoader.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.database.MergeCursor
+import android.net.Uri
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.RemoteException
+import android.provider.DocumentsContract.Document
+import android.util.Log
+import androidx.loader.content.AsyncTaskLoader
+import com.android.documentsui.DirectoryResult
+import com.android.documentsui.base.Lookup
+import com.android.documentsui.base.UserId
+import com.android.documentsui.roots.RootCursorWrapper
+
+const val TAG = "SearchV2"
+
+val FILE_ENTRY_COLUMNS = arrayOf(
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_SUMMARY,
+ Document.COLUMN_SIZE,
+ Document.COLUMN_ICON,
+)
+
+fun emptyCursor(): Cursor {
+ return MatrixCursor(FILE_ENTRY_COLUMNS)
+}
+
+/**
+ * Helper function that returns a single, non-null cursor constructed from the given list of
+ * cursors.
+ */
+fun toSingleCursor(cursorList: List<Cursor>): Cursor {
+ if (cursorList.isEmpty()) {
+ return emptyCursor()
+ }
+ if (cursorList.size == 1) {
+ return cursorList[0]
+ }
+ return MergeCursor(cursorList.toTypedArray())
+}
+
+/**
+ * The base class for search and directory loaders. This class implements common functionality
+ * shared by these loaders. The extending classes should implement loadInBackground, which
+ * should call the queryLocation method.
+ */
+abstract class BaseFileLoader(
+ context: Context,
+ private val mUserIdList: List<UserId>,
+ protected val mMimeTypeLookup: Lookup<String, String>,
+) : AsyncTaskLoader<DirectoryResult>(context) {
+
+ private var mSignal: CancellationSignal? = null
+ private var mResult: DirectoryResult? = null
+
+ override fun cancelLoadInBackground() {
+ Log.d(TAG, "BasedFileLoader.cancelLoadInBackground")
+ super.cancelLoadInBackground()
+
+ synchronized(this) {
+ mSignal?.cancel()
+ }
+ }
+
+ override fun deliverResult(result: DirectoryResult?) {
+ Log.d(TAG, "BasedFileLoader.deliverResult")
+ if (isReset) {
+ closeResult(result)
+ return
+ }
+ val oldResult: DirectoryResult? = mResult
+ mResult = result
+
+ if (isStarted) {
+ super.deliverResult(result)
+ }
+
+ if (oldResult != null && oldResult !== result) {
+ closeResult(oldResult)
+ }
+ }
+
+ override fun onStartLoading() {
+ Log.d(TAG, "BasedFileLoader.onStartLoading")
+ val isCursorStale: Boolean = checkIfCursorStale(mResult)
+ if (mResult != null && !isCursorStale) {
+ deliverResult(mResult)
+ }
+ if (takeContentChanged() || mResult == null || isCursorStale) {
+ forceLoad()
+ }
+ }
+
+ override fun onStopLoading() {
+ Log.d(TAG, "BasedFileLoader.onStopLoading")
+ cancelLoad()
+ }
+
+ override fun onCanceled(result: DirectoryResult?) {
+ Log.d(TAG, "BasedFileLoader.onCanceled")
+ closeResult(result)
+ }
+
+ override fun onReset() {
+ Log.d(TAG, "BasedFileLoader.onReset")
+ super.onReset()
+
+ // Ensure the loader is stopped
+ onStopLoading()
+
+ closeResult(mResult)
+ mResult = null
+ }
+
+ /**
+ * Quietly closes the result cursor, if results are still available.
+ */
+ fun closeResult(result: DirectoryResult?) {
+ try {
+ result?.close()
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to close result", e)
+ }
+ }
+
+ private fun checkIfCursorStale(result: DirectoryResult?): Boolean {
+ if (result == null) {
+ return true
+ }
+ val cursor = result.cursor ?: return true
+ if (cursor.isClosed) {
+ return true
+ }
+ Log.d(TAG, "Long check of cursor staleness")
+ val count = cursor.count
+ if (!cursor.moveToPosition(-1)) {
+ return true
+ }
+ for (i in 1..count) {
+ if (!cursor.moveToNext()) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * A function that, for the specified location rooted in the root with the given rootId
+ * attempts to obtain a non-null cursor from the content provider client obtained for the
+ * given locationUri. It returns the first non-null cursor, if one can be found, or null,
+ * if it fails to query the given location for all known users.
+ */
+ fun queryLocation(
+ rootId: String,
+ locationUri: Uri,
+ queryArgs: Bundle?,
+ maxResults: Int,
+ ): Cursor? {
+ val authority = locationUri.authority ?: return null
+ for (userId in mUserIdList) {
+ Log.d(TAG, "BaseFileLoader.queryLocation for $userId at $locationUri")
+ val resolver = userId.getContentResolver(context)
+ try {
+ resolver.acquireUnstableContentProviderClient(
+ authority
+ ).use { client ->
+ if (client == null) {
+ return null
+ }
+ try {
+ val cursor =
+ client.query(locationUri, null, queryArgs, mSignal) ?: return null
+ return RootCursorWrapper(userId, authority, rootId, cursor, maxResults)
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Failed to get cursor for $locationUri", e)
+ }
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to get a content provider client for $locationUri", e)
+ }
+ }
+
+ return null
+ }
+}
diff --git a/src/com/android/documentsui/loaders/FolderLoader.kt b/src/com/android/documentsui/loaders/FolderLoader.kt
new file mode 100644
index 000000000..2bfcd895d
--- /dev/null
+++ b/src/com/android/documentsui/loaders/FolderLoader.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import android.content.Context
+import android.provider.DocumentsContract
+import com.android.documentsui.ContentLock
+import com.android.documentsui.DirectoryResult
+import com.android.documentsui.LockingContentObserver
+import com.android.documentsui.base.DocumentInfo
+import com.android.documentsui.base.FilteringCursorWrapper
+import com.android.documentsui.base.Lookup
+import com.android.documentsui.base.RootInfo
+import com.android.documentsui.base.UserId
+import com.android.documentsui.sorting.SortModel
+
+/**
+ * A specialization of the BaseFileLoader that loads the children of a single folder. To list
+ * a directory you need to provide:
+ *
+ * - The current application context
+ * - A content lock for which a locking content observer is built
+ * - A list of user IDs on behalf of which the search is conducted
+ * - The root info of the listed directory
+ * - The document info of the listed directory
+ * - a lookup from file extension to file type
+ * - The model capable of sorting results
+ */
+class FolderLoader(
+ context: Context,
+ userIdList: List<UserId>,
+ mimeTypeLookup: Lookup<String, String>,
+ contentLock: ContentLock,
+ private val mRoot: RootInfo,
+ private val mListedDir: DocumentInfo,
+ private val mOptions: QueryOptions,
+ private val mSortModel: SortModel,
+) : BaseFileLoader(context, userIdList, mimeTypeLookup) {
+
+ // An observer registered on the cursor to force a reload if the cursor reports a change.
+ private val mObserver = LockingContentObserver(contentLock, this::onContentChanged)
+
+ // Creates a directory result object corresponding to the current parameters of the loader.
+ override fun loadInBackground(): DirectoryResult? {
+ val rejectBeforeTimestamp = mOptions.getRejectBeforeTimestamp()
+ val folderChildrenUri = DocumentsContract.buildChildDocumentsUri(
+ mListedDir.authority,
+ mListedDir.documentId
+ )
+ var cursor =
+ queryLocation(mRoot.rootId, folderChildrenUri, null, ALL_RESULTS) ?: emptyCursor()
+ val filteredCursor = FilteringCursorWrapper(cursor)
+ filteredCursor.filterHiddenFiles(mOptions.showHidden)
+ if (rejectBeforeTimestamp > 0L) {
+ filteredCursor.filterLastModified(rejectBeforeTimestamp)
+ }
+ // TODO(b:380945065): Add filtering by category, such as images, audio, video.
+ val sortedCursor = mSortModel.sortCursor(filteredCursor, mMimeTypeLookup)
+ sortedCursor.registerContentObserver(mObserver)
+
+ val result = DirectoryResult()
+ result.doc = mListedDir
+ result.cursor = sortedCursor
+ return result
+ }
+}
diff --git a/src/com/android/documentsui/loaders/QueryOptions.kt b/src/com/android/documentsui/loaders/QueryOptions.kt
new file mode 100644
index 000000000..1e098b288
--- /dev/null
+++ b/src/com/android/documentsui/loaders/QueryOptions.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import java.time.Duration
+
+/**
+ * The constant to be used for the maxResults parameter, if we wish to get all (unlimited) results.
+ */
+const val ALL_RESULTS: Int = -1
+
+/**
+ * Common query options. These are:
+ * - maximum number to return; pass ALL_RESULTS to impose no limits.
+ * - maximum lastModified delta in milliseconds: the delta from now used to reject files that were
+ * not modified in the specified milliseconds; pass null for no limits.
+ * - maximum time the query should return, including empty, results; pass null for no limits.
+ * - whether or not to show hidden files.
+ * - A list of MIME types used to filter returned files.
+ */
+data class QueryOptions(
+ val maxResults: Int,
+ val maxLastModifiedDelta: Duration?,
+ val maxQueryTime: Duration?,
+ val showHidden: Boolean,
+ val acceptableMimeTypes: Array<String>,
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as QueryOptions
+
+ return maxResults == other.maxResults &&
+ maxLastModifiedDelta == other.maxLastModifiedDelta &&
+ maxQueryTime == other.maxQueryTime &&
+ showHidden == other.showHidden &&
+ acceptableMimeTypes.contentEquals(other.acceptableMimeTypes)
+ }
+
+ /**
+ * Helper method that computes the earliest valid last modified timestamp. Converts last
+ * modified duration to milliseconds past now. If the maxLastModifiedDelta is negative
+ * this method returns 0L.
+ */
+ fun getRejectBeforeTimestamp() =
+ if (maxLastModifiedDelta == null) {
+ 0L
+ } else {
+ System.currentTimeMillis() - maxLastModifiedDelta.toMillis()
+ }
+
+ /**
+ * Helper function that indicates if query time is unlimited. Due to internal reliance on
+ * Java's Duration class it assumes anything larger than 60 seconds has unlimited waiting
+ * time.
+ */
+ fun isQueryTimeUnlimited() = maxQueryTime == null
+
+ override fun hashCode(): Int {
+ var result = maxResults
+ result = 31 * result + maxLastModifiedDelta.hashCode()
+ result = 31 * result + maxQueryTime.hashCode()
+ result = 31 * result + showHidden.hashCode()
+ result = 31 * result + acceptableMimeTypes.contentHashCode()
+ return result
+ }
+}
diff --git a/src/com/android/documentsui/loaders/SearchLoader.kt b/src/com/android/documentsui/loaders/SearchLoader.kt
new file mode 100644
index 000000000..b394c009e
--- /dev/null
+++ b/src/com/android/documentsui/loaders/SearchLoader.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+import android.provider.DocumentsContract
+import android.provider.DocumentsContract.Document
+import android.util.Log
+import com.android.documentsui.DirectoryResult
+import com.android.documentsui.LockingContentObserver
+import com.android.documentsui.base.DocumentInfo
+import com.android.documentsui.base.FilteringCursorWrapper
+import com.android.documentsui.base.Lookup
+import com.android.documentsui.base.RootInfo
+import com.android.documentsui.base.UserId
+import com.android.documentsui.sorting.SortModel
+import com.google.common.util.concurrent.AbstractFuture
+import java.io.Closeable
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.TimeUnit
+import kotlin.time.measureTime
+
+/**
+ * A specialization of the BaseFileLoader that searches the set of specified roots. To search
+ * the roots you must provider:
+ *
+ * - The current application context
+ * - A content lock for which a locking content observer is built
+ * - A list of user IDs, on whose behalf we query content provider clients.
+ * - A list of RootInfo objects representing searched roots
+ * - A query used to search for matching files.
+ * - Query options such as maximum number of results, last modified time delta, etc.
+ * - a lookup from file extension to file type
+ * - The model capable of sorting results
+ * - An acceptable mime types
+ */
+class SearchLoader(
+ context: Context,
+ userIdList: List<UserId>,
+ mimeTypeLookup: Lookup<String, String>,
+ private val mObserver: LockingContentObserver,
+ private val mRootList: Collection<RootInfo>,
+ private val mQuery: String?,
+ private val mOptions: QueryOptions,
+ private val mSortModel: SortModel,
+ private val mExecutorService: ExecutorService,
+) : BaseFileLoader(context, userIdList, mimeTypeLookup) {
+
+ /**
+ * Helper class that runs query on a single user for the given parameter. This class implements
+ * an abstract future so that if the task is completed, we can retrieve the cursor via the get
+ * method.
+ */
+ inner class SearchTask(
+ private val mRootId: String,
+ private val mSearchUri: Uri,
+ private val mQueryArgs: Bundle,
+ private val mLatch: CountDownLatch,
+ ) : Closeable, Runnable, AbstractFuture<Cursor>() {
+ private var mCursor: Cursor? = null
+ val cursor: Cursor? get() = mCursor
+ val taskId: String get() = mSearchUri.toString()
+
+ override fun close() {
+ mCursor = null
+ }
+
+ override fun run() {
+ val queryDuration = measureTime {
+ try {
+ mCursor = queryLocation(mRootId, mSearchUri, mQueryArgs, mOptions.maxResults)
+ set(mCursor)
+ } finally {
+ mLatch.countDown()
+ }
+ }
+ Log.d(TAG, "Query on $mSearchUri took $queryDuration")
+ }
+ }
+
+ @Volatile
+ private lateinit var mSearchTaskList: List<SearchTask>
+
+ // Creates a directory result object corresponding to the current parameters of the loader.
+ override fun loadInBackground(): DirectoryResult? {
+ val result = DirectoryResult()
+ // TODO(b:378590632): If root list has one root use it to construct result.doc
+ result.doc = DocumentInfo()
+ result.cursor = emptyCursor()
+
+ val searchedRoots = mRootList
+ val countDownLatch = CountDownLatch(searchedRoots.size)
+ val rejectBeforeTimestamp = mOptions.getRejectBeforeTimestamp()
+
+ // Step 1: Build a list of search tasks.
+ val searchTaskList =
+ createSearchTaskList(rejectBeforeTimestamp, countDownLatch, mRootList)
+ Log.d(TAG, "${searchTaskList.size} tasks have been created")
+
+ // Check if we are cancelled; if not copy the task list.
+ if (isLoadInBackgroundCanceled) {
+ return result
+ }
+ mSearchTaskList = searchTaskList
+
+ // Step 2: Enqueue tasks and wait for them to complete or time out.
+ for (task in mSearchTaskList) {
+ mExecutorService.execute(task)
+ }
+ Log.d(TAG, "${mSearchTaskList.size} tasks have been enqueued")
+
+ // Step 3: Wait for the results.
+ try {
+ if (mOptions.isQueryTimeUnlimited()) {
+ Log.d(TAG, "Waiting for results with no time limit")
+ countDownLatch.await()
+ } else {
+ Log.d(TAG, "Waiting ${mOptions.maxQueryTime!!.toMillis()}ms for results")
+ countDownLatch.await(
+ mOptions.maxQueryTime.toMillis(),
+ TimeUnit.MILLISECONDS
+ )
+ }
+ Log.d(TAG, "Waiting for results is done")
+ } catch (e: InterruptedException) {
+ Log.d(TAG, "Failed to complete all searches within ${mOptions.maxQueryTime}")
+ // TODO(b:388336095): Record a metrics indicating incomplete search.
+ throw RuntimeException(e)
+ }
+
+ // Step 4: Collect cursors from done tasks.
+ val cursorList = mutableListOf<Cursor>()
+ for (task in mSearchTaskList) {
+ Log.d(TAG, "Processing task ${task.taskId}")
+ if (isLoadInBackgroundCanceled) {
+ break
+ }
+ // TODO(b:388336095): Record a metric for each done and not done task.
+ val cursor = task.cursor
+ if (task.isDone && cursor != null) {
+ // TODO(b:388336095): Record a metric for null and not null cursor.
+ Log.d(TAG, "Task ${task.taskId} has ${cursor.count} results")
+ cursorList.add(cursor)
+ }
+ }
+ Log.d(TAG, "Search complete with ${cursorList.size} cursors collected")
+
+ // Step 5: Assign the cursor, after adding filtering and sorting, to the results.
+ val filteringCursor = FilteringCursorWrapper(toSingleCursor(cursorList))
+ filteringCursor.filterHiddenFiles(mOptions.showHidden)
+ if (rejectBeforeTimestamp > 0L) {
+ filteringCursor.filterLastModified(rejectBeforeTimestamp)
+ }
+ filteringCursor.filterMimes(mOptions.acceptableMimeTypes, arrayOf(Document.MIME_TYPE_DIR))
+ val sortingCursor = mSortModel.sortCursor(filteringCursor, mMimeTypeLookup)
+ sortingCursor.registerContentObserver(mObserver)
+ result.cursor = sortingCursor
+
+ // TODO(b:388336095): Record the total time it took to complete search.
+ return result
+ }
+
+ private fun createContentProviderQuery(root: RootInfo) =
+ if (mQuery == null || mQuery.isBlank()) {
+ DocumentsContract.buildRecentDocumentsUri(
+ root.authority,
+ root.rootId
+ )
+ } else {
+ // NOTE: We pass empty query, as the name matching query is placed in queryArgs.
+ DocumentsContract.buildSearchDocumentsUri(
+ root.authority,
+ root.rootId,
+ ""
+ )
+ }
+
+ private fun createQueryArgs(rejectBeforeTimestamp: Long): Bundle {
+ val queryArgs = Bundle()
+ mSortModel.addQuerySortArgs(queryArgs)
+ if (rejectBeforeTimestamp > 0L) {
+ queryArgs.putLong(
+ DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
+ rejectBeforeTimestamp
+ )
+ }
+ if (mQuery != null && !mQuery.isBlank()) {
+ queryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME, mQuery)
+ }
+ return queryArgs
+ }
+
+ /**
+ * Helper function that creates a list of search tasks for the given countdown latch.
+ */
+ private fun createSearchTaskList(
+ rejectBeforeTimestamp: Long,
+ countDownLatch: CountDownLatch,
+ rootList: Collection<RootInfo>
+ ): List<SearchTask> {
+ val searchTaskList = mutableListOf<SearchTask>()
+ for (root in rootList) {
+ if (isLoadInBackgroundCanceled) {
+ break
+ }
+ val rootSearchUri = createContentProviderQuery(root)
+ // TODO(b:385789236): Correctly pass sort order information.
+ val queryArgs = createQueryArgs(rejectBeforeTimestamp)
+ mSortModel.addQuerySortArgs(queryArgs)
+ Log.d(TAG, "Query $rootSearchUri and queryArgs $queryArgs")
+ val task = SearchTask(
+ root.rootId,
+ rootSearchUri,
+ queryArgs,
+ countDownLatch
+ )
+ searchTaskList.add(task)
+ }
+ return searchTaskList
+ }
+
+ override fun onReset() {
+ for (task in mSearchTaskList) {
+ task.close()
+ }
+ Log.d(TAG, "Resetting search loader; search task list emptied.")
+ super.onReset()
+ }
+}
diff --git a/src/com/android/documentsui/sorting/HeaderCell.java b/src/com/android/documentsui/sorting/HeaderCell.java
index 43e254e39..b0e79e93c 100644
--- a/src/com/android/documentsui/sorting/HeaderCell.java
+++ b/src/com/android/documentsui/sorting/HeaderCell.java
@@ -19,8 +19,6 @@ package com.android.documentsui.sorting;
import android.animation.AnimatorInflater;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
-import androidx.annotation.AnimatorRes;
-import androidx.annotation.StringRes;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -29,14 +27,14 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.AnimatorRes;
+import androidx.annotation.StringRes;
+
import com.android.documentsui.R;
-import com.android.documentsui.sorting.SortDimension;
/**
- * A clickable, sortable table header cell layout.
- *
- * It updates its display when it binds to {@link SortDimension} and changes the status of sorting
- * when it's clicked.
+ * A clickable, sortable table header cell layout. It updates its display when it binds to {@link
+ * SortDimension} and changes the status of sorting when it's clicked.
*/
public class HeaderCell extends LinearLayout {
@@ -62,7 +60,7 @@ public class HeaderCell extends LinearLayout {
setVisibility(dimension.getVisibility());
if (dimension.getVisibility() == View.VISIBLE) {
- TextView label = (TextView) findViewById(R.id.label);
+ TextView label = findViewById(R.id.label);
label.setText(dimension.getLabelId());
switch (dimension.getDataType()) {
case SortDimension.DATA_TYPE_NUMBER:
@@ -77,17 +75,21 @@ public class HeaderCell extends LinearLayout {
}
if (mCurDirection != dimension.getSortDirection()) {
- ImageView arrow = (ImageView) findViewById(R.id.sort_arrow);
+ ImageView arrow = findViewById(R.id.sort_arrow);
switch (dimension.getSortDirection()) {
case SortDimension.SORT_DIRECTION_NONE:
arrow.setVisibility(View.GONE);
break;
case SortDimension.SORT_DIRECTION_ASCENDING:
- showArrow(arrow, R.animator.arrow_rotate_up,
+ showArrow(
+ arrow,
+ R.animator.arrow_rotate_up,
R.string.sort_direction_ascending);
break;
case SortDimension.SORT_DIRECTION_DESCENDING:
- showArrow(arrow, R.animator.arrow_rotate_down,
+ showArrow(
+ arrow,
+ R.animator.arrow_rotate_down,
R.string.sort_direction_descending);
break;
default:
diff --git a/src/com/android/documentsui/sorting/TableHeaderController.java b/src/com/android/documentsui/sorting/TableHeaderController.java
index 549478cfc..17fe29c55 100644
--- a/src/com/android/documentsui/sorting/TableHeaderController.java
+++ b/src/com/android/documentsui/sorting/TableHeaderController.java
@@ -22,43 +22,44 @@ import com.android.documentsui.R;
import javax.annotation.Nullable;
-/**
- * View controller for table header that associates header cells in table header and columns.
- */
+/** View controller for table header that associates header cells in table header and columns. */
public final class TableHeaderController implements SortController.WidgetController {
- private View mTableHeader;
-
private final HeaderCell mTitleCell;
private final HeaderCell mSummaryCell;
private final HeaderCell mSizeCell;
private final HeaderCell mFileTypeCell;
private final HeaderCell mDateCell;
-
+ private final SortModel mModel;
// We assign this here porque each method reference creates a new object
// instance (which is wasteful).
private final View.OnClickListener mOnCellClickListener = this::onCellClicked;
private final SortModel.UpdateListener mModelListener = this::onModelUpdate;
-
- private final SortModel mModel;
+ private final View mTableHeader;
private TableHeaderController(SortModel sortModel, View tableHeader) {
- assert(sortModel != null);
- assert(tableHeader != null);
+ assert (sortModel != null);
+ assert (tableHeader != null);
mModel = sortModel;
mTableHeader = tableHeader;
- mTitleCell = (HeaderCell) tableHeader.findViewById(android.R.id.title);
- mSummaryCell = (HeaderCell) tableHeader.findViewById(android.R.id.summary);
- mSizeCell = (HeaderCell) tableHeader.findViewById(R.id.size);
- mFileTypeCell = (HeaderCell) tableHeader.findViewById(R.id.file_type);
- mDateCell = (HeaderCell) tableHeader.findViewById(R.id.date);
+ mTitleCell = tableHeader.findViewById(android.R.id.title);
+ mSummaryCell = tableHeader.findViewById(android.R.id.summary);
+ mSizeCell = tableHeader.findViewById(R.id.size);
+ mFileTypeCell = tableHeader.findViewById(R.id.file_type);
+ mDateCell = tableHeader.findViewById(R.id.date);
onModelUpdate(mModel, SortModel.UPDATE_TYPE_UNSPECIFIED);
mModel.addListener(mModelListener);
}
+ /** Creates a TableHeaderController. */
+ public static @Nullable TableHeaderController create(
+ SortModel sortModel, @Nullable View tableHeader) {
+ return (tableHeader == null) ? null : new TableHeaderController(sortModel, tableHeader);
+ }
+
private void onModelUpdate(SortModel model, int updateTypeUnspecified) {
bindCell(mTitleCell, SortModel.SORT_DIMENSION_ID_TITLE);
bindCell(mSummaryCell, SortModel.SORT_DIMENSION_ID_SUMMARY);
@@ -78,7 +79,7 @@ public final class TableHeaderController implements SortController.WidgetControl
}
private void bindCell(HeaderCell cell, int id) {
- assert(cell != null);
+ assert (cell != null);
SortDimension dimension = mModel.getDimensionById(id);
cell.setTag(dimension);
@@ -97,9 +98,4 @@ public final class TableHeaderController implements SortController.WidgetControl
mModel.sortByUser(dimension.getId(), dimension.getNextDirection());
}
-
- public static @Nullable TableHeaderController create(
- SortModel sortModel, @Nullable View tableHeader) {
- return (tableHeader == null) ? null : new TableHeaderController(sortModel, tableHeader);
- }
}
diff --git a/tests/common/com/android/documentsui/bots/SortBot.java b/tests/common/com/android/documentsui/bots/SortBot.java
index 0a77de4ee..a2990d7d9 100644
--- a/tests/common/com/android/documentsui/bots/SortBot.java
+++ b/tests/common/com/android/documentsui/bots/SortBot.java
@@ -49,10 +49,11 @@ import com.android.documentsui.sorting.SortModel;
import org.hamcrest.Matcher;
/**
- * A test helper class that provides support for controlling the UI Breadcrumb
- * programmatically, and making assertions against the state of the UI.
- * <p>
- * Support for working directly with Roots and Directory view can be found in the respective bots.
+ * A test helper class that provides support for controlling the UI Breadcrumb programmatically, and
+ * making assertions against the state of the UI.
+ *
+ * <p>Support for working directly with Roots and Directory view can be found in the respective
+ * bots.
*/
public class SortBot extends Bots.BaseBot {
@@ -67,7 +68,7 @@ public class SortBot extends Bots.BaseBot {
}
public void sortBy(int id, @SortDirection int direction) {
- assert(direction != SortDimension.SORT_DIRECTION_NONE);
+ assert (direction != SortDimension.SORT_DIRECTION_NONE);
final @StringRes int labelId = mSortModel.getDimensionById(id).getLabelId();
final String label = mContext.getString(labelId);
@@ -78,16 +79,15 @@ public class SortBot extends Bots.BaseBot {
result = sortByMenu(id, direction);
}
- assertTrue("Sorting by id: " + id + " in direction: " + direction + " failed.",
- result);
+ assertTrue("Sorting by id: " + id + " in direction: " + direction + " failed.", result);
}
public boolean isHeaderShow() {
- return Matchers.present(mColumnBot.MATCHER);
+ return Matchers.present(ColumnSortBot.MATCHER);
}
public void assertHeaderHide() {
- assertFalse(Matchers.present(mColumnBot.MATCHER));
+ assertFalse(Matchers.present(ColumnSortBot.MATCHER));
}
public void assertHeaderShow() {
@@ -98,11 +98,11 @@ public class SortBot extends Bots.BaseBot {
// or with espresso. It's sad that I'm leaving you
// with this little gremlin, but we all have to
// move on and get stuff done :)
- assertTrue(Matchers.present(mColumnBot.MATCHER));
+ assertTrue(Matchers.present(ColumnSortBot.MATCHER));
}
private boolean sortByMenu(int id, @SortDirection int direction) {
- assert(direction != SortDimension.SORT_DIRECTION_NONE);
+ assert (direction != SortDimension.SORT_DIRECTION_NONE);
clickMenuSort();
mDevice.waitForIdle();
@@ -131,9 +131,8 @@ public class SortBot extends Bots.BaseBot {
private static final Matcher<View> MATCHER = withId(R.id.table_header);
private boolean sortBy(String label, @SortDirection int direction) {
- final Matcher<View> cellMatcher = allOf(
- withChild(withText(label)),
- isDescendantOfA(MATCHER));
+ final Matcher<View> cellMatcher =
+ allOf(withChild(withText(label)), isDescendantOfA(MATCHER));
onView(cellMatcher).perform(click());
final @SortDirection int viewDirection = getDirection(cellMatcher);
diff --git a/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java b/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
index 28416775a..fb1a7b6db 100644
--- a/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
+++ b/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
@@ -24,6 +24,7 @@ import com.android.documentsui.MenuManager.DirectoryDetails;
public class TestDirectoryDetails extends DirectoryDetails {
public boolean isInRecents;
+ public boolean isInArchive;
public boolean hasRootSettings;
public boolean hasItemsToPaste;
public boolean canCreateDoc;
@@ -50,6 +51,11 @@ public class TestDirectoryDetails extends DirectoryDetails {
}
@Override
+ public boolean isInArchive() {
+ return isInArchive;
+ }
+
+ @Override
public boolean canCreateDoc() {
return canCreateDoc;
}
diff --git a/tests/common/com/android/documentsui/testing/TestMenu.java b/tests/common/com/android/documentsui/testing/TestMenu.java
index 10e0ea493..9795fd373 100644
--- a/tests/common/com/android/documentsui/testing/TestMenu.java
+++ b/tests/common/com/android/documentsui/testing/TestMenu.java
@@ -77,6 +77,7 @@ public abstract class TestMenu implements Menu {
R.id.option_menu_debug,
R.id.option_menu_new_window,
R.id.option_menu_create_dir,
+ R.id.option_menu_extract_all,
R.id.option_menu_select_all,
R.id.option_menu_settings,
R.id.option_menu_inspect,
@@ -101,6 +102,11 @@ public abstract class TestMenu implements Menu {
if (id == R.id.option_menu_search) {
item.setActionView(Mockito.mock(SearchView.class));
}
+
+ if (id == R.id.option_menu_extract_all) {
+ item.setEnabled(false);
+ item.setVisible(false);
+ }
}
return menu;
}
diff --git a/tests/functional/com/android/documentsui/SortDocumentUiTest.java b/tests/functional/com/android/documentsui/SortDocumentUiTest.java
index 05878bbe7..a6907d680 100644
--- a/tests/functional/com/android/documentsui/SortDocumentUiTest.java
+++ b/tests/functional/com/android/documentsui/SortDocumentUiTest.java
@@ -38,28 +38,34 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
private static final String MIME_2 = "text/html"; // HTML document
private static final String MIME_3 = "image/jpeg"; // JPG image
- private static final String[] FILES = { FILE_1, FILE_3, FILE_2 };
- private static final String[] MIMES = { MIME_1, MIME_3, MIME_2 };
- private static final String[] DIRS = { DIR_1, DIR_2 };
-
- private static final String[] DIRS_IN_NAME_ASC = { DIR_2, DIR_1 };
+ private static final String[] FILES = {FILE_1, FILE_3, FILE_2};
+ private static final String[] FILES_IN_MODIFIED_DESC = reverse(FILES);
+ private static final String[] MIMES = {MIME_1, MIME_3, MIME_2};
+ private static final String[] DIRS = {DIR_1, DIR_2};
+ private static final String[] DIRS_IN_MODIFIED_DESC = reverse(DIRS);
+ private static final String[] DIRS_IN_NAME_ASC = {DIR_2, DIR_1};
private static final String[] DIRS_IN_NAME_DESC = reverse(DIRS_IN_NAME_ASC);
- private static final String[] FILES_IN_NAME_ASC = { FILE_2, FILE_1, FILE_3 };
+ private static final String[] FILES_IN_NAME_ASC = {FILE_2, FILE_1, FILE_3};
private static final String[] FILES_IN_NAME_DESC = reverse(FILES_IN_NAME_ASC);
-
- private static final String[] FILES_IN_SIZE_ASC = { FILE_2, FILE_1, FILE_3 };
+ private static final String[] FILES_IN_SIZE_ASC = {FILE_2, FILE_1, FILE_3};
private static final String[] FILES_IN_SIZE_DESC = reverse(FILES_IN_SIZE_ASC);
-
- private static final String[] DIRS_IN_MODIFIED_DESC = reverse(DIRS);
- private static final String[] FILES_IN_MODIFIED_DESC = reverse(FILES);
-
- private static final String[] FILES_IN_TYPE_ASC = { FILE_2, FILE_3, FILE_1 };
+ private static final String[] FILES_IN_TYPE_ASC = {FILE_2, FILE_3, FILE_1};
private static final String[] FILES_IN_TYPE_DESC = reverse(FILES_IN_TYPE_ASC);
public SortDocumentUiTest() {
super(FilesActivity.class);
}
+ private static String[] reverse(String[] array) {
+ String[] ret = new String[array.length];
+
+ for (int i = 0; i < array.length; ++i) {
+ ret[ret.length - i - 1] = array[i];
+ }
+
+ return ret;
+ }
+
@Override
public void setUp() throws Exception {
super.setUp();
@@ -71,8 +77,9 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
}
/**
- * Initiate test files. It allows waiting between creations of files, so that we can assure
- * the modified date of each document is different.
+ * Initiate test files. It allows waiting between creations of files, so that we can assure the
+ * modified date of each document is different.
+ *
* @param sleep time to sleep in ms
*/
private void initFiles(long sleep) throws Exception {
@@ -110,8 +117,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToListMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_ASCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_ASCENDING);
bots.directory.assertOrder(DIRS_IN_NAME_ASC, FILES_IN_SIZE_ASC);
}
@@ -120,8 +126,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToListMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_DESCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_DESCENDING);
bots.directory.assertOrder(DIRS_IN_NAME_ASC, FILES_IN_SIZE_DESC);
}
@@ -130,8 +135,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToListMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_ASCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_ASCENDING);
bots.directory.assertOrder(DIRS, FILES);
}
@@ -140,8 +144,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToListMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_DESCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_DESCENDING);
bots.directory.assertOrder(DIRS_IN_MODIFIED_DESC, FILES_IN_MODIFIED_DESC);
}
@@ -180,8 +183,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToGridMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_ASCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_ASCENDING);
bots.directory.assertOrder(DIRS_IN_NAME_ASC, FILES_IN_SIZE_ASC);
}
@@ -190,8 +192,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToGridMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_DESCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_SIZE, SortDimension.SORT_DIRECTION_DESCENDING);
bots.directory.assertOrder(DIRS_IN_NAME_ASC, FILES_IN_SIZE_DESC);
}
@@ -200,8 +201,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToGridMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_ASCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_ASCENDING);
bots.directory.assertOrder(DIRS, FILES);
}
@@ -210,8 +210,7 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
bots.main.switchToGridMode();
- bots.sort.sortBy(
- SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_DESCENDING);
+ bots.sort.sortBy(SortModel.SORT_DIMENSION_ID_DATE, SortDimension.SORT_DIRECTION_DESCENDING);
bots.directory.assertOrder(DIRS_IN_MODIFIED_DESC, FILES_IN_MODIFIED_DESC);
}
@@ -234,14 +233,4 @@ public class SortDocumentUiTest extends ActivityTest<FilesActivity> {
SortModel.SORT_DIMENSION_ID_FILE_TYPE, SortDimension.SORT_DIRECTION_DESCENDING);
bots.directory.assertOrder(DIRS_IN_NAME_ASC, FILES_IN_TYPE_DESC);
}
-
- private static String[] reverse(String[] array) {
- String[] ret = new String[array.length];
-
- for (int i = 0; i < array.length; ++i) {
- ret[ret.length - i - 1] = array[i];
- }
-
- return ret;
- }
}
diff --git a/tests/unit/com/android/documentsui/files/MenuManagerTest.java b/tests/unit/com/android/documentsui/files/MenuManagerTest.java
index 6f97de814..f3b7078e8 100644
--- a/tests/unit/com/android/documentsui/files/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/files/MenuManagerTest.java
@@ -111,6 +111,7 @@ public final class MenuManagerTest {
private TestMenuItem optionSort;
private TestMenuItem mOptionLauncher;
private TestMenuItem mOptionShowHiddenFiles;
+ private TestMenuItem mOptionExtractAll;
/* Sub Option Menu items */
private TestMenuItem subOptionGrid;
@@ -185,6 +186,7 @@ public final class MenuManagerTest {
optionSort = testMenu.findItem(R.id.option_menu_sort);
mOptionLauncher = testMenu.findItem(R.id.option_menu_launcher);
mOptionShowHiddenFiles = testMenu.findItem(R.id.option_menu_show_hidden_files);
+ mOptionExtractAll = testMenu.findItem(R.id.option_menu_extract_all);
// Menu actions on root title row.
subOptionGrid = testMenu.findItem(R.id.sub_menu_grid);
@@ -253,6 +255,7 @@ public final class MenuManagerTest {
actionModeSort.assertEnabledAndVisible();
actionModeSelectAll.assertEnabledAndVisible();
mActionModeDeselectAll.assertDisabledAndInvisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
}
@Test
@@ -268,6 +271,7 @@ public final class MenuManagerTest {
actionModeExtractTo.assertDisabledAndInvisible();
actionModeMoveTo.assertDisabledAndInvisible();
actionModeViewInOwner.assertDisabledAndInvisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
}
@Test
diff --git a/tests/unit/com/android/documentsui/loaders/BaseLoaderTest.kt b/tests/unit/com/android/documentsui/loaders/BaseLoaderTest.kt
new file mode 100644
index 000000000..71512e9a1
--- /dev/null
+++ b/tests/unit/com/android/documentsui/loaders/BaseLoaderTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import android.os.Parcel
+import com.android.documentsui.DirectoryResult
+import com.android.documentsui.TestActivity
+import com.android.documentsui.TestConfigStore
+import com.android.documentsui.base.DocumentInfo
+import com.android.documentsui.sorting.SortModel
+import com.android.documentsui.testing.ActivityManagers
+import com.android.documentsui.testing.TestEnv
+import com.android.documentsui.testing.UserManagers
+import java.util.Locale
+import org.junit.Before
+
+/**
+ * Returns the number of matched files, or -1.
+ */
+fun getFileCount(result: DirectoryResult?) = result?.cursor?.count ?: -1
+
+/**
+ * Common base class for search and folder loaders.
+ */
+open class BaseLoaderTest {
+ lateinit var mEnv: TestEnv
+ lateinit var mActivity: TestActivity
+ lateinit var mTestConfigStore: TestConfigStore
+
+ @Before
+ open fun setUp() {
+ mEnv = TestEnv.create()
+ mTestConfigStore = TestConfigStore()
+ mEnv.state.configStore = mTestConfigStore
+ mEnv.state.showHiddenFiles = false
+ val parcel = Parcel.obtain()
+ mEnv.state.sortModel = SortModel.CREATOR.createFromParcel(parcel)
+
+ mActivity = TestActivity.create(mEnv)
+ mActivity.activityManager = ActivityManagers.create(false)
+ mActivity.userManager = UserManagers.create()
+ }
+
+ fun createDocuments(count: Int): Array<DocumentInfo> {
+ val extensionList = arrayOf("txt", "png", "mp4", "mpg")
+ return Array<DocumentInfo>(count) { i ->
+ val id = String.format(Locale.US, "%05d", i)
+ mEnv.model.createFile("sample-$id.${extensionList[i % extensionList.size]}")
+ }
+ }
+}
diff --git a/tests/unit/com/android/documentsui/loaders/FolderLoaderTest.kt b/tests/unit/com/android/documentsui/loaders/FolderLoaderTest.kt
new file mode 100644
index 000000000..92aaaa041
--- /dev/null
+++ b/tests/unit/com/android/documentsui/loaders/FolderLoaderTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import androidx.test.filters.SmallTest
+import com.android.documentsui.ContentLock
+import com.android.documentsui.base.DocumentInfo
+import com.android.documentsui.testing.TestFileTypeLookup
+import com.android.documentsui.testing.TestProvidersAccess
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+
+@SmallTest
+class FolderLoaderTest : BaseLoaderTest() {
+ @Test
+ fun testLoadInBackground() {
+ val mockProvider = mEnv.mockProviders[TestProvidersAccess.DOWNLOADS.authority]
+ val docs = createDocuments(5)
+ mockProvider!!.setNextChildDocumentsReturns(*docs)
+ val userIds = listOf(TestProvidersAccess.DOWNLOADS.userId)
+ val queryOptions = QueryOptions(10, null, null, true, arrayOf<String>("*/*"))
+ val contentLock = ContentLock()
+ // TODO(majewski): Is there a better way to create Downloads root folder DocumentInfo?
+ val rootFolderInfo = DocumentInfo()
+ rootFolderInfo.authority = TestProvidersAccess.DOWNLOADS.authority
+ rootFolderInfo.userId = userIds[0]
+
+ val loader =
+ FolderLoader(
+ mActivity,
+ userIds,
+ TestFileTypeLookup(),
+ contentLock,
+ TestProvidersAccess.DOWNLOADS,
+ rootFolderInfo,
+ queryOptions,
+ mEnv.state.sortModel
+ )
+ val directoryResult = loader.loadInBackground()
+ assertEquals(docs.size, getFileCount(directoryResult))
+ }
+}
diff --git a/tests/unit/com/android/documentsui/loaders/SearchLoaderTest.kt b/tests/unit/com/android/documentsui/loaders/SearchLoaderTest.kt
new file mode 100644
index 000000000..6d78ffdd9
--- /dev/null
+++ b/tests/unit/com/android/documentsui/loaders/SearchLoaderTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.loaders
+
+import com.android.documentsui.ContentLock
+import com.android.documentsui.LockingContentObserver
+import com.android.documentsui.base.DocumentInfo
+import com.android.documentsui.testing.TestFileTypeLookup
+import com.android.documentsui.testing.TestProvidersAccess
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+class SearchLoaderTest : BaseLoaderTest() {
+ lateinit var mExecutor: ExecutorService
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ mExecutor = Executors.newSingleThreadExecutor()
+ }
+
+ @Test
+ fun testLoadInBackground() {
+ val mockProvider = mEnv.mockProviders[TestProvidersAccess.DOWNLOADS.authority]
+ val docs = createDocuments(8)
+ mockProvider!!.setNextChildDocumentsReturns(*docs)
+ val userIds = listOf(TestProvidersAccess.DOWNLOADS.userId)
+ val queryOptions = QueryOptions(10, null, null, true, arrayOf("*/*"))
+ val contentLock = ContentLock()
+ val rootIds = listOf(TestProvidersAccess.DOWNLOADS)
+ val observer = LockingContentObserver(contentLock) {
+ }
+
+ // TODO(majewski): Is there a better way to create Downloads root folder DocumentInfo?
+ val rootFolderInfo = DocumentInfo()
+ rootFolderInfo.authority = TestProvidersAccess.DOWNLOADS.authority
+ rootFolderInfo.userId = userIds[0]
+
+ val loader =
+ SearchLoader(
+ mActivity,
+ userIds,
+ TestFileTypeLookup(),
+ observer,
+ rootIds,
+ "txt",
+ queryOptions,
+ mEnv.state.sortModel,
+ mExecutor,
+ )
+ val directoryResult = loader.loadInBackground()
+ // Expect only 2 text files to match txt.
+ assertEquals(2, getFileCount(directoryResult))
+ }
+}
diff --git a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
index cf9699a3b..7ebf1e6a3 100644
--- a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
@@ -61,6 +61,7 @@ public final class MenuManagerTest {
private TestMenuItem dirCutToClipboard;
private TestMenuItem dirCopyToClipboard;
private TestMenuItem dirPasteFromClipboard;
+ private TestMenuItem mDirCompress;
private TestMenuItem dirCreateDir;
private TestMenuItem dirSelectAll;
private TestMenuItem mDirDeselectAll;
@@ -102,6 +103,7 @@ public final class MenuManagerTest {
private TestMenuItem optionSort;
private TestMenuItem mOptionLauncher;
private TestMenuItem mOptionShowHiddenFiles;
+ private TestMenuItem mOptionExtractAll;
private TestMenuItem subOptionGrid;
private TestMenuItem subOptionList;
@@ -124,6 +126,7 @@ public final class MenuManagerTest {
dirOpenWith = testMenu.findItem(R.id.dir_menu_open_with);
dirCutToClipboard = testMenu.findItem(R.id.dir_menu_cut_to_clipboard);
dirCopyToClipboard = testMenu.findItem(R.id.dir_menu_copy_to_clipboard);
+ mDirCompress = testMenu.findItem(R.id.dir_menu_compress);
dirPasteFromClipboard = testMenu.findItem(R.id.dir_menu_paste_from_clipboard);
dirCreateDir = testMenu.findItem(R.id.dir_menu_create_dir);
dirSelectAll = testMenu.findItem(R.id.dir_menu_select_all);
@@ -162,6 +165,7 @@ public final class MenuManagerTest {
optionSort = testMenu.findItem(R.id.option_menu_sort);
mOptionLauncher = testMenu.findItem(R.id.option_menu_launcher);
mOptionShowHiddenFiles = testMenu.findItem(R.id.option_menu_show_hidden_files);
+ mOptionExtractAll = testMenu.findItem(R.id.option_menu_extract_all);
// Menu actions on root title row.
subOptionGrid = testMenu.findItem(R.id.sub_menu_grid);
@@ -195,6 +199,7 @@ public final class MenuManagerTest {
mActionModeDeselectAll.assertDisabledAndInvisible();
actionModeViewInOwner.assertDisabledAndInvisible();
actionModeSort.assertEnabledAndVisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
}
@Test
@@ -268,6 +273,7 @@ public final class MenuManagerTest {
optionSort.assertEnabledAndVisible();
mOptionLauncher.assertDisabledAndInvisible();
mOptionShowHiddenFiles.assertEnabledAndVisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
assertTrue(testSearchManager.showMenuCalled());
}
@@ -281,6 +287,7 @@ public final class MenuManagerTest {
optionCreateDir.assertDisabledAndInvisible();
subOptionGrid.assertEnabledAndVisible();
subOptionList.assertDisabledAndInvisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
assertFalse(testSearchManager.showMenuCalled());
}
@@ -300,6 +307,7 @@ public final class MenuManagerTest {
subOptionGrid.assertDisabledAndInvisible();
subOptionList.assertDisabledAndInvisible();
+ mOptionExtractAll.assertDisabledAndInvisible();
}
@@ -402,6 +410,7 @@ public final class MenuManagerTest {
dirOpenWith.assertDisabledAndInvisible();
dirCutToClipboard.assertDisabledAndInvisible();
dirCopyToClipboard.assertEnabledAndVisible();
+ mDirCompress.assertDisabledAndInvisible();
dirRename.assertDisabledAndInvisible();
dirDelete.assertDisabledAndInvisible();
}
@@ -414,6 +423,7 @@ public final class MenuManagerTest {
dirOpenInNewWindow.assertDisabledAndInvisible();
dirCutToClipboard.assertDisabledAndInvisible();
dirCopyToClipboard.assertEnabledAndVisible();
+ mDirCompress.assertDisabledAndInvisible();
// Doesn't matter if directory is selected, we don't want pasteInto for PickerActivity
dirPasteIntoFolder.assertDisabledAndInvisible();
dirRename.assertDisabledAndInvisible();
@@ -429,6 +439,7 @@ public final class MenuManagerTest {
mgr.updateContextMenu(testMenu, selectionDetails);
dirCutToClipboard.assertEnabledAndVisible();
dirCopyToClipboard.assertEnabledAndVisible();
+ mDirCompress.assertDisabledAndInvisible();
dirDelete.assertEnabledAndVisible();
}
@@ -442,6 +453,7 @@ public final class MenuManagerTest {
mgr.updateContextMenu(testMenu, selectionDetails);
dirCutToClipboard.assertDisabledAndInvisible();
dirCopyToClipboard.assertDisabledAndInvisible();
+ mDirCompress.assertDisabledAndInvisible();
dirDelete.assertEnabledAndVisible();
}
@@ -454,6 +466,7 @@ public final class MenuManagerTest {
mgr.updateContextMenu(testMenu, selectionDetails);
dirCutToClipboard.assertDisabledAndInvisible();
dirCopyToClipboard.assertEnabledAndVisible();
+ mDirCompress.assertDisabledAndInvisible();
dirDelete.assertDisabledAndInvisible();
}