summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--aconfig/FeatureFlags.aconfig27
-rw-r--r--java/res/layout/chooser_action_view.xml2
-rw-r--r--java/res/layout/chooser_grid_item_hover.xml72
-rw-r--r--java/res/layout/chooser_grid_preview_image.xml3
-rw-r--r--java/res/layout/chooser_headline_row.xml2
-rw-r--r--java/res/values-af/strings.xml1
-rw-r--r--java/res/values-am/strings.xml1
-rw-r--r--java/res/values-ar/strings.xml1
-rw-r--r--java/res/values-as/strings.xml1
-rw-r--r--java/res/values-az/strings.xml1
-rw-r--r--java/res/values-b+sr+Latn/strings.xml1
-rw-r--r--java/res/values-be/strings.xml1
-rw-r--r--java/res/values-bg/strings.xml3
-rw-r--r--java/res/values-bn/strings.xml3
-rw-r--r--java/res/values-bs/strings.xml1
-rw-r--r--java/res/values-ca/strings.xml1
-rw-r--r--java/res/values-cs/strings.xml1
-rw-r--r--java/res/values-da/strings.xml1
-rw-r--r--java/res/values-de/strings.xml1
-rw-r--r--java/res/values-el/strings.xml1
-rw-r--r--java/res/values-en-rAU/strings.xml1
-rw-r--r--java/res/values-en-rCA/strings.xml1
-rw-r--r--java/res/values-en-rGB/strings.xml1
-rw-r--r--java/res/values-en-rIN/strings.xml1
-rw-r--r--java/res/values-es-rUS/strings.xml1
-rw-r--r--java/res/values-es/strings.xml1
-rw-r--r--java/res/values-et/strings.xml1
-rw-r--r--java/res/values-eu/strings.xml1
-rw-r--r--java/res/values-fa/strings.xml3
-rw-r--r--java/res/values-fi/strings.xml1
-rw-r--r--java/res/values-fr-rCA/strings.xml1
-rw-r--r--java/res/values-fr/strings.xml1
-rw-r--r--java/res/values-gl/strings.xml1
-rw-r--r--java/res/values-gu/strings.xml1
-rw-r--r--java/res/values-hi/strings.xml1
-rw-r--r--java/res/values-hr/strings.xml3
-rw-r--r--java/res/values-hu/strings.xml1
-rw-r--r--java/res/values-hy/strings.xml1
-rw-r--r--java/res/values-in/strings.xml1
-rw-r--r--java/res/values-is/strings.xml1
-rw-r--r--java/res/values-it/strings.xml1
-rw-r--r--java/res/values-iw/strings.xml1
-rw-r--r--java/res/values-ja/strings.xml1
-rw-r--r--java/res/values-ka/strings.xml1
-rw-r--r--java/res/values-kk/strings.xml1
-rw-r--r--java/res/values-km/strings.xml1
-rw-r--r--java/res/values-kn/strings.xml3
-rw-r--r--java/res/values-ko/strings.xml1
-rw-r--r--java/res/values-ky/strings.xml1
-rw-r--r--java/res/values-lo/strings.xml1
-rw-r--r--java/res/values-lt/strings.xml1
-rw-r--r--java/res/values-lv/strings.xml3
-rw-r--r--java/res/values-mk/strings.xml1
-rw-r--r--java/res/values-ml/strings.xml1
-rw-r--r--java/res/values-mn/strings.xml1
-rw-r--r--java/res/values-mr/strings.xml1
-rw-r--r--java/res/values-ms/strings.xml1
-rw-r--r--java/res/values-my/strings.xml1
-rw-r--r--java/res/values-nb/strings.xml1
-rw-r--r--java/res/values-ne/strings.xml1
-rw-r--r--java/res/values-nl/strings.xml1
-rw-r--r--java/res/values-or/strings.xml1
-rw-r--r--java/res/values-pa/strings.xml1
-rw-r--r--java/res/values-pl/strings.xml1
-rw-r--r--java/res/values-pt-rBR/strings.xml3
-rw-r--r--java/res/values-pt-rPT/strings.xml1
-rw-r--r--java/res/values-pt/strings.xml3
-rw-r--r--java/res/values-ro/strings.xml3
-rw-r--r--java/res/values-ru/strings.xml1
-rw-r--r--java/res/values-si/strings.xml1
-rw-r--r--java/res/values-sk/strings.xml1
-rw-r--r--java/res/values-sl/strings.xml1
-rw-r--r--java/res/values-sq/strings.xml1
-rw-r--r--java/res/values-sr/strings.xml1
-rw-r--r--java/res/values-sv/strings.xml1
-rw-r--r--java/res/values-sw/strings.xml5
-rw-r--r--java/res/values-sw600dp/dimens.xml1
-rw-r--r--java/res/values-ta/strings.xml1
-rw-r--r--java/res/values-te/strings.xml1
-rw-r--r--java/res/values-th/strings.xml1
-rw-r--r--java/res/values-tl/strings.xml1
-rw-r--r--java/res/values-tr/strings.xml3
-rw-r--r--java/res/values-uk/strings.xml1
-rw-r--r--java/res/values-ur/strings.xml1
-rw-r--r--java/res/values-uz/strings.xml1
-rw-r--r--java/res/values-vi/strings.xml1
-rw-r--r--java/res/values-zh-rCN/strings.xml1
-rw-r--r--java/res/values-zh-rHK/strings.xml1
-rw-r--r--java/res/values-zh-rTW/strings.xml1
-rw-r--r--java/res/values-zu/strings.xml1
-rw-r--r--java/res/values/attrs.xml8
-rw-r--r--java/res/values/dimens.xml6
-rw-r--r--java/res/values/strings.xml2
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java49
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java39
-rw-r--r--java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java9
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java6
-rw-r--r--java/src/com/android/intentresolver/ShortcutSelectionLogic.java13
-rw-r--r--java/src/com/android/intentresolver/SimpleIconFactory.java15
-rw-r--r--java/src/com/android/intentresolver/TargetPresentationGetter.java62
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java7
-rw-r--r--java/src/com/android/intentresolver/chooser/TargetInfo.java4
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java8
-rw-r--r--java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt26
-rw-r--r--java/src/com/android/intentresolver/domain/ChooserRequestExt.kt70
-rw-r--r--java/src/com/android/intentresolver/icons/BaseLoadIconTask.java17
-rw-r--r--java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt46
-rw-r--r--java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt57
-rw-r--r--java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt41
-rw-r--r--java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java32
-rw-r--r--java/src/com/android/intentresolver/icons/LoadIconTask.java19
-rw-r--r--java/src/com/android/intentresolver/icons/TargetDataLoader.kt10
-rw-r--r--java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt34
-rw-r--r--java/src/com/android/intentresolver/inject/ActivityModelModule.kt27
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt20
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt32
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/IntentExt.kt58
-rw-r--r--java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt141
-rw-r--r--java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt50
-rw-r--r--java/src/com/android/intentresolver/widget/ViewRoleDescriptionAccessibilityDelegate.kt29
-rw-r--r--tests/activity/Android.bp1
-rw-r--r--tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java2
-rw-r--r--tests/integration/Android.bp1
-rw-r--r--tests/unit/Android.bp1
-rw-r--r--tests/unit/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt203
-rw-r--r--tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt26
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt18
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt23
-rw-r--r--tests/unit/src/com/android/intentresolver/icons/CachingTargetDataLoaderTest.kt74
-rw-r--r--tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt8
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt55
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/viewmodel/IntentExtTest.kt174
134 files changed, 1321 insertions, 421 deletions
diff --git a/Android.bp b/Android.bp
index 75e29a8c..c0e09105 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,6 +54,7 @@ android_library {
"dagger2",
"hilt_android",
"IntentResolverFlagsLib",
+ "iconloader",
"jsr330",
"kotlin-stdlib",
"kotlinx_coroutines",
diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig
index 6ac6efb3..e2b2f57b 100644
--- a/aconfig/FeatureFlags.aconfig
+++ b/aconfig/FeatureFlags.aconfig
@@ -117,6 +117,23 @@ flag {
}
flag {
+ name: "rebuild_adapters_on_target_pinning"
+ namespace: "intentresolver"
+ description: "Rebuild and swap adapters when a target gets (un)pinned to avoid flickering."
+ bug: "230703572"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "target_hover_and_keyboard_focus_states"
+ namespace: "intentresolver"
+ description: "Adopt Launcher pointer hover and keyboard novigation focus effects for targets."
+ bug: "295175912"
+}
+
+flag {
name: "preview_image_loader"
namespace: "intentresolver"
description: "Use the unified preview image loader for all preview variations; support variable preview sizes."
@@ -124,6 +141,16 @@ flag {
}
flag {
+ name: "save_shareousel_state"
+ namespace: "intentresolver"
+ description: "Preserve Shareousel state over a system-initiated process death."
+ bug: "362347212"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "shareousel_update_exclude_components_extra"
namespace: "intentresolver"
description: "Allow Shareousel selection change callback to update Intent#EXTRA_EXCLUDE_COMPONENTS"
diff --git a/java/res/layout/chooser_action_view.xml b/java/res/layout/chooser_action_view.xml
index 6177821a..b4859258 100644
--- a/java/res/layout/chooser_action_view.xml
+++ b/java/res/layout/chooser_action_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License
-->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/chooser_action_button_bg"
diff --git a/java/res/layout/chooser_grid_item_hover.xml b/java/res/layout/chooser_grid_item_hover.xml
new file mode 100644
index 00000000..05206065
--- /dev/null
+++ b/java/res/layout/chooser_grid_item_hover.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2006, 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.
+*/
+-->
+<com.android.intentresolver.widget.ChooserTargetItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@androidprv:id/item"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="100dp"
+ android:gravity="top|center_horizontal"
+ android:paddingVertical="1dp"
+ android:paddingHorizontal="4dp"
+ android:focusable="true"
+ android:defaultFocusHighlightEnabled="false"
+ app:focusOutlineWidth="@dimen/chooser_item_focus_outline_width"
+ app:focusOutlineCornerRadius="@dimen/chooser_item_focus_outline_corner_radius"
+ app:focusOutlineColor="?androidprv:attr/materialColorSecondaryFixed"
+ app:focusInnerOutlineColor="?androidprv:attr/materialColorOnSecondaryFixedVariant">
+
+ <ImageView android:id="@android:id/icon"
+ android:layout_width="@dimen/chooser_icon_width_with_padding"
+ android:layout_height="@dimen/chooser_icon_height_with_padding"
+ android:paddingHorizontal="@dimen/chooser_icon_horizontal_padding"
+ android:paddingBottom="@dimen/chooser_icon_vertical_padding"
+ android:scaleType="fitCenter" />
+
+ <!-- NOTE: for id/text1 and id/text2 below set the width to match parent as a workaround for
+ b/269395540 i.e. prevent views bounds change during a transition animation. It does not
+ affect pinned views as we change their layout parameters programmatically (but that's even
+ more narrow possibility and it's not clear if the root cause or the bug would affect it).
+ -->
+ <!-- App name or Direct Share target name, DS set to 2 lines -->
+ <com.android.intentresolver.widget.BadgeTextView
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="@dimen/chooser_grid_target_name_text_size"
+ android:maxLines="1"
+ android:ellipsize="end" />
+
+ <!-- Activity name if set, gone for Direct Share targets -->
+ <TextView android:id="@android:id/text2"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textSize="@dimen/chooser_grid_activity_name_text_size"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lines="1"
+ android:gravity="top|center_horizontal"
+ android:ellipsize="end"/>
+
+</com.android.intentresolver.widget.ChooserTargetItemView>
diff --git a/java/res/layout/chooser_grid_preview_image.xml b/java/res/layout/chooser_grid_preview_image.xml
index 4745e04c..f1aad727 100644
--- a/java/res/layout/chooser_grid_preview_image.xml
+++ b/java/res/layout/chooser_grid_preview_image.xml
@@ -41,7 +41,8 @@
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
app:itemInnerSpacing="3dp"
- app:itemOuterSpacing="@dimen/chooser_edge_margin_normal"/>
+ app:itemOuterSpacing="@dimen/chooser_edge_margin_normal"
+ app:editButtonRoleDescription="@string/role_description_button"/>
<include layout="@layout/chooser_action_row"/>
</LinearLayout>
diff --git a/java/res/layout/chooser_headline_row.xml b/java/res/layout/chooser_headline_row.xml
index 01be653f..4e19249b 100644
--- a/java/res/layout/chooser_headline_row.xml
+++ b/java/res/layout/chooser_headline_row.xml
@@ -60,7 +60,7 @@
app:barrierDirection="start"
app:constraint_referenced_ids="reselection_action,include_text_action" />
- <TextView
+ <Button
android:id="@+id/reselection_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 55d84dfa..f24528a0 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Kiesbare prent"</string>
<string name="selectable_video" msgid="1271768647699300826">"Kiesbare video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Kiesbare item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Knoppie"</string>
</resources>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index a7b5922b..d46f88d1 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ሊመረጥ የሚችል ምስል"</string>
<string name="selectable_video" msgid="1271768647699300826">"ሊመረጥ የሚችል ቪድዮ"</string>
<string name="selectable_item" msgid="7557320816744205280">"ሊመረጥ የሚችል ንጥል"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"አዝራር"</string>
</resources>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 49769c57..278e03f2 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"صورة يمكن اختيارها"</string>
<string name="selectable_video" msgid="1271768647699300826">"فيديو يمكن اختياره"</string>
<string name="selectable_item" msgid="7557320816744205280">"عنصر يمكن اختياره"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"زرّ"</string>
</resources>
diff --git a/java/res/values-as/strings.xml b/java/res/values-as/strings.xml
index 1983e4fe..2177c527 100644
--- a/java/res/values-as/strings.xml
+++ b/java/res/values-as/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"বাছনি কৰিব পৰা প্ৰতিচ্ছবি"</string>
<string name="selectable_video" msgid="1271768647699300826">"বাছনি কৰিব পৰা ভিডিঅ’"</string>
<string name="selectable_item" msgid="7557320816744205280">"বাছনি কৰিব পৰা বস্তু"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"বুটাম"</string>
</resources>
diff --git a/java/res/values-az/strings.xml b/java/res/values-az/strings.xml
index c5674b86..93086938 100644
--- a/java/res/values-az/strings.xml
+++ b/java/res/values-az/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Seçilə bilən şəkil"</string>
<string name="selectable_video" msgid="1271768647699300826">"Seçilə bilən video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Seçilə bilən element"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Düymə"</string>
</resources>
diff --git a/java/res/values-b+sr+Latn/strings.xml b/java/res/values-b+sr+Latn/strings.xml
index 6d9dbd87..86fc1854 100644
--- a/java/res/values-b+sr+Latn/strings.xml
+++ b/java/res/values-b+sr+Latn/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Slika koja može da se izabere"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video koji može da se izabere"</string>
<string name="selectable_item" msgid="7557320816744205280">"Stavka koja može da se izabere"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Dugme"</string>
</resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 2724855b..97ca27d3 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Відарыс, які можна выбраць"</string>
<string name="selectable_video" msgid="1271768647699300826">"Відэа, якое можна выбраць"</string>
<string name="selectable_item" msgid="7557320816744205280">"Элемент, які можна выбраць"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Кнопка"</string>
</resources>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 450712b1..3cec0cdf 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{+ # файл}other{+ # файла}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{+ още # файл}other{+ още # файла}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Текстът се споделя"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Връзката се споделя"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Споделяне на връзката"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Изображението се споделя}other{# изображения се споделят}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Видеоклипът се споделя}other{# видеоклипа се споделят}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{# файл се споделя}other{# файла се споделят}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Избираемо изображение"</string>
<string name="selectable_video" msgid="1271768647699300826">"Избираем видеоклип"</string>
<string name="selectable_item" msgid="7557320816744205280">"Избираем елемент"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Бутон"</string>
</resources>
diff --git a/java/res/values-bn/strings.xml b/java/res/values-bn/strings.xml
index 2d33eb29..ea524006 100644
--- a/java/res/values-bn/strings.xml
+++ b/java/res/values-bn/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{আরও #টি ফাইল}one{আরও #টি ফাইল}other{আরও #টি ফাইল}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{আরও #টি ফাইল}one{আরও #টি ফাইল}other{আরও #টি ফাইল}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"টেক্সট শেয়ার করা হচ্ছে"</string>
- <string name="sharing_link" msgid="2307694372813942916">"শেয়ার করা লিঙ্ক"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"শেয়ার করার জন্য লিঙ্ক"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{ছবি শেয়ার করা হচ্ছে}one{#টি ছবি শেয়ার করা হচ্ছে}other{#টি ছবি শেয়ার করা হচ্ছে}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{ভিডিও শেয়ার করা হচ্ছে}one{#টি ভিডিও শেয়ার করা হচ্ছে}other{#টি ভিডিও শেয়ার করা হচ্ছে}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{#টি ফাইল শেয়ার করা হচ্ছে}one{#টি ফাইল শেয়ার করা হচ্ছে}other{#টি ফাইল শেয়ার করা হচ্ছে}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"বেছে নেওয়া যাবে এমন ছবি"</string>
<string name="selectable_video" msgid="1271768647699300826">"বেছে নেওয়া যাবে এমন ভিডিও"</string>
<string name="selectable_item" msgid="7557320816744205280">"বেছে নেওয়া যাবে এমন আইটেম"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"বোতাম"</string>
</resources>
diff --git a/java/res/values-bs/strings.xml b/java/res/values-bs/strings.xml
index 10335fab..ddf3119b 100644
--- a/java/res/values-bs/strings.xml
+++ b/java/res/values-bs/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Slika koju je moguće odabrati"</string>
<string name="selectable_video" msgid="1271768647699300826">"Videozapis koji je moguće odabrati"</string>
<string name="selectable_item" msgid="7557320816744205280">"Stavka koju je moguće odabrati"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Dugme"</string>
</resources>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 11029365..48d7138f 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imatge seleccionable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo seleccionable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Element seleccionable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botó"</string>
</resources>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 0ce7e140..151e2147 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Vybratelný obrázek"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vybratelné video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Vybratelná položka"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Tlačítko"</string>
</resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 3a3e2062..e9d952fe 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Billede, der kan vælges"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video, der kan vælges"</string>
<string name="selectable_item" msgid="7557320816744205280">"Element, der kan vælges"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Knap"</string>
</resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 3a561101..911dd273 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Auswählbares Bild"</string>
<string name="selectable_video" msgid="1271768647699300826">"Auswählbares Video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Auswählbares Element"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Schaltfläche"</string>
</resources>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 8903eec1..319a3e2c 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Εικόνα με δυνατότητα επιλογής"</string>
<string name="selectable_video" msgid="1271768647699300826">"Βίντεο με δυνατότητα επιλογής"</string>
<string name="selectable_item" msgid="7557320816744205280">"Στοιχείο με δυνατότητα επιλογής"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Κουμπί"</string>
</resources>
diff --git a/java/res/values-en-rAU/strings.xml b/java/res/values-en-rAU/strings.xml
index 53e64659..4d16a6f4 100644
--- a/java/res/values-en-rAU/strings.xml
+++ b/java/res/values-en-rAU/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Selectable image"</string>
<string name="selectable_video" msgid="1271768647699300826">"Selectable video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Selectable item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Button"</string>
</resources>
diff --git a/java/res/values-en-rCA/strings.xml b/java/res/values-en-rCA/strings.xml
index 1c44b945..9f6d20c3 100644
--- a/java/res/values-en-rCA/strings.xml
+++ b/java/res/values-en-rCA/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Selectable image"</string>
<string name="selectable_video" msgid="1271768647699300826">"Selectable video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Selectable item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Button"</string>
</resources>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 53e64659..4d16a6f4 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Selectable image"</string>
<string name="selectable_video" msgid="1271768647699300826">"Selectable video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Selectable item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Button"</string>
</resources>
diff --git a/java/res/values-en-rIN/strings.xml b/java/res/values-en-rIN/strings.xml
index 53e64659..4d16a6f4 100644
--- a/java/res/values-en-rIN/strings.xml
+++ b/java/res/values-en-rIN/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Selectable image"</string>
<string name="selectable_video" msgid="1271768647699300826">"Selectable video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Selectable item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Button"</string>
</resources>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index f3b7fe85..923e9d36 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagen seleccionable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video seleccionable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Elemento seleccionable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botón"</string>
</resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 460de896..f7c3c0b0 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagen seleccionable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo seleccionable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Elemento seleccionable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botón"</string>
</resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 85fca08f..6a17f5b3 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Valitav pilt"</string>
<string name="selectable_video" msgid="1271768647699300826">"Valitav video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Valitav üksus"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Nupp"</string>
</resources>
diff --git a/java/res/values-eu/strings.xml b/java/res/values-eu/strings.xml
index 5020f62d..e80edad4 100644
--- a/java/res/values-eu/strings.xml
+++ b/java/res/values-eu/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Hauta daitekeen irudia"</string>
<string name="selectable_video" msgid="1271768647699300826">"Hauta daitekeen bideoa"</string>
<string name="selectable_item" msgid="7557320816744205280">"Hauta daitekeen elementua"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botoia"</string>
</resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 7b3dc6ea..71386d35 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -74,7 +74,7 @@
<string name="image_preview_a11y_description" msgid="297102643932491797">"ریزعکس پیش‌نمای تصویر"</string>
<string name="video_preview_a11y_description" msgid="683440858811095990">"ریزعکس پیش‌نمای ویدیو"</string>
<string name="file_preview_a11y_description" msgid="7397224827802410602">"ریزعکس پیش‌نمای فایل"</string>
- <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"هیچ فردی که با او هم‌رسانی کنید توصیه نشده است"</string>
+ <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"هیچ فرد توصیه‌شده‌ای برای هم‌رسانی وجود ندارد"</string>
<string name="usb_device_resolve_prompt_warn" msgid="4254493957548169620">"‏مجوز ضبط به این برنامه داده نشده است اما می‌تواند صدا را ازطریق این دستگاه USB ضبط کند."</string>
<string name="resolver_personal_tab" msgid="1381052735324320565">"شخصی"</string>
<string name="resolver_work_tab" msgid="3588325717455216412">"کاری"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"تصویر قابل‌انتخاب"</string>
<string name="selectable_video" msgid="1271768647699300826">"ویدیو قابل‌انتخاب"</string>
<string name="selectable_item" msgid="7557320816744205280">"مورد قابل‌انتخاب"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"دکمه"</string>
</resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 65244293..6938d4fa 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Valittava kuva"</string>
<string name="selectable_video" msgid="1271768647699300826">"Valittava video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Valittava kohde"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Painike"</string>
</resources>
diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml
index b2ae5f5c..7fdda598 100644
--- a/java/res/values-fr-rCA/strings.xml
+++ b/java/res/values-fr-rCA/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Image sélectionnable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vidéo sélectionnable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Élément sélectionnable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Bouton"</string>
</resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 2b96c92f..39d436a7 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Image sélectionnable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vidéo sélectionnable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Élément sélectionnable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Bouton"</string>
</resources>
diff --git a/java/res/values-gl/strings.xml b/java/res/values-gl/strings.xml
index a8caf6f3..d45e982e 100644
--- a/java/res/values-gl/strings.xml
+++ b/java/res/values-gl/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imaxe seleccionable"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo seleccionable"</string>
<string name="selectable_item" msgid="7557320816744205280">"Elemento seleccionable"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botón"</string>
</resources>
diff --git a/java/res/values-gu/strings.xml b/java/res/values-gu/strings.xml
index a70a1b0f..d0e65a18 100644
--- a/java/res/values-gu/strings.xml
+++ b/java/res/values-gu/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"પસંદ કરી શકાય તેવી છબી"</string>
<string name="selectable_video" msgid="1271768647699300826">"પસંદ કરી શકાય તેવો વીડિયો"</string>
<string name="selectable_item" msgid="7557320816744205280">"પસંદ કરી શકાય તેવી આઇટમ"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"બટન"</string>
</resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 3f6db1be..70da0c22 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ऐसी इमेज जिसे चुना जा सकता है"</string>
<string name="selectable_video" msgid="1271768647699300826">"ऐसा वीडियो जिसे चुना जा सकता है"</string>
<string name="selectable_item" msgid="7557320816744205280">"ऐसा आइटम जिसे चुना जा सकता है"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"बटन"</string>
</resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 85858303..c8f8c90d 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{+ # datoteka}one{+ # datoteka}few{+ # datoteke}other{+ # datoteka}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{i još # datoteka}one{i još # datoteka}few{i još # datoteke}other{i još # datoteka}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Dijeli se tekst"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Dijeli se veza"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Veza za dijeljenje"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Dijeli se videozapis}one{Dijeli se # videozapis}few{Dijele se # videozapisa}other{Dijeli se # videozapisa}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{Dijeli se # datoteka}one{Dijeli se # datoteka}few{Dijele se # datoteke}other{Dijeli se # datoteka}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Slika koja se može odabrati"</string>
<string name="selectable_video" msgid="1271768647699300826">"Videozapis koji se može odabrati"</string>
<string name="selectable_item" msgid="7557320816744205280">"Stavka koja se može odabrati"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Gumb"</string>
</resources>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 792b07e2..a9e5e820 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Kijelölhető kép"</string>
<string name="selectable_video" msgid="1271768647699300826">"Kijelölhető videó"</string>
<string name="selectable_item" msgid="7557320816744205280">"Kijelölhető elem"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Gomb"</string>
</resources>
diff --git a/java/res/values-hy/strings.xml b/java/res/values-hy/strings.xml
index f9232a5a..b0b0b235 100644
--- a/java/res/values-hy/strings.xml
+++ b/java/res/values-hy/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Ընտրելու հնարավորությամբ պատկեր"</string>
<string name="selectable_video" msgid="1271768647699300826">"Ընտրելու հնարավորությամբ տեսանյութ"</string>
<string name="selectable_item" msgid="7557320816744205280">"Ընտրելու հնարավորությամբ տարր"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Կոճակ"</string>
</resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index df05fdd0..86828b7c 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Gambar yang dapat dipilih"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video yang dapat dipilih"</string>
<string name="selectable_item" msgid="7557320816744205280">"Item yang dapat dipilih"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Tombol"</string>
</resources>
diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml
index 680ed17a..9125bae9 100644
--- a/java/res/values-is/strings.xml
+++ b/java/res/values-is/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Mynd sem hægt er að velja"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeó sem hægt er að velja"</string>
<string name="selectable_item" msgid="7557320816744205280">"Atriði sem hægt er að velja"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Hnappur"</string>
</resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 3762f58b..7d0a7fa7 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Immagine selezionabile"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video selezionabile"</string>
<string name="selectable_item" msgid="7557320816744205280">"Elemento selezionabile"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Pulsante"</string>
</resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index bed01ff0..3c1be527 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"תמונה שניתן לבחור"</string>
<string name="selectable_video" msgid="1271768647699300826">"סרטון שניתן לבחור"</string>
<string name="selectable_item" msgid="7557320816744205280">"פריט שניתן לבחור"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"לחצן"</string>
</resources>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 1d2a2f06..094106c3 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"選択可能な画像"</string>
<string name="selectable_video" msgid="1271768647699300826">"選択可能な動画"</string>
<string name="selectable_item" msgid="7557320816744205280">"選択可能なアイテム"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ボタン"</string>
</resources>
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
index 4675734b..e0951e39 100644
--- a/java/res/values-ka/strings.xml
+++ b/java/res/values-ka/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"არჩევადი სურათი"</string>
<string name="selectable_video" msgid="1271768647699300826">"არჩევადი ვიდეო"</string>
<string name="selectable_item" msgid="7557320816744205280">"არჩევადი ერთეული"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ღილაკი"</string>
</resources>
diff --git a/java/res/values-kk/strings.xml b/java/res/values-kk/strings.xml
index 362db640..99357ef6 100644
--- a/java/res/values-kk/strings.xml
+++ b/java/res/values-kk/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Таңдауға болатын сурет"</string>
<string name="selectable_video" msgid="1271768647699300826">"Таңдауға болатын бейне"</string>
<string name="selectable_item" msgid="7557320816744205280">"Таңдауға болатын элемент"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Түйме"</string>
</resources>
diff --git a/java/res/values-km/strings.xml b/java/res/values-km/strings.xml
index cee11e26..29d80e96 100644
--- a/java/res/values-km/strings.xml
+++ b/java/res/values-km/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"រូបភាពដែល​អាចជ្រើសរើសបាន"</string>
<string name="selectable_video" msgid="1271768647699300826">"វីដេអូដែល​អាចជ្រើសរើសបាន"</string>
<string name="selectable_item" msgid="7557320816744205280">"ធាតុដែល​អាចជ្រើសរើសបាន"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ប៊ូតុង"</string>
</resources>
diff --git a/java/res/values-kn/strings.xml b/java/res/values-kn/strings.xml
index 35bf148c..d777b6fa 100644
--- a/java/res/values-kn/strings.xml
+++ b/java/res/values-kn/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{+ # ಫೈಲ್‌}one{+ # ಫೈಲ್‌ಗಳು}other{+ # ಫೈಲ್‌ಗಳು}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{+ # ಇನ್ನಷ್ಟು ಫೈಲ್}one{+ # ಇನ್ನಷ್ಟು ಫೈಲ್‌ಗಳು}other{+ # ಇನ್ನಷ್ಟು ಫೈಲ್‌ಗಳು}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"ಪಠ್ಯ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string>
- <string name="sharing_link" msgid="2307694372813942916">"ಲಿಂಕ್ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"ಹಂಚಿಕೊಳ್ಳಬಹುದಾದ ಲಿಂಕ್"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{# ಫೈಲ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ಆಯ್ಕೆಮಾಡಬಹುದಾದ ಚಿತ್ರ"</string>
<string name="selectable_video" msgid="1271768647699300826">"ಆಯ್ಕೆ ಮಾಡಬಹುದಾದ ವೀಡಿಯೊ"</string>
<string name="selectable_item" msgid="7557320816744205280">"ಆಯ್ಕೆ ಮಾಡಬಹುದಾದ ಐಟಂ"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ಬಟನ್"</string>
</resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 094f09b0..0ab0cefb 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"선택 가능한 이미지"</string>
<string name="selectable_video" msgid="1271768647699300826">"선택 가능한 동영상"</string>
<string name="selectable_item" msgid="7557320816744205280">"선택 가능한 항목"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"버튼"</string>
</resources>
diff --git a/java/res/values-ky/strings.xml b/java/res/values-ky/strings.xml
index 610adaf2..7de1593d 100644
--- a/java/res/values-ky/strings.xml
+++ b/java/res/values-ky/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Тандала турган сүрөт"</string>
<string name="selectable_video" msgid="1271768647699300826">"Тандала турган видео"</string>
<string name="selectable_item" msgid="7557320816744205280">"Тандала турган нерсе"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Баскыч"</string>
</resources>
diff --git a/java/res/values-lo/strings.xml b/java/res/values-lo/strings.xml
index 2cdea91f..9481a9ae 100644
--- a/java/res/values-lo/strings.xml
+++ b/java/res/values-lo/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ຮູບທີ່ເລືອກໄດ້"</string>
<string name="selectable_video" msgid="1271768647699300826">"ວິດີໂອທີ່ເລືອກໄດ້"</string>
<string name="selectable_item" msgid="7557320816744205280">"ລາຍການທີ່ເລືອກໄດ້"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ປຸ່ມ"</string>
</resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 7b0c6695..f1a0494d 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Pasirenkamas vaizdas"</string>
<string name="selectable_video" msgid="1271768647699300826">"Pasirenkamas vaizdo įrašas"</string>
<string name="selectable_item" msgid="7557320816744205280">"Pasirenkamas elementas"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Mygtukas"</string>
</resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 1c14c2b8..5fed4d43 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{un vēl # fails}zero{un vēl # faili}one{un vēl # fails}other{un vēl # faili}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{Un vēl # fails}zero{Un vēl # failu}one{Un vēl # fails}other{Un vēl # faili}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Tiek kopīgots teksts"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Tiek kopīgota saite"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Kopīgošanas saite"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Tiek kopīgots attēls}zero{Tiek kopīgoti # attēli}one{Tiek kopīgots # attēls}other{Tiek kopīgoti # attēli}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Tiek kopīgots video}zero{Tiek kopīgoti # video}one{Tiek kopīgots # video}other{Tiek kopīgoti # video}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{Notiek # faila kopīgošana}zero{Notiek # failu kopīgošana}one{Notiek # faila kopīgošana}other{Notiek # failu kopīgošana}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Atlasāms attēls"</string>
<string name="selectable_video" msgid="1271768647699300826">"Atlasāms video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Atlasāms vienums"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Poga"</string>
</resources>
diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml
index 19ff3c67..2ab3c072 100644
--- a/java/res/values-mk/strings.xml
+++ b/java/res/values-mk/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Слика што може да се избере"</string>
<string name="selectable_video" msgid="1271768647699300826">"Видео што може да се избере"</string>
<string name="selectable_item" msgid="7557320816744205280">"Ставка што може да се избере"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Копче"</string>
</resources>
diff --git a/java/res/values-ml/strings.xml b/java/res/values-ml/strings.xml
index bcd07dd7..6318a101 100644
--- a/java/res/values-ml/strings.xml
+++ b/java/res/values-ml/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"തിരഞ്ഞെടുക്കാവുന്ന ചിത്രം"</string>
<string name="selectable_video" msgid="1271768647699300826">"തിരഞ്ഞെടുക്കാവുന്ന വീഡിയോ"</string>
<string name="selectable_item" msgid="7557320816744205280">"തിരഞ്ഞെടുക്കാവുന്ന ഇനം"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ബട്ടൺ"</string>
</resources>
diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml
index 81d97d99..8dc3cd58 100644
--- a/java/res/values-mn/strings.xml
+++ b/java/res/values-mn/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Сонгох боломжтой зураг"</string>
<string name="selectable_video" msgid="1271768647699300826">"Сонгох боломжтой видео"</string>
<string name="selectable_item" msgid="7557320816744205280">"Сонгох боломжтой зүйл"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Товч"</string>
</resources>
diff --git a/java/res/values-mr/strings.xml b/java/res/values-mr/strings.xml
index 4a061601..5e54a61a 100644
--- a/java/res/values-mr/strings.xml
+++ b/java/res/values-mr/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"निवडण्यायोग्य इमेज"</string>
<string name="selectable_video" msgid="1271768647699300826">"निवडण्यायोग्य व्हिडिओ"</string>
<string name="selectable_item" msgid="7557320816744205280">"निवडण्यायोग्य आयटम"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"बटण"</string>
</resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index a01376c6..b6dca50f 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imej yang boleh dipilih"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video yang boleh dipilih"</string>
<string name="selectable_item" msgid="7557320816744205280">"Item yang boleh dipilih"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Butang"</string>
</resources>
diff --git a/java/res/values-my/strings.xml b/java/res/values-my/strings.xml
index 9eeda078..af596656 100644
--- a/java/res/values-my/strings.xml
+++ b/java/res/values-my/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ရွေးချယ်နိုင်သောပုံ"</string>
<string name="selectable_video" msgid="1271768647699300826">"ရွေးချယ်နိုင်သော ဗီဒီယို"</string>
<string name="selectable_item" msgid="7557320816744205280">"ရွေးချယ်နိုင်သောအရာ"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ခလုတ်"</string>
</resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 7a67bc34..bd31a926 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Bilde som kan velges"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video som kan velges"</string>
<string name="selectable_item" msgid="7557320816744205280">"Element som kan velges"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Knapp"</string>
</resources>
diff --git a/java/res/values-ne/strings.xml b/java/res/values-ne/strings.xml
index 76365455..112329c6 100644
--- a/java/res/values-ne/strings.xml
+++ b/java/res/values-ne/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"चयन गर्न मिल्ने फोटो"</string>
<string name="selectable_video" msgid="1271768647699300826">"चयन गर्न मिल्ने भिडियो"</string>
<string name="selectable_item" msgid="7557320816744205280">"चयन गर्न मिल्ने वस्तु"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"बटन"</string>
</resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index e452e98e..54123bef 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Selecteerbare afbeelding"</string>
<string name="selectable_video" msgid="1271768647699300826">"Selecteerbare video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Selecteerbaar item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Knop"</string>
</resources>
diff --git a/java/res/values-or/strings.xml b/java/res/values-or/strings.xml
index 0e2ece56..785acbe1 100644
--- a/java/res/values-or/strings.xml
+++ b/java/res/values-or/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ଚୟନ କରାଯାଇପାରୁଥିବା ଇମେଜ"</string>
<string name="selectable_video" msgid="1271768647699300826">"ଚୟନ କରାଯାଇପାରୁଥିବା ଭିଡିଓ"</string>
<string name="selectable_item" msgid="7557320816744205280">"ଚୟନ କରାଯାଇପାରୁଥିବା ଆଇଟମ"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ବଟନ"</string>
</resources>
diff --git a/java/res/values-pa/strings.xml b/java/res/values-pa/strings.xml
index 607f7d26..8b9f528c 100644
--- a/java/res/values-pa/strings.xml
+++ b/java/res/values-pa/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ਚੁਣਨਯੋਗ ਚਿੱਤਰ"</string>
<string name="selectable_video" msgid="1271768647699300826">"ਚੁਣਨਯੋਗ ਵੀਡੀਓ"</string>
<string name="selectable_item" msgid="7557320816744205280">"ਚੁਣਨਯੋਗ ਆਈਟਮ"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ਬਟਨ"</string>
</resources>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 10dda621..3de2b1f4 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Obraz do wyboru"</string>
<string name="selectable_video" msgid="1271768647699300826">"Film do wyboru"</string>
<string name="selectable_item" msgid="7557320816744205280">"Element do wyboru"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Przycisk"</string>
</resources>
diff --git a/java/res/values-pt-rBR/strings.xml b/java/res/values-pt-rBR/strings.xml
index c8ce55a8..5ed57493 100644
--- a/java/res/values-pt-rBR/strings.xml
+++ b/java/res/values-pt-rBR/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Compartilhando texto"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Compartilhando link"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Compartilhar link"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagem selecionável"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo selecionável"</string>
<string name="selectable_item" msgid="7557320816744205280">"Item selecionável"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botão"</string>
</resources>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index ffcf9a1e..73d12957 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagem selecionável"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo selecionável"</string>
<string name="selectable_item" msgid="7557320816744205280">"Item selecionável"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botão"</string>
</resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index c8ce55a8..5ed57493 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Compartilhando texto"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Compartilhando link"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Compartilhar link"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagem selecionável"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vídeo selecionável"</string>
<string name="selectable_item" msgid="7557320816744205280">"Item selecionável"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Botão"</string>
</resources>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index c2843bab..7c8816b6 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -74,7 +74,7 @@
<string name="image_preview_a11y_description" msgid="297102643932491797">"Miniatură pentru previzualizarea imaginii"</string>
<string name="video_preview_a11y_description" msgid="683440858811095990">"Miniatură pentru previzualizarea videoclipului"</string>
<string name="file_preview_a11y_description" msgid="7397224827802410602">"Miniatură pentru previzualizarea fișierului"</string>
- <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"Nu există persoane recomandate pentru permiterea accesului"</string>
+ <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"Nu există persoane recomandate pentru trimitere"</string>
<string name="usb_device_resolve_prompt_warn" msgid="4254493957548169620">"Permisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
<string name="resolver_personal_tab" msgid="1381052735324320565">"Personal"</string>
<string name="resolver_work_tab" msgid="3588325717455216412">"Serviciu"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imagine care poate fi selectată"</string>
<string name="selectable_video" msgid="1271768647699300826">"Videoclip care poate fi selectat"</string>
<string name="selectable_item" msgid="7557320816744205280">"Articol care poate fi selectat"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Buton"</string>
</resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 9b4c2d20..e0934ad1 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Изображение, которое можно выбрать"</string>
<string name="selectable_video" msgid="1271768647699300826">"Видео, которое можно выбрать"</string>
<string name="selectable_item" msgid="7557320816744205280">"Объект, который можно выбрать"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Кнопка"</string>
</resources>
diff --git a/java/res/values-si/strings.xml b/java/res/values-si/strings.xml
index 1fc87e4d..19af7794 100644
--- a/java/res/values-si/strings.xml
+++ b/java/res/values-si/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"තෝරා ගත හැකි රූපය"</string>
<string name="selectable_video" msgid="1271768647699300826">"තෝරා ගත හැකි වීඩියෝව"</string>
<string name="selectable_item" msgid="7557320816744205280">"තෝරා ගත හැකි අයිතමය"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"බොත්තම"</string>
</resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 9119aaa0..36898690 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Vybrateľný obrázok"</string>
<string name="selectable_video" msgid="1271768647699300826">"Vybrateľné video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Vybrateľná položka"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Tlačidlo"</string>
</resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 78e07ad1..714ba171 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Slika, ki jo je mogoče izbrati."</string>
<string name="selectable_video" msgid="1271768647699300826">"Videoposnetek, ki ga je mogoče izbrati."</string>
<string name="selectable_item" msgid="7557320816744205280">"Element, ki ga je mogoče izbrati."</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Gumb"</string>
</resources>
diff --git a/java/res/values-sq/strings.xml b/java/res/values-sq/strings.xml
index 374b2e0a..dba01219 100644
--- a/java/res/values-sq/strings.xml
+++ b/java/res/values-sq/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Imazh që mund të zgjidhet"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video që mund të zgjidhet"</string>
<string name="selectable_item" msgid="7557320816744205280">"Artikull që mund të zgjidhet"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Buton"</string>
</resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 8e7c57d1..8591ef7d 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Слика која може да се изабере"</string>
<string name="selectable_video" msgid="1271768647699300826">"Видео који може да се изабере"</string>
<string name="selectable_item" msgid="7557320816744205280">"Ставка која може да се изабере"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Дугме"</string>
</resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index d48cc781..6810faa7 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Bild som kan markeras"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video som kan markeras"</string>
<string name="selectable_item" msgid="7557320816744205280">"Objekt som kan markeras"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Knapp"</string>
</resources>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 2f63e887..56496041 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -56,8 +56,8 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{+ faili #}other{+ faili #}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{Faili nyingine #}other{Faili zingine #}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Kutuma maandishi"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Inashiriki kiungo"</string>
- <string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Inashiriki picha}other{Inashiriki picha #}}"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Inatuma kiungo"</string>
+ <string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Kutuma picha}other{Kutuma picha #}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Inashiriki video}other{Inashiriki video #}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{Inashiriki faili #}other{Inashiriki faili #}}"</string>
<string name="select_items_to_share" msgid="1026071777275022579">"Chagua vipengee vya kutuma"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Picha inayoweza kuchaguliwa"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video inayoweza kuchaguliwa"</string>
<string name="selectable_item" msgid="7557320816744205280">"Kipengee kinachoweza kuchaguliwa"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Kitufe"</string>
</resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 240ee067..e152ba06 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -20,4 +20,5 @@
<resources>
<dimen name="chooser_width">624dp</dimen>
<dimen name="modify_share_text_toggle_max_width">250dp</dimen>
+ <dimen name="chooser_item_focus_outline_corner_radius">16dp</dimen>
</resources>
diff --git a/java/res/values-ta/strings.xml b/java/res/values-ta/strings.xml
index f1df5cba..f53e5b29 100644
--- a/java/res/values-ta/strings.xml
+++ b/java/res/values-ta/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"தேர்ந்தெடுக்கக்கூடிய படம்"</string>
<string name="selectable_video" msgid="1271768647699300826">"தேர்ந்தெடுக்கக்கூடிய வீடியோ"</string>
<string name="selectable_item" msgid="7557320816744205280">"தேர்ந்தெடுக்கக்கூடியது"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"பட்டன்"</string>
</resources>
diff --git a/java/res/values-te/strings.xml b/java/res/values-te/strings.xml
index b88d7d4e..5003d8eb 100644
--- a/java/res/values-te/strings.xml
+++ b/java/res/values-te/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"ఎంచుకోదగిన ఇమేజ్"</string>
<string name="selectable_video" msgid="1271768647699300826">"ఎంచుకోదగిన వీడియో"</string>
<string name="selectable_item" msgid="7557320816744205280">"ఎంచుకోదగిన ఐటెమ్"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"బటన్"</string>
</resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 5effd16c..8bb9408f 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"รูปภาพที่เลือกได้"</string>
<string name="selectable_video" msgid="1271768647699300826">"วิดีโอที่เลือกได้"</string>
<string name="selectable_item" msgid="7557320816744205280">"รายการที่เลือกได้"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"ปุ่ม"</string>
</resources>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 67782253..e98c06bf 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Napipiling larawan"</string>
<string name="selectable_video" msgid="1271768647699300826">"Napipiling video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Napipiling item"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Button"</string>
</resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 5dee9296..25b7e860 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -56,7 +56,7 @@
<string name="other_files" msgid="4501185823517473875">"{count,plural, =1{+ # dosya}other{+ # dosya}}"</string>
<string name="more_files" msgid="1043875756612339842">"{count,plural, =1{+ # dosya daha}other{+ # dosya daha}}"</string>
<string name="sharing_text" msgid="8137537443603304062">"Metin paylaşılıyor"</string>
- <string name="sharing_link" msgid="2307694372813942916">"Bağlantı paylaşılıyor"</string>
+ <string name="sharing_link" msgid="2307694372813942916">"Paylaşım bağlantısı"</string>
<string name="sharing_images" msgid="5251443722186962006">"{count,plural, =1{Resim paylaşılıyor}other{# resim paylaşılıyor}}"</string>
<string name="sharing_videos" msgid="3583423190182877434">"{count,plural, =1{Video paylaşılıyor}other{# video paylaşılıyor}}"</string>
<string name="sharing_files" msgid="1275646542246028823">"{count,plural, =1{# dosya paylaşılıyor}other{# dosya paylaşılıyor}}"</string>
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Seçilebilir resim"</string>
<string name="selectable_video" msgid="1271768647699300826">"Seçilebilir video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Seçilebilir öğe"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Düğme"</string>
</resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 293696fd..33f9e350 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Зображення, яке можна вибрати"</string>
<string name="selectable_video" msgid="1271768647699300826">"Відео, яке можна вибрати"</string>
<string name="selectable_item" msgid="7557320816744205280">"Об’єкт, який можна вибрати"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Кнопка"</string>
</resources>
diff --git a/java/res/values-ur/strings.xml b/java/res/values-ur/strings.xml
index 9ecc8443..950041e7 100644
--- a/java/res/values-ur/strings.xml
+++ b/java/res/values-ur/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"قابل انتخاب تصویر"</string>
<string name="selectable_video" msgid="1271768647699300826">"قابل انتخاب ویڈیو"</string>
<string name="selectable_item" msgid="7557320816744205280">"قابل انتخاب آئٹم"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"بٹن"</string>
</resources>
diff --git a/java/res/values-uz/strings.xml b/java/res/values-uz/strings.xml
index f9434b18..1792e0d2 100644
--- a/java/res/values-uz/strings.xml
+++ b/java/res/values-uz/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Tanlanadigan rasm"</string>
<string name="selectable_video" msgid="1271768647699300826">"Tanlanadigan video"</string>
<string name="selectable_item" msgid="7557320816744205280">"Tanlanadigan fayl"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Tugma"</string>
</resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 4c84256e..a32bacc1 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Hình ảnh có thể chọn"</string>
<string name="selectable_video" msgid="1271768647699300826">"Video có thể chọn"</string>
<string name="selectable_item" msgid="7557320816744205280">"Mục có thể chọn"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Nút"</string>
</resources>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index c2fa444f..603a4e5e 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"可选择的图片"</string>
<string name="selectable_video" msgid="1271768647699300826">"可选择的视频"</string>
<string name="selectable_item" msgid="7557320816744205280">"可选择的内容"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"按钮"</string>
</resources>
diff --git a/java/res/values-zh-rHK/strings.xml b/java/res/values-zh-rHK/strings.xml
index 54a61c7e..b3aed885 100644
--- a/java/res/values-zh-rHK/strings.xml
+++ b/java/res/values-zh-rHK/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"可以揀嘅圖片"</string>
<string name="selectable_video" msgid="1271768647699300826">"可以揀嘅影片"</string>
<string name="selectable_item" msgid="7557320816744205280">"可以揀嘅項目"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"按鈕"</string>
</resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 0d369318..97770baf 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"可選取的圖片"</string>
<string name="selectable_video" msgid="1271768647699300826">"可選取的影片"</string>
<string name="selectable_item" msgid="7557320816744205280">"可選取的項目"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"按鈕"</string>
</resources>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 9d6d13dc..bdf42d69 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -106,4 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"Umfanekiso okhethekayo"</string>
<string name="selectable_video" msgid="1271768647699300826">"Ividiyo ekhethekayo"</string>
<string name="selectable_item" msgid="7557320816744205280">"Into ekhethekayo"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"Inkinobho"</string>
</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index c9f2c300..19d85573 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -55,5 +55,13 @@
<attr name="itemInnerSpacing" format="dimension" />
<attr name="itemOuterSpacing" format="dimension" />
<attr name="maxWidthHint" format="dimension" />
+ <attr name="editButtonRoleDescription" format="string" />
+ </declare-styleable>
+
+ <declare-styleable name="ChooserTargetItemView">
+ <attr name="focusOutlineColor" format="color" />
+ <attr name="focusInnerOutlineColor" format="color" />
+ <attr name="focusOutlineWidth" format="dimension" />
+ <attr name="focusOutlineCornerRadius" format="dimension" />
</declare-styleable>
</resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index a1f03276..515343b6 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -34,9 +34,15 @@
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_icon_size">56dp</dimen>
<dimen name="chooser_badge_size">22dp</dimen>
+ <dimen name="chooser_icon_horizontal_padding">8dp</dimen>
+ <dimen name="chooser_icon_vertical_padding">7dp</dimen>
+ <dimen name="chooser_icon_width_with_padding">72dp</dimen> <!-- = chooser_icon_size + chooser_icon_horizontal_padding * 2 -->
+ <dimen name="chooser_icon_height_with_padding">70dp</dimen> <!-- = chooser_icon_size + chooser_icon_vertical_padding * 2 -->
<dimen name="chooser_headline_text_size">18sp</dimen>
<dimen name="chooser_grid_target_name_text_size">12sp</dimen>
<dimen name="chooser_grid_activity_name_text_size">12sp</dimen>
+ <dimen name="chooser_item_focus_outline_corner_radius">11dp</dimen>
+ <dimen name="chooser_item_focus_outline_width">2dp</dimen>
<dimen name="resolver_icon_size">32dp</dimen>
<dimen name="resolver_button_bar_spacing">0dp</dimen>
<dimen name="resolver_badge_size">18dp</dimen>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 4f77d248..2261a4a8 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -338,4 +338,6 @@
<!-- Accessibility content description for an item that the user may select for sharing.
[CHAR LIMIT=NONE] -->
<string name="selectable_item">Selectable item</string>
+ <!-- Accessibility role description for a11y on button. [CHAR LIMIT=NONE] -->
+ <string name="role_description_button">Button</string>
</resources>
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index c8387c4e..54f575d7 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -25,6 +25,7 @@ import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
import static com.android.intentresolver.ChooserActionFactory.EDIT_SOURCE;
import static com.android.intentresolver.Flags.fixShortcutsFlashing;
import static com.android.intentresolver.Flags.keyboardNavigationFix;
+import static com.android.intentresolver.Flags.rebuildAdaptersOnTargetPinning;
import static com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra;
import static com.android.intentresolver.Flags.unselectFinalItem;
import static com.android.intentresolver.ext.CreationExtrasExtKt.replaceDefaultArgs;
@@ -171,7 +172,6 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.inject.Inject;
-import javax.inject.Provider;
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
@@ -257,16 +257,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
@Inject public ChooserHelper mChooserHelper;
@Inject public FeatureFlags mFeatureFlags;
- @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags;
@Inject public EventLog mEventLog;
@Inject @AppPredictionAvailable public boolean mAppPredictionAvailable;
@Inject @ImageEditor public Optional<ComponentName> mImageEditor;
@Inject @NearbyShare public Optional<ComponentName> mNearbyShare;
- protected TargetDataLoader mTargetDataLoader;
- @Inject public Provider<TargetDataLoader> mTargetDataLoaderProvider;
@Inject
@Caching
- public Provider<TargetDataLoader> mCachingTargetDataLoaderProvider;
+ public TargetDataLoader mTargetDataLoader;
@Inject public DevicePolicyResources mDevicePolicyResources;
@Inject public ProfilePagerResources mProfilePagerResources;
@Inject public PackageManager mPackageManager;
@@ -345,20 +342,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
Log.i(TAG, "onCreate");
mActivityModelRepository.initialize(this::createActivityModel);
- mTargetDataLoader = mChooserServiceFeatureFlags.chooserPayloadToggling()
- ? mCachingTargetDataLoaderProvider.get()
- : mTargetDataLoaderProvider.get();
-
setTheme(R.style.Theme_DeviceDefault_Chooser);
// Initializer is invoked when this function returns, via Lifecycle.
mChooserHelper.setInitializer(this::initialize);
- if (mChooserServiceFeatureFlags.chooserPayloadToggling()) {
- mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged);
- mChooserHelper.setOnPendingSelection(this::onPendingSelection);
- if (unselectFinalItem()) {
- mChooserHelper.setOnHasSelections(this::onHasSelections);
- }
+ mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged);
+ mChooserHelper.setOnPendingSelection(this::onPendingSelection);
+ if (unselectFinalItem()) {
+ mChooserHelper.setOnHasSelections(this::onHasSelections);
}
}
private int mInitialProfile = -1;
@@ -659,8 +650,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mEnterTransitionAnimationDelegate,
new HeadlineGeneratorImpl(this),
mRequest.getContentTypeHint(),
- mRequest.getMetadataText(),
- mChooserServiceFeatureFlags.chooserPayloadToggling());
+ mRequest.getMetadataText());
updateStickyContentPreview();
if (shouldShowStickyContentPreview()) {
getEventLog().logActionShareWithPreview(
@@ -777,9 +767,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void recreatePagerAdapter() {
- if (!mChooserServiceFeatureFlags.chooserPayloadToggling()) {
- return;
- }
destroyProfileRecords();
createProfileRecords(
new AppPredictorFactory(
@@ -852,6 +839,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
setTabsViewEnabled(false);
+ if (mSystemWindowInsets != null) {
+ applyFooterView(mSystemWindowInsets.bottom);
+ }
}
private void setTabsViewEnabled(boolean isEnabled) {
@@ -1559,10 +1549,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
// Refresh pinned items
mPinnedSharedPrefs = getPinnedSharedPrefs(this);
- if (listAdapter == null) {
- mChooserMultiProfilePagerAdapter.refreshPackagesInAllTabs();
+ if (rebuildAdaptersOnTargetPinning()) {
+ recreatePagerAdapter();
} else {
- listAdapter.handlePackagesChanged();
+ if (listAdapter == null) {
+ mChooserMultiProfilePagerAdapter.refreshPackagesInAllTabs();
+ } else {
+ listAdapter.handlePackagesChanged();
+ }
}
}
@@ -2454,17 +2448,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
// ResolverListAdapter#mPostListReadyRunnable is executed.
if (chooserListAdapter.getDisplayResolveInfoCount() == 0) {
Log.d(TAG, "getDisplayResolveInfoCount() == 0");
- if (rebuildComplete && mChooserServiceFeatureFlags.chooserPayloadToggling()) {
+ if (rebuildComplete) {
onAppTargetsLoaded(listAdapter);
}
chooserListAdapter.notifyDataSetChanged();
} else {
- if (mChooserServiceFeatureFlags.chooserPayloadToggling()) {
- chooserListAdapter.updateAlphabeticalList(
- () -> onAppTargetsLoaded(listAdapter));
- } else {
- chooserListAdapter.updateAlphabeticalList();
- }
+ chooserListAdapter.updateAlphabeticalList(() -> onAppTargetsLoaded(listAdapter));
}
if (rebuildComplete) {
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 016eb714..563d7d1a 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -18,6 +18,7 @@ package com.android.intentresolver;
import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import static com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates;
import android.app.ActivityManager;
import android.app.prediction.AppTarget;
@@ -59,6 +60,8 @@ import com.android.intentresolver.widget.BadgeTextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -365,7 +368,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
@Override
View onCreateView(ViewGroup parent) {
- return mInflater.inflate(R.layout.chooser_grid_item, parent, false);
+ int layout = targetHoverAndKeyboardFocusStates()
+ ? R.layout.chooser_grid_item_hover
+ : R.layout.chooser_grid_item;
+ return mInflater.inflate(layout, parent, false);
}
@Override
@@ -512,18 +518,13 @@ public class ChooserListAdapter extends ResolverListAdapter {
/**
* Group application targets
*/
- public void updateAlphabeticalList() {
- updateAlphabeticalList(() -> {});
- }
-
- /**
- * Group application targets
- */
public void updateAlphabeticalList(Runnable onCompleted) {
final DisplayResolveInfoAzInfoComparator
comparator = new DisplayResolveInfoAzInfoComparator(mContext);
- final List<DisplayResolveInfo> allTargets = new ArrayList<>();
- allTargets.addAll(getTargetsInCurrentDisplayList());
+ ImmutableList<DisplayResolveInfo> displayList = getTargetsInCurrentDisplayList();
+ final List<DisplayResolveInfo> allTargets =
+ new ArrayList<>(displayList.size() + mCallerTargets.size());
+ allTargets.addAll(displayList);
allTargets.addAll(mCallerTargets);
new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
@@ -543,6 +544,24 @@ public class ChooserListAdapter extends ResolverListAdapter {
// Consolidate multiple targets from same app.
return allTargets
.stream()
+ .map(appTarget -> {
+ if (targetHoverAndKeyboardFocusStates()) {
+ // Icon drawables are effectively cached per target info.
+ // Without cloning target infos, the same target info could be used
+ // for two different positions in the grid: once in the ranked
+ // targets row (from ResolverListAdapter#mDisplayList or
+ // #mCallerTargets, see #getItem()) and again in the all-app-target
+ // grid (copied from #mDisplayList and #mCallerTargets to
+ // #mSortedList).
+ // Using the same drawable for two list items would result in visual
+ // effects being applied to both simultaneously.
+ DisplayResolveInfo copy = appTarget.copy();
+ copy.getDisplayIconHolder().setDisplayIcon(null);
+ return copy;
+ } else {
+ return appTarget;
+ }
+ })
.collect(Collectors.groupingBy(target ->
target.getResolvedComponentName().getPackageName()
+ "#" + target.getDisplayLabel()
diff --git a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
index ae80fad4..8070fc84 100644
--- a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
+++ b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
@@ -33,6 +33,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -136,7 +137,7 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment
final TargetPresentationGetter pg = getProvidingAppPresentationGetter();
title.setText(isShortcutTarget() ? mShortcutTitle : pg.getLabel());
- icon.setImageDrawable(pg.getIcon(mUserHandle));
+ icon.setImageDrawable(new BitmapDrawable(getResources(), pg.getIconBitmap(mUserHandle)));
rv.setAdapter(new VHAdapter(items));
return v;
@@ -280,7 +281,11 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment
final int iconDpi = am.getLauncherLargeIconDensity();
// Use the matching application icon and label for the title, any TargetInfo will do
- return new TargetPresentationGetter.Factory(getContext(), iconDpi)
+ final Context context = getContext();
+ return new TargetPresentationGetter.Factory(
+ () -> SimpleIconFactory.obtain(context),
+ context.getPackageManager(),
+ iconDpi)
.makePresentationGetter(mTargetInfos.get(0).getResolveInfo());
}
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index 2f220cf1..38259281 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -150,6 +150,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
@Inject public IntentForwarding mIntentForwarding;
@Inject public FeatureFlags mFeatureFlags;
@Inject public ActivityModelRepository mActivityModelRepository;
+ @Inject public DefaultTargetDataLoader.Factory mTargetDataLoaderFactory;
private ResolverViewModel mViewModel;
private ResolverRequest mRequest;
@@ -334,10 +335,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
mResolvingHome = mRequest.isResolvingHome();
- mTargetDataLoader = new DefaultTargetDataLoader(
- this,
- getLifecycle(),
- mRequest.isAudioCaptureDevice());
+ mTargetDataLoader = mTargetDataLoaderFactory.create(mRequest.isAudioCaptureDevice());
// The last argument of createResolverListAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
diff --git a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
index 2d5ec451..3a1a51e3 100644
--- a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
+++ b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
@@ -16,6 +16,8 @@
package com.android.intentresolver;
+import static com.android.intentresolver.Flags.rebuildAdaptersOnTargetPinning;
+
import android.app.prediction.AppTarget;
import android.content.Context;
import android.content.Intent;
@@ -171,16 +173,21 @@ public class ShortcutSelectionLogic {
List<TargetInfo> serviceTargets) {
// Check for duplicates and abort if found
- for (TargetInfo otherTargetInfo : serviceTargets) {
+ for (int i = 0; i < serviceTargets.size(); i++) {
+ TargetInfo otherTargetInfo = serviceTargets.get(i);
if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+ if (rebuildAdaptersOnTargetPinning()
+ && chooserTargetInfo.isPinned() != otherTargetInfo.isPinned()) {
+ serviceTargets.set(i, chooserTargetInfo);
+ return true;
+ }
return false;
}
}
int currentSize = serviceTargets.size();
final float newScore = chooserTargetInfo.getModifiedScore();
- for (int i = 0; i < Math.min(currentSize, maxRankedTargets);
- i++) {
+ for (int i = 0; i < Math.min(currentSize, maxRankedTargets); i++) {
final TargetInfo serviceTarget = serviceTargets.get(i);
if (serviceTarget == null) {
serviceTargets.set(i, chooserTargetInfo);
diff --git a/java/src/com/android/intentresolver/SimpleIconFactory.java b/java/src/com/android/intentresolver/SimpleIconFactory.java
index f4871e36..afb7d19e 100644
--- a/java/src/com/android/intentresolver/SimpleIconFactory.java
+++ b/java/src/com/android/intentresolver/SimpleIconFactory.java
@@ -64,7 +64,7 @@ import java.util.Optional;
* possibly badged. It is intended to be used only by Sharesheet for the Q release with custom code.
*/
@Deprecated
-public class SimpleIconFactory {
+public class SimpleIconFactory implements AutoCloseable {
private static final SynchronizedPool<SimpleIconFactory> sPool =
@@ -139,6 +139,11 @@ public class SimpleIconFactory {
"Expected theme to define iconfactoryBadgeSize.");
}
+ @Override
+ public void close() {
+ recycle();
+ }
+
/**
* Recycles the SimpleIconFactory so others may use it.
*
@@ -146,9 +151,11 @@ public class SimpleIconFactory {
*/
@Deprecated
public void recycle() {
- // Return to default background color
- setWrapperBackgroundColor(Color.WHITE);
- sPool.release(this);
+ if (sPoolEnabled) {
+ // Return to default background color
+ setWrapperBackgroundColor(Color.WHITE);
+ sPool.release(this);
+ }
}
/**
diff --git a/java/src/com/android/intentresolver/TargetPresentationGetter.java b/java/src/com/android/intentresolver/TargetPresentationGetter.java
index 910c65c9..3a7f807d 100644
--- a/java/src/com/android/intentresolver/TargetPresentationGetter.java
+++ b/java/src/com/android/intentresolver/TargetPresentationGetter.java
@@ -16,14 +16,12 @@
package com.android.intentresolver;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -31,6 +29,8 @@ import android.util.Log;
import androidx.annotation.Nullable;
+import javax.inject.Provider;
+
/**
* Loads the icon and label for the provided ApplicationInfo. Defaults to using the application icon
* and label over any IntentFilter or Activity icon to increase user understanding, with an
@@ -49,22 +49,29 @@ public abstract class TargetPresentationGetter {
/** Helper to build appropriate type-specific {@link TargetPresentationGetter} instances. */
public static class Factory {
- private final Context mContext;
+ private final Provider<SimpleIconFactory> mIconFactoryProvider;
+ private final PackageManager mPackageManager;
private final int mIconDpi;
- public Factory(Context context, int iconDpi) {
- mContext = context;
+ public Factory(
+ Provider<SimpleIconFactory> iconfactoryProvider,
+ PackageManager packageManager,
+ int iconDpi) {
+ mIconFactoryProvider = iconfactoryProvider;
+ mPackageManager = packageManager;
mIconDpi = iconDpi;
}
/** Make a {@link TargetPresentationGetter} for an {@link ActivityInfo}. */
public TargetPresentationGetter makePresentationGetter(ActivityInfo activityInfo) {
- return new ActivityInfoPresentationGetter(mContext, mIconDpi, activityInfo);
+ return new ActivityInfoPresentationGetter(
+ mIconFactoryProvider, mPackageManager, mIconDpi, activityInfo);
}
/** Make a {@link TargetPresentationGetter} for a {@link ResolveInfo}. */
public TargetPresentationGetter makePresentationGetter(ResolveInfo resolveInfo) {
- return new ResolveInfoPresentationGetter(mContext, mIconDpi, resolveInfo);
+ return new ResolveInfoPresentationGetter(
+ mIconFactoryProvider, mPackageManager, mIconDpi, resolveInfo);
}
}
@@ -77,7 +84,7 @@ public abstract class TargetPresentationGetter {
@Nullable
protected abstract String getAppLabelForSubstitutePermission();
- private Context mContext;
+ private final Provider<SimpleIconFactory> mIconFactoryProvider;
private final int mIconDpi;
private final boolean mHasSubstitutePermission;
private final ApplicationInfo mAppInfo;
@@ -88,14 +95,6 @@ public abstract class TargetPresentationGetter {
* Retrieve the image that should be displayed as the icon when this target is presented to the
* specified {@code userHandle}.
*/
- public Drawable getIcon(UserHandle userHandle) {
- return new BitmapDrawable(mContext.getResources(), getIconBitmap(userHandle));
- }
-
- /**
- * Retrieve the image that should be displayed as the icon when this target is presented to the
- * specified {@code userHandle}.
- */
public Bitmap getIconBitmap(@Nullable UserHandle userHandle) {
Drawable drawable = null;
if (mHasSubstitutePermission) {
@@ -116,9 +115,10 @@ public abstract class TargetPresentationGetter {
drawable = mAppInfo.loadIcon(mPm);
}
- SimpleIconFactory iconFactory = SimpleIconFactory.obtain(mContext);
- Bitmap icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle);
- iconFactory.recycle();
+ Bitmap icon;
+ try (SimpleIconFactory iconFactory = mIconFactoryProvider.get()) {
+ icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle);
+ }
return icon;
}
@@ -168,9 +168,13 @@ public abstract class TargetPresentationGetter {
return res.getDrawableForDensity(resId, mIconDpi);
}
- private TargetPresentationGetter(Context context, int iconDpi, ApplicationInfo appInfo) {
- mContext = context;
- mPm = context.getPackageManager();
+ private TargetPresentationGetter(
+ Provider<SimpleIconFactory> iconfactoryProvider,
+ PackageManager packageManager,
+ int iconDpi,
+ ApplicationInfo appInfo) {
+ mIconFactoryProvider = iconfactoryProvider;
+ mPm = packageManager;
mAppInfo = appInfo;
mIconDpi = iconDpi;
mHasSubstitutePermission = (PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
@@ -183,8 +187,11 @@ public abstract class TargetPresentationGetter {
private final ResolveInfo mResolveInfo;
ResolveInfoPresentationGetter(
- Context context, int iconDpi, ResolveInfo resolveInfo) {
- super(context, iconDpi, resolveInfo.activityInfo);
+ Provider<SimpleIconFactory> iconfactoryProvider,
+ PackageManager packageManager,
+ int iconDpi,
+ ResolveInfo resolveInfo) {
+ super(iconfactoryProvider, packageManager, iconDpi, resolveInfo.activityInfo);
mResolveInfo = resolveInfo;
}
@@ -230,8 +237,11 @@ public abstract class TargetPresentationGetter {
private final ActivityInfo mActivityInfo;
ActivityInfoPresentationGetter(
- Context context, int iconDpi, ActivityInfo activityInfo) {
- super(context, iconDpi, activityInfo.applicationInfo);
+ Provider<SimpleIconFactory> iconfactoryProvider,
+ PackageManager packageManager,
+ int iconDpi,
+ ActivityInfo activityInfo) {
+ super(iconfactoryProvider, packageManager, iconDpi, activityInfo.applicationInfo);
mActivityInfo = activityInfo;
}
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index 5e44c53e..f0674a27 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -239,4 +239,11 @@ public class DisplayResolveInfo implements TargetInfo {
public void setPinned(boolean pinned) {
mPinned = pinned;
}
+
+ /**
+ * Creates a copy of the object.
+ */
+ public DisplayResolveInfo copy() {
+ return new DisplayResolveInfo(this);
+ }
}
diff --git a/java/src/com/android/intentresolver/chooser/TargetInfo.java b/java/src/com/android/intentresolver/chooser/TargetInfo.java
index ba6c3c05..e5f40001 100644
--- a/java/src/com/android/intentresolver/chooser/TargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/TargetInfo.java
@@ -65,7 +65,7 @@ public interface TargetInfo {
* @param icon the icon to return on subsequent calls to {@link #getDisplayIcon()}.
* Implementations may discard this request as a no-op if they don't support setting.
*/
- void setDisplayIcon(Drawable icon);
+ void setDisplayIcon(@Nullable Drawable icon);
}
/** A simple mutable-container implementation of {@link IconHolder}. */
@@ -78,7 +78,7 @@ public interface TargetInfo {
return mDisplayIcon;
}
- public void setDisplayIcon(Drawable icon) {
+ public void setDisplayIcon(@Nullable Drawable icon) {
mDisplayIcon = icon;
}
}
diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
index 1128ec5d..4166e5ae 100644
--- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
@@ -51,7 +51,6 @@ import java.util.function.Supplier;
public final class ChooserContentPreviewUi {
private final CoroutineScope mScope;
- private final boolean mIsPayloadTogglingEnabled;
/**
* Delegate to build the default system action buttons to display in the preview layout, if/when
@@ -109,11 +108,8 @@ public final class ChooserContentPreviewUi {
TransitionElementStatusCallback transitionElementStatusCallback,
HeadlineGenerator headlineGenerator,
ContentTypeHint contentTypeHint,
- @Nullable CharSequence metadata,
- // TODO: replace with the FeatureFlag ref when v1 is gone
- boolean isPayloadTogglingEnabled) {
+ @Nullable CharSequence metadata) {
mScope = scope;
- mIsPayloadTogglingEnabled = isPayloadTogglingEnabled;
mModifyShareActionFactory = modifyShareActionFactory;
mContentPreviewUi = createContentPreview(
previewData,
@@ -169,7 +165,7 @@ public final class ChooserContentPreviewUi {
return fileContentPreviewUi;
}
- if (previewType == CONTENT_PREVIEW_PAYLOAD_SELECTION && mIsPayloadTogglingEnabled) {
+ if (previewType == CONTENT_PREVIEW_PAYLOAD_SELECTION) {
transitionElementStatusCallback.onAllTransitionElementsReady(); // TODO
return new ShareouselContentPreviewUi();
}
diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
index 07cbaa04..d7b9077d 100644
--- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
+++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
@@ -24,7 +24,6 @@ import android.provider.DocumentsContract
import android.provider.DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL
import android.provider.Downloads
import android.provider.OpenableColumns
-import android.service.chooser.Flags.chooserPayloadToggling
import android.text.TextUtils
import android.util.Log
import androidx.annotation.OpenForTesting
@@ -133,7 +132,7 @@ constructor(
* IMAGE, FILE, TEXT. */
if (!targetIntent.isSend || records.isEmpty()) {
CONTENT_PREVIEW_TEXT
- } else if (chooserPayloadToggling() && shouldShowPayloadSelection()) {
+ } else if (shouldShowPayloadSelection()) {
// TODO: replace with the proper flags injection
CONTENT_PREVIEW_PAYLOAD_SELECTION
} else {
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
index 4fe5e8d5..fc193eca 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
@@ -17,14 +17,13 @@
package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor
import android.content.Intent
-import com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.CustomAction
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.PendingIntentSender
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.toCustomActionModel
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate
-import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.onValue
import com.android.intentresolver.data.repository.ChooserRequestRepository
+import com.android.intentresolver.domain.updateWith
import javax.inject.Inject
import kotlinx.coroutines.flow.update
@@ -36,28 +35,7 @@ constructor(
@CustomAction private val pendingIntentSender: PendingIntentSender,
) {
fun applyUpdate(targetIntent: Intent, update: ShareouselUpdate) {
- repository.chooserRequest.update { current ->
- current.copy(
- targetIntent = targetIntent,
- callerChooserTargets =
- update.callerTargets.getOrDefault(current.callerChooserTargets),
- modifyShareAction =
- update.modifyShareAction.getOrDefault(current.modifyShareAction),
- additionalTargets = update.alternateIntents.getOrDefault(current.additionalTargets),
- chosenComponentSender =
- update.resultIntentSender.getOrDefault(current.chosenComponentSender),
- refinementIntentSender =
- update.refinementIntentSender.getOrDefault(current.refinementIntentSender),
- metadataText = update.metadataText.getOrDefault(current.metadataText),
- chooserActions = update.customActions.getOrDefault(current.chooserActions),
- filteredComponentNames =
- if (shareouselUpdateExcludeComponentsExtra()) {
- update.excludeComponents.getOrDefault(current.filteredComponentNames)
- } else {
- current.filteredComponentNames
- }
- )
- }
+ repository.chooserRequest.update { it.updateWith(targetIntent, update) }
update.customActions.onValue { actions ->
repository.customActions.value =
actions.map { it.toCustomActionModel(pendingIntentSender) }
diff --git a/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt b/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt
new file mode 100644
index 00000000..5ca3ad20
--- /dev/null
+++ b/java/src/com/android/intentresolver/domain/ChooserRequestExt.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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
+ *
+ * https://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.intentresolver.domain
+
+import android.content.Intent
+import android.content.Intent.EXTRA_ALTERNATE_INTENTS
+import android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS
+import android.content.Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION
+import android.content.Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
+import android.content.Intent.EXTRA_CHOOSER_RESULT_INTENT_SENDER
+import android.content.Intent.EXTRA_CHOOSER_TARGETS
+import android.content.Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
+import android.content.Intent.EXTRA_EXCLUDE_COMPONENTS
+import android.content.Intent.EXTRA_INTENT
+import android.content.Intent.EXTRA_METADATA_TEXT
+import android.os.Bundle
+import com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault
+import com.android.intentresolver.data.model.ChooserRequest
+
+/** Creates a new ChooserRequest with the target intent and updates from a Shareousel callback */
+fun ChooserRequest.updateWith(targetIntent: Intent, update: ShareouselUpdate): ChooserRequest =
+ copy(
+ targetIntent = targetIntent,
+ callerChooserTargets = update.callerTargets.getOrDefault(callerChooserTargets),
+ modifyShareAction = update.modifyShareAction.getOrDefault(modifyShareAction),
+ additionalTargets = update.alternateIntents.getOrDefault(additionalTargets),
+ chosenComponentSender = update.resultIntentSender.getOrDefault(chosenComponentSender),
+ refinementIntentSender = update.refinementIntentSender.getOrDefault(refinementIntentSender),
+ metadataText = update.metadataText.getOrDefault(metadataText),
+ chooserActions = update.customActions.getOrDefault(chooserActions),
+ filteredComponentNames =
+ if (shareouselUpdateExcludeComponentsExtra()) {
+ update.excludeComponents.getOrDefault(filteredComponentNames)
+ } else {
+ filteredComponentNames
+ },
+ )
+
+/** Save ChooserRequest values that can be updated by the Shareousel into a Bundle */
+fun ChooserRequest.saveUpdates(bundle: Bundle): Bundle {
+ bundle.putParcelable(EXTRA_INTENT, targetIntent)
+ bundle.putParcelableArray(EXTRA_CHOOSER_TARGETS, callerChooserTargets.toTypedArray())
+ bundle.putParcelable(EXTRA_CHOOSER_MODIFY_SHARE_ACTION, modifyShareAction)
+ bundle.putParcelableArray(EXTRA_ALTERNATE_INTENTS, additionalTargets.toTypedArray())
+ bundle.putParcelable(EXTRA_CHOOSER_RESULT_INTENT_SENDER, chosenComponentSender)
+ bundle.putParcelable(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, chosenComponentSender)
+ bundle.putParcelable(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER, refinementIntentSender)
+ bundle.putCharSequence(EXTRA_METADATA_TEXT, metadataText)
+ bundle.putParcelableArray(EXTRA_CHOOSER_CUSTOM_ACTIONS, chooserActions.toTypedArray())
+ if (shareouselUpdateExcludeComponentsExtra()) {
+ bundle.putParcelableArray(EXTRA_EXCLUDE_COMPONENTS, filteredComponentNames.toTypedArray())
+ }
+ return bundle
+}
diff --git a/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java
index 2eceb89c..f09fcfc5 100644
--- a/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java
+++ b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java
@@ -17,34 +17,31 @@
package com.android.intentresolver.icons;
import android.content.Context;
-import android.graphics.drawable.Drawable;
+import android.graphics.Bitmap;
import android.os.AsyncTask;
-import com.android.intentresolver.R;
+import androidx.annotation.Nullable;
+
import com.android.intentresolver.TargetPresentationGetter;
import java.util.function.Consumer;
-abstract class BaseLoadIconTask extends AsyncTask<Void, Void, Drawable> {
+abstract class BaseLoadIconTask extends AsyncTask<Void, Void, Bitmap> {
protected final Context mContext;
protected final TargetPresentationGetter.Factory mPresentationFactory;
- private final Consumer<Drawable> mCallback;
+ private final Consumer<Bitmap> mCallback;
BaseLoadIconTask(
Context context,
TargetPresentationGetter.Factory presentationFactory,
- Consumer<Drawable> callback) {
+ Consumer<Bitmap> callback) {
mContext = context;
mPresentationFactory = presentationFactory;
mCallback = callback;
}
- protected final Drawable loadIconPlaceholder() {
- return mContext.getDrawable(R.drawable.resolver_icon_placeholder);
- }
-
@Override
- protected final void onPostExecute(Drawable d) {
+ protected final void onPostExecute(@Nullable Bitmap d) {
mCallback.accept(d);
}
}
diff --git a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
index 8474b4c3..793b7621 100644
--- a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
@@ -17,9 +17,13 @@
package com.android.intentresolver.icons
import android.content.ComponentName
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.UserHandle
import androidx.collection.LruCache
+import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.chooser.SelectableTargetInfo
import java.util.function.Consumer
@@ -28,23 +32,24 @@ import javax.inject.Qualifier
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.BINARY) annotation class Caching
-private typealias IconCache = LruCache<String, Drawable>
+private typealias IconCache = LruCache<String, Bitmap>
class CachingTargetDataLoader(
+ private val context: Context,
private val targetDataLoader: TargetDataLoader,
private val cacheSize: Int = 100,
-) : TargetDataLoader() {
+) : TargetDataLoader {
@GuardedBy("self") private val perProfileIconCache = HashMap<UserHandle, IconCache>()
override fun getOrLoadAppTargetIcon(
info: DisplayResolveInfo,
userHandle: UserHandle,
- callback: Consumer<Drawable>
+ callback: Consumer<Drawable>,
): Drawable? {
val cacheKey = info.toCacheKey()
- return getCachedAppIcon(cacheKey, userHandle)
+ return getCachedAppIcon(cacheKey, userHandle)?.toDrawable()
?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable ->
- getProfileIconCache(userHandle).put(cacheKey, drawable)
+ drawable.extractBitmap()?.let { getProfileIconCache(userHandle).put(cacheKey, it) }
callback.accept(drawable)
}
}
@@ -52,13 +57,15 @@ class CachingTargetDataLoader(
override fun getOrLoadDirectShareIcon(
info: SelectableTargetInfo,
userHandle: UserHandle,
- callback: Consumer<Drawable>
+ callback: Consumer<Drawable>,
): Drawable? {
val cacheKey = info.toCacheKey()
- return cacheKey?.let { getCachedAppIcon(it, userHandle) }
+ return cacheKey?.let { getCachedAppIcon(it, userHandle) }?.toDrawable()
?: targetDataLoader.getOrLoadDirectShareIcon(info, userHandle) { drawable ->
if (cacheKey != null) {
- getProfileIconCache(userHandle).put(cacheKey, drawable)
+ drawable.extractBitmap()?.let {
+ getProfileIconCache(userHandle).put(cacheKey, it)
+ }
}
callback.accept(drawable)
}
@@ -69,7 +76,7 @@ class CachingTargetDataLoader(
override fun getOrLoadLabel(info: DisplayResolveInfo) = targetDataLoader.getOrLoadLabel(info)
- private fun getCachedAppIcon(component: String, userHandle: UserHandle): Drawable? =
+ private fun getCachedAppIcon(component: String, userHandle: UserHandle): Bitmap? =
getProfileIconCache(userHandle)[component]
private fun getProfileIconCache(userHandle: UserHandle): IconCache =
@@ -78,10 +85,7 @@ class CachingTargetDataLoader(
}
private fun DisplayResolveInfo.toCacheKey() =
- ComponentName(
- resolveInfo.activityInfo.packageName,
- resolveInfo.activityInfo.name,
- )
+ ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
.flattenToString()
private fun SelectableTargetInfo.toCacheKey(): String? =
@@ -95,4 +99,20 @@ class CachingTargetDataLoader(
append(directShareShortcutInfo?.id ?: "")
}
}
+
+ private fun Bitmap.toDrawable(): Drawable {
+ return if (targetHoverAndKeyboardFocusStates()) {
+ HoverBitmapDrawable(this)
+ } else {
+ BitmapDrawable(context.resources, this)
+ }
+ }
+
+ private fun Drawable.extractBitmap(): Bitmap? {
+ return when (this) {
+ is BitmapDrawable -> bitmap
+ is HoverBitmapDrawable -> bitmap
+ else -> null
+ }
+ }
}
diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
index e7392f58..1ff1ddfa 100644
--- a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
@@ -16,8 +16,9 @@
package com.android.intentresolver.icons
-import android.app.ActivityManager
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.AsyncTask
import android.os.UserHandle
@@ -26,27 +27,34 @@ import androidx.annotation.GuardedBy
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates
+import com.android.intentresolver.R
+import com.android.intentresolver.SimpleIconFactory
import com.android.intentresolver.TargetPresentationGetter
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.chooser.SelectableTargetInfo
+import com.android.intentresolver.inject.ActivityOwned
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.qualifiers.ActivityContext
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
+import javax.inject.Provider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
/** An actual [TargetDataLoader] implementation. */
// TODO: replace async tasks with coroutines.
-class DefaultTargetDataLoader(
- private val context: Context,
- private val lifecycle: Lifecycle,
- private val isAudioCaptureDevice: Boolean,
-) : TargetDataLoader() {
- private val presentationFactory =
- TargetPresentationGetter.Factory(
- context,
- context.getSystemService(ActivityManager::class.java)?.launcherLargeIconDensity
- ?: error("Unable to access ActivityManager")
- )
+class DefaultTargetDataLoader
+@AssistedInject
+constructor(
+ @ActivityContext private val context: Context,
+ @ActivityOwned private val lifecycle: Lifecycle,
+ private val iconFactoryProvider: Provider<SimpleIconFactory>,
+ private val presentationFactory: TargetPresentationGetter.Factory,
+ @Assisted private val isAudioCaptureDevice: Boolean,
+) : TargetDataLoader {
private val nextTaskId = AtomicInteger(0)
@GuardedBy("self") private val activeTasks = SparseArray<AsyncTask<*, *, *>>()
private val executor = Dispatchers.IO.asExecutor()
@@ -68,9 +76,9 @@ class DefaultTargetDataLoader(
callback: Consumer<Drawable>,
): Drawable? {
val taskId = nextTaskId.getAndIncrement()
- LoadIconTask(context, info, userHandle, presentationFactory) { result ->
+ LoadIconTask(context, info, presentationFactory) { bitmap ->
removeTask(taskId)
- callback.accept(result)
+ callback.accept(bitmap?.toDrawable() ?: loadIconPlaceholder())
}
.also { addTask(taskId, it) }
.executeOnExecutor(executor)
@@ -87,9 +95,10 @@ class DefaultTargetDataLoader(
context.createContextAsUser(userHandle, 0),
info,
presentationFactory,
- ) { result ->
+ iconFactoryProvider,
+ ) { bitmap ->
removeTask(taskId)
- callback.accept(result)
+ callback.accept(bitmap?.toDrawable() ?: loadIconPlaceholder())
}
.also { addTask(taskId, it) }
.executeOnExecutor(executor)
@@ -123,6 +132,9 @@ class DefaultTargetDataLoader(
synchronized(activeTasks) { activeTasks.remove(id) }
}
+ private fun loadIconPlaceholder(): Drawable =
+ requireNotNull(context.getDrawable(R.drawable.resolver_icon_placeholder))
+
private fun destroy() {
synchronized(activeTasks) {
for (i in 0 until activeTasks.size()) {
@@ -131,4 +143,17 @@ class DefaultTargetDataLoader(
activeTasks.clear()
}
}
+
+ private fun Bitmap.toDrawable(): Drawable {
+ return if (targetHoverAndKeyboardFocusStates()) {
+ HoverBitmapDrawable(this)
+ } else {
+ BitmapDrawable(context.resources, this)
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(isAudioCaptureDevice: Boolean): DefaultTargetDataLoader
+ }
}
diff --git a/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt b/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt
new file mode 100644
index 00000000..4a21df92
--- /dev/null
+++ b/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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
+ *
+ * https://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.intentresolver.icons
+
+import android.graphics.Bitmap
+import com.android.launcher3.icons.FastBitmapDrawable
+
+/** A [FastBitmapDrawable] extension that provides access to the bitmap. */
+class HoverBitmapDrawable(val bitmap: Bitmap) : FastBitmapDrawable(bitmap) {
+
+ override fun newConstantState(): FastBitmapConstantState {
+ return HoverBitmapDrawableState(bitmap, iconColor)
+ }
+
+ private class HoverBitmapDrawableState(private val bitmap: Bitmap, color: Int) :
+ FastBitmapConstantState(bitmap, color) {
+ override fun createDrawable(): FastBitmapDrawable {
+ return HoverBitmapDrawable(bitmap)
+ }
+ }
+
+ companion object {
+ init {
+ setFlagHoverEnabled(true)
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java
index e2c0362d..01f9330e 100644
--- a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java
+++ b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java
@@ -23,7 +23,6 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Trace;
@@ -39,30 +38,36 @@ import com.android.intentresolver.util.UriFilters;
import java.util.function.Consumer;
+import javax.inject.Provider;
+
/**
* Loads direct share targets icons.
*/
class LoadDirectShareIconTask extends BaseLoadIconTask {
private static final String TAG = "DirectShareIconTask";
private final SelectableTargetInfo mTargetInfo;
+ private final Provider<SimpleIconFactory> mIconFactoryProvider;
LoadDirectShareIconTask(
Context context,
SelectableTargetInfo targetInfo,
TargetPresentationGetter.Factory presentationFactory,
- Consumer<Drawable> callback) {
+ Provider<SimpleIconFactory> iconFactoryProvider,
+ Consumer<Bitmap> callback) {
super(context, presentationFactory, callback);
+ mIconFactoryProvider = iconFactoryProvider;
mTargetInfo = targetInfo;
}
@Override
- protected Drawable doInBackground(Void... voids) {
- Drawable drawable = null;
+ @Nullable
+ protected Bitmap doInBackground(Void... voids) {
+ Bitmap iconBitmap = null;
Trace.beginSection("shortcut-icon");
try {
final Icon icon = mTargetInfo.getChooserTargetIcon();
if (icon == null || UriFilters.hasValidIcon(icon)) {
- drawable = getChooserTargetIconDrawable(
+ iconBitmap = getChooserTargetIconBitmap(
mContext,
icon,
mTargetInfo.getChooserTargetComponentName(),
@@ -71,25 +76,21 @@ class LoadDirectShareIconTask extends BaseLoadIconTask {
Log.e(TAG, "Failed to load shortcut icon for "
+ mTargetInfo.getChooserTargetComponentName() + "; no access");
}
- if (drawable == null) {
- drawable = loadIconPlaceholder();
- }
} catch (Exception e) {
Log.e(
TAG,
"Failed to load shortcut icon for "
+ mTargetInfo.getChooserTargetComponentName(),
e);
- drawable = loadIconPlaceholder();
} finally {
Trace.endSection();
}
- return drawable;
+ return iconBitmap;
}
@WorkerThread
@Nullable
- private Drawable getChooserTargetIconDrawable(
+ private Bitmap getChooserTargetIconBitmap(
Context context,
@Nullable Icon icon,
ComponentName targetComponentName,
@@ -125,10 +126,11 @@ class LoadDirectShareIconTask extends BaseLoadIconTask {
Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null);
// Raster target drawable with appIcon as a badge
- SimpleIconFactory sif = SimpleIconFactory.obtain(context);
- Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
- sif.recycle();
+ Bitmap directShareBadgedIcon;
+ try (SimpleIconFactory sif = mIconFactoryProvider.get()) {
+ directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
+ }
- return new BitmapDrawable(context.getResources(), directShareBadgedIcon);
+ return directShareBadgedIcon;
}
}
diff --git a/java/src/com/android/intentresolver/icons/LoadIconTask.java b/java/src/com/android/intentresolver/icons/LoadIconTask.java
index 75132208..4573fadf 100644
--- a/java/src/com/android/intentresolver/icons/LoadIconTask.java
+++ b/java/src/com/android/intentresolver/icons/LoadIconTask.java
@@ -19,11 +19,12 @@ package com.android.intentresolver.icons;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
+import android.graphics.Bitmap;
import android.os.Trace;
-import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.intentresolver.TargetPresentationGetter;
import com.android.intentresolver.chooser.DisplayResolveInfo;
@@ -32,38 +33,36 @@ import java.util.function.Consumer;
class LoadIconTask extends BaseLoadIconTask {
private static final String TAG = "IconTask";
protected final DisplayResolveInfo mDisplayResolveInfo;
- private final UserHandle mUserHandle;
private final ResolveInfo mResolveInfo;
LoadIconTask(
Context context, DisplayResolveInfo dri,
- UserHandle userHandle,
TargetPresentationGetter.Factory presentationFactory,
- Consumer<Drawable> callback) {
+ Consumer<Bitmap> callback) {
super(context, presentationFactory, callback);
- mUserHandle = userHandle;
mDisplayResolveInfo = dri;
mResolveInfo = dri.getResolveInfo();
}
@Override
- protected Drawable doInBackground(Void... params) {
+ @Nullable
+ protected Bitmap doInBackground(Void... params) {
Trace.beginSection("app-icon");
try {
return loadIconForResolveInfo(mResolveInfo);
} catch (Exception e) {
ComponentName componentName = mDisplayResolveInfo.getResolvedComponentName();
Log.e(TAG, "Failed to load app icon for " + componentName, e);
- return loadIconPlaceholder();
+ return null;
} finally {
Trace.endSection();
}
}
- protected final Drawable loadIconForResolveInfo(ResolveInfo ri) {
+ protected final Bitmap loadIconForResolveInfo(ResolveInfo ri) {
// Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons
// should be badged.
- return mPresentationFactory.makePresentationGetter(ri).getIcon(ri.userHandle);
+ return mPresentationFactory.makePresentationGetter(ri).getIconBitmap(ri.userHandle);
}
}
diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
index 935b527a..7cbd040e 100644
--- a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
@@ -23,24 +23,24 @@ import com.android.intentresolver.chooser.SelectableTargetInfo
import java.util.function.Consumer
/** A target data loader contract. Added to support testing. */
-abstract class TargetDataLoader {
+interface TargetDataLoader {
/** Load an app target icon */
- abstract fun getOrLoadAppTargetIcon(
+ fun getOrLoadAppTargetIcon(
info: DisplayResolveInfo,
userHandle: UserHandle,
callback: Consumer<Drawable>,
): Drawable?
/** Load a shortcut icon */
- abstract fun getOrLoadDirectShareIcon(
+ fun getOrLoadDirectShareIcon(
info: SelectableTargetInfo,
userHandle: UserHandle,
callback: Consumer<Drawable>,
): Drawable?
/** Load target label */
- abstract fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>)
+ fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>)
/** Loads DisplayResolveInfo's display label synchronously, if needed */
- abstract fun getOrLoadLabel(info: DisplayResolveInfo)
+ fun getOrLoadLabel(info: DisplayResolveInfo)
}
diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
index 9c0acb11..d6d4aae1 100644
--- a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
+++ b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
@@ -16,29 +16,45 @@
package com.android.intentresolver.icons
+import android.app.ActivityManager
import android.content.Context
-import androidx.lifecycle.Lifecycle
-import com.android.intentresolver.inject.ActivityOwned
+import android.content.pm.PackageManager
+import com.android.intentresolver.SimpleIconFactory
+import com.android.intentresolver.TargetPresentationGetter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
+import javax.inject.Provider
@Module
@InstallIn(ActivityComponent::class)
object TargetDataLoaderModule {
@Provides
- @ActivityScoped
- fun targetDataLoader(
- @ActivityContext context: Context,
- @ActivityOwned lifecycle: Lifecycle,
- ): TargetDataLoader = DefaultTargetDataLoader(context, lifecycle, isAudioCaptureDevice = false)
+ fun simpleIconFactory(@ActivityContext context: Context): SimpleIconFactory =
+ SimpleIconFactory.obtain(context)
+
+ @Provides
+ fun presentationGetterFactory(
+ iconFactoryProvider: Provider<SimpleIconFactory>,
+ packageManager: PackageManager,
+ activityManager: ActivityManager,
+ ): TargetPresentationGetter.Factory =
+ TargetPresentationGetter.Factory(
+ iconFactoryProvider,
+ packageManager,
+ activityManager.launcherLargeIconDensity,
+ )
@Provides
@ActivityScoped
@Caching
- fun cachingTargetDataLoader(targetDataLoader: TargetDataLoader): TargetDataLoader =
- CachingTargetDataLoader(targetDataLoader)
+ fun cachingTargetDataLoader(
+ @ActivityContext context: Context,
+ dataLoaderFactory: DefaultTargetDataLoader.Factory,
+ ): TargetDataLoader =
+ // Intended to be used in Chooser only thus the hardcoded isAudioCaptureDevice value.
+ CachingTargetDataLoader(context, dataLoaderFactory.create(isAudioCaptureDevice = false))
}
diff --git a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
index 7201bd2b..60eff925 100644
--- a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
+++ b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
@@ -18,9 +18,13 @@ package com.android.intentresolver.inject
import android.content.Intent
import android.net.Uri
+import android.os.Bundle
import android.service.chooser.ChooserAction
+import androidx.lifecycle.SavedStateHandle
+import com.android.intentresolver.Flags.saveShareouselState
import com.android.intentresolver.data.model.ChooserRequest
import com.android.intentresolver.data.repository.ActivityModelRepository
+import com.android.intentresolver.ui.viewmodel.CHOOSER_REQUEST_KEY
import com.android.intentresolver.ui.viewmodel.readChooserRequest
import com.android.intentresolver.util.ownedByCurrentUser
import com.android.intentresolver.validation.Valid
@@ -44,8 +48,12 @@ object ActivityModelModule {
@ViewModelScoped
fun provideInitialRequest(
activityModelRepo: ActivityModelRepository,
- flags: ChooserServiceFlags,
- ): ValidationResult<ChooserRequest> = readChooserRequest(activityModelRepo.value, flags)
+ savedStateHandle: SavedStateHandle,
+ ): ValidationResult<ChooserRequest> {
+ val activityModel = activityModelRepo.value
+ val extras = restoreChooserRequestExtras(activityModel.intent.extras, savedStateHandle)
+ return readChooserRequest(activityModel, extras)
+ }
@Provides
fun provideChooserRequest(initialRequest: ValidationResult<ChooserRequest>): ChooserRequest =
@@ -117,3 +125,18 @@ private val Intent.contentUris: Sequence<Uri>
}
}
}
+
+private fun restoreChooserRequestExtras(
+ initialExtras: Bundle?,
+ savedStateHandle: SavedStateHandle,
+): Bundle =
+ if (saveShareouselState()) {
+ savedStateHandle.get<Bundle>(CHOOSER_REQUEST_KEY)?.let { savedSateBundle ->
+ Bundle().apply {
+ initialExtras?.let { putAll(it) }
+ putAll(savedSateBundle)
+ }
+ } ?: initialExtras
+ } else {
+ initialExtras
+ } ?: Bundle()
diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt
index 13cadf37..13de84b2 100644
--- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt
@@ -36,7 +36,6 @@ import android.content.Intent.EXTRA_TEXT
import android.content.Intent.EXTRA_TITLE
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
-import android.content.IntentFilter
import android.content.IntentSender
import android.net.Uri
import android.os.Bundle
@@ -48,7 +47,6 @@ import com.android.intentresolver.R
import com.android.intentresolver.data.model.ChooserRequest
import com.android.intentresolver.ext.hasSendAction
import com.android.intentresolver.ext.ifMatch
-import com.android.intentresolver.inject.ChooserServiceFlags
import com.android.intentresolver.shared.model.ActivityModel
import com.android.intentresolver.util.hasValidIcon
import com.android.intentresolver.validation.Validation
@@ -69,11 +67,10 @@ internal fun Intent.maybeAddSendActionFlags() =
fun readChooserRequest(
model: ActivityModel,
- flags: ChooserServiceFlags,
+ savedState: Bundle = model.intent.extras ?: Bundle(),
): ValidationResult<ChooserRequest> {
- val extras = model.intent.extras ?: Bundle()
@Suppress("DEPRECATION")
- return validateFrom(extras::get) {
+ return validateFrom(savedState::get) {
val targetIntent = required(IntentOrUri(EXTRA_INTENT)).maybeAddSendActionFlags()
val isSendAction = targetIntent.hasSendAction()
@@ -126,7 +123,7 @@ fun readChooserRequest(
val additionalContentUri: Uri?
val focusedItemPos: Int
- if (isSendAction && flags.chooserPayloadToggling()) {
+ if (isSendAction) {
additionalContentUri = optional(value<Uri>(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI))
focusedItemPos = optional(value<Int>(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION)) ?: 0
} else {
@@ -166,7 +163,7 @@ fun readChooserRequest(
refinementIntentSender = refinementIntentSender,
sharedText = sharedText,
sharedTextTitle = sharedTextTitle,
- shareTargetFilter = targetIntent.toShareTargetFilter(),
+ shareTargetFilter = targetIntent.createIntentFilter(),
additionalContentUri = additionalContentUri,
focusedItemPosition = focusedItemPos,
contentTypeHint = contentTypeHint,
@@ -182,12 +179,3 @@ fun Validation.readChooserActions(): List<ChooserAction>? =
optional(array<ChooserAction>(EXTRA_CHOOSER_CUSTOM_ACTIONS))
?.filter { hasValidIcon(it) }
?.take(MAX_CHOOSER_ACTIONS)
-
-private fun Intent.toShareTargetFilter(): IntentFilter? {
- return type?.let {
- IntentFilter().apply {
- action?.also { addAction(it) }
- addDataType(it)
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt
index fe7e9109..8597d802 100644
--- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt
@@ -16,9 +16,12 @@
package com.android.intentresolver.ui.viewmodel
import android.content.ContentInterface
+import android.os.Bundle
import android.util.Log
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.intentresolver.Flags.saveShareouselState
import com.android.intentresolver.contentpreview.ImageLoader
import com.android.intentresolver.contentpreview.PreviewDataProvider
import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.FetchPreviewsInteractor
@@ -27,8 +30,8 @@ import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.Shar
import com.android.intentresolver.data.model.ChooserRequest
import com.android.intentresolver.data.repository.ActivityModelRepository
import com.android.intentresolver.data.repository.ChooserRequestRepository
+import com.android.intentresolver.domain.saveUpdates
import com.android.intentresolver.inject.Background
-import com.android.intentresolver.inject.ChooserServiceFlags
import com.android.intentresolver.shared.model.ActivityModel
import com.android.intentresolver.validation.Invalid
import com.android.intentresolver.validation.Valid
@@ -43,17 +46,18 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
private const val TAG = "ChooserViewModel"
+const val CHOOSER_REQUEST_KEY = "chooser-request"
@HiltViewModel
class ChooserViewModel
@Inject
constructor(
+ savedStateHandle: SavedStateHandle,
activityModelRepository: ActivityModelRepository,
private val shareouselViewModelProvider: Lazy<ShareouselViewModel>,
private val processUpdatesInteractor: Lazy<ProcessTargetIntentUpdatesInteractor>,
private val fetchPreviewsInteractor: Lazy<FetchPreviewsInteractor>,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val flags: ChooserServiceFlags,
/**
* Provided only for the express purpose of early exit in the event of an invalid request.
*
@@ -71,10 +75,6 @@ constructor(
val shareouselViewModel: ShareouselViewModel by lazy {
// TODO: consolidate this logic, this would require a consolidated preview view model but
// for now just postpone starting the payload selection preview machinery until it's needed
- assert(flags.chooserPayloadToggling()) {
- "An attempt to use payload selection preview with the disabled flag"
- }
-
viewModelScope.launch(bgDispatcher) { processUpdatesInteractor.get().activate() }
viewModelScope.launch(bgDispatcher) { fetchPreviewsInteractor.get().activate() }
shareouselViewModelProvider.get()
@@ -99,8 +99,24 @@ constructor(
}
init {
- if (initialRequest is Invalid) {
- Log.w(TAG, "initialRequest is Invalid, initialization failed")
+ when (initialRequest) {
+ is Invalid -> {
+ Log.w(TAG, "initialRequest is Invalid, initialization failed")
+ }
+ is Valid<ChooserRequest> -> {
+ if (saveShareouselState()) {
+ val isRestored =
+ savedStateHandle.get<Bundle>(CHOOSER_REQUEST_KEY)?.takeIf { !it.isEmpty } !=
+ null
+ savedStateHandle.setSavedStateProvider(CHOOSER_REQUEST_KEY) {
+ Bundle().also { result ->
+ request.value
+ .takeIf { isRestored || it != initialRequest.value }
+ ?.saveUpdates(result)
+ }
+ }
+ }
+ }
}
}
}
diff --git a/java/src/com/android/intentresolver/ui/viewmodel/IntentExt.kt b/java/src/com/android/intentresolver/ui/viewmodel/IntentExt.kt
new file mode 100644
index 00000000..30f16d20
--- /dev/null
+++ b/java/src/com/android/intentresolver/ui/viewmodel/IntentExt.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.intentresolver.ui.viewmodel
+
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.IntentFilter.MalformedMimeTypeException
+import android.net.Uri
+import android.os.PatternMatcher
+
+/** Collects Uris from standard locations within the Intent. */
+fun Intent.collectUris(): Set<Uri> = buildSet {
+ data?.also { add(it) }
+ @Suppress("DEPRECATION")
+ when (val stream = extras?.get(Intent.EXTRA_STREAM)) {
+ is Uri -> add(stream)
+ is ArrayList<*> -> addAll(stream.mapNotNull { it as? Uri })
+ else -> Unit
+ }
+ clipData?.apply { (0..<itemCount).mapNotNull { getItemAt(it).uri }.forEach(::add) }
+}
+
+fun IntentFilter.addUri(uri: Uri) {
+ uri.scheme?.also { addDataScheme(it) }
+ uri.host?.also { addDataAuthority(it, null) }
+ uri.path?.also { addDataPath(it, PatternMatcher.PATTERN_LITERAL) }
+}
+
+fun Intent.createIntentFilter(): IntentFilter? {
+ val uris = collectUris()
+ if (action == null && uris.isEmpty()) {
+ // at least one is required to be meaningful
+ return null
+ }
+ return IntentFilter().also { filter ->
+ type?.also {
+ try {
+ filter.addDataType(it)
+ } catch (_: MalformedMimeTypeException) { // ignore malformed type
+ }
+ }
+ action?.also { filter.addAction(it) }
+ uris.forEach(filter::addUri)
+ }
+}
diff --git a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
new file mode 100644
index 00000000..b5a4d617
--- /dev/null
+++ b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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
+ *
+ * https://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.intentresolver.widget
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.intentresolver.R
+
+class ChooserTargetItemView(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ private val outlineRadius: Float
+ private val outlineWidth: Float
+ private val outlinePaint: Paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
+ private val outlineInnerPaint: Paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
+ private var iconView: ImageView? = null
+
+ constructor(context: Context) : this(context, null)
+
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : this(context, attrs, defStyleAttr, 0)
+
+ init {
+ val a = context.obtainStyledAttributes(attrs, R.styleable.ChooserTargetItemView)
+ val defaultWidth =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 2f,
+ context.resources.displayMetrics,
+ )
+ outlineRadius =
+ a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineCornerRadius, 0f)
+ outlineWidth =
+ a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineWidth, defaultWidth)
+
+ outlinePaint.strokeWidth = outlineWidth
+ outlinePaint.color =
+ a.getColor(R.styleable.ChooserTargetItemView_focusOutlineColor, Color.TRANSPARENT)
+
+ outlineInnerPaint.strokeWidth = outlineWidth
+ outlineInnerPaint.color =
+ a.getColor(R.styleable.ChooserTargetItemView_focusInnerOutlineColor, Color.TRANSPARENT)
+ a.recycle()
+ }
+
+ override fun onViewAdded(child: View) {
+ super.onViewAdded(child)
+ if (child is ImageView) {
+ iconView = child
+ }
+ }
+
+ override fun onViewRemoved(child: View?) {
+ super.onViewRemoved(child)
+ if (child === iconView) {
+ iconView = null
+ }
+ }
+
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ val iconView = iconView ?: return false
+ if (!isEnabled) return true
+ when (event.action) {
+ MotionEvent.ACTION_HOVER_ENTER -> {
+ iconView.isHovered = true
+ }
+ MotionEvent.ACTION_HOVER_EXIT -> {
+ iconView.isHovered = false
+ }
+ }
+ return true
+ }
+
+ override fun onInterceptHoverEvent(event: MotionEvent?) = true
+
+ override fun dispatchDraw(canvas: Canvas) {
+ super.dispatchDraw(canvas)
+ if (isFocused) {
+ drawFocusInnerOutline(canvas)
+ drawFocusOutline(canvas)
+ }
+ }
+
+ private fun drawFocusInnerOutline(canvas: Canvas) {
+ val outlineOffset = outlineWidth + outlineWidth / 2
+ canvas.drawRoundRect(
+ outlineOffset,
+ outlineOffset,
+ maxOf(0f, width - outlineOffset),
+ maxOf(0f, height - outlineOffset),
+ outlineRadius - outlineWidth,
+ outlineRadius - outlineWidth,
+ outlineInnerPaint,
+ )
+ }
+
+ private fun drawFocusOutline(canvas: Canvas) {
+ val outlineOffset = outlineWidth / 2
+ canvas.drawRoundRect(
+ outlineOffset,
+ outlineOffset,
+ maxOf(0f, width - outlineOffset),
+ maxOf(0f, height - outlineOffset),
+ outlineRadius,
+ outlineRadius,
+ outlinePaint,
+ )
+ }
+}
diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
index c706e3ee..935a8724 100644
--- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
+++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
@@ -71,38 +71,39 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
constructor(
context: Context,
attrs: AttributeSet?,
- defStyleAttr: Int
+ defStyleAttr: Int,
) : super(context, attrs, defStyleAttr) {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ val editButtonRoleDescription: CharSequence?
context
.obtainStyledAttributes(attrs, R.styleable.ScrollableImagePreviewView, defStyleAttr, 0)
.use { a ->
var innerSpacing =
a.getDimensionPixelSize(
R.styleable.ScrollableImagePreviewView_itemInnerSpacing,
- -1
+ -1,
)
if (innerSpacing < 0) {
innerSpacing =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
3f,
- context.resources.displayMetrics
+ context.resources.displayMetrics,
)
.toInt()
}
outerSpacing =
a.getDimensionPixelSize(
R.styleable.ScrollableImagePreviewView_itemOuterSpacing,
- -1
+ -1,
)
if (outerSpacing < 0) {
outerSpacing =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
16f,
- context.resources.displayMetrics
+ context.resources.displayMetrics,
)
.toInt()
}
@@ -110,10 +111,13 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
maxWidthHint =
a.getDimensionPixelSize(R.styleable.ScrollableImagePreviewView_maxWidthHint, -1)
+
+ editButtonRoleDescription =
+ a.getText(R.styleable.ScrollableImagePreviewView_editButtonRoleDescription)
}
val itemAnimator = ItemAnimator()
super.setItemAnimator(itemAnimator)
- super.setAdapter(Adapter(context, itemAnimator.getAddDuration()))
+ super.setAdapter(Adapter(context, itemAnimator.getAddDuration(), editButtonRoleDescription))
}
private var batchLoader: BatchPreviewLoader? = null
@@ -125,7 +129,6 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
*/
var maxWidthHint: Int = -1
- private var requestedHeight: Int = 0
private var isMeasured = false
private var maxAspectRatio = MAX_ASPECT_RATIO
private var maxAspectRatioString = MAX_ASPECT_RATIO_STRING
@@ -217,7 +220,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
onNoPreviewCallback?.run()
}
previewAdapter.markLoaded()
- }
+ },
)
maybeLoadAspectRatios()
}
@@ -281,24 +284,25 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
val type: PreviewType,
val uri: Uri,
val editAction: Runnable?,
- internal var aspectRatioString: String
+ internal var aspectRatioString: String,
) {
constructor(
type: PreviewType,
uri: Uri,
- editAction: Runnable?
+ editAction: Runnable?,
) : this(type, uri, editAction, "1:1")
}
enum class PreviewType {
Image,
Video,
- File
+ File,
}
private class Adapter(
private val context: Context,
private val fadeInDurationMs: Long,
+ private val editButtonRoleDescription: CharSequence?,
) : RecyclerView.Adapter<ViewHolder>() {
private val previews = ArrayList<Preview>()
private val imagePreviewDescription =
@@ -409,6 +413,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
previewSize,
fadeInDurationMs,
isSharedTransitionElement = position == firstImagePos,
+ editButtonRoleDescription,
previewReadyCallback =
if (
position == firstImagePos && transitionStatusElementCallback != null
@@ -416,7 +421,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
this::onTransitionElementReady
} else {
null
- }
+ },
)
}
}
@@ -461,7 +466,8 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
previewSize: Size,
fadeInDurationMs: Long,
isSharedTransitionElement: Boolean,
- previewReadyCallback: ((String) -> Unit)?
+ editButtonRoleDescription: CharSequence?,
+ previewReadyCallback: ((String) -> Unit)?,
) {
image.setImageDrawable(null)
image.alpha = 1f
@@ -495,6 +501,12 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
editActionContainer?.apply {
setOnClickListener { onClick.run() }
visibility = View.VISIBLE
+ if (editButtonRoleDescription != null) {
+ ViewCompat.setAccessibilityDelegate(
+ this,
+ ViewRoleDescriptionAccessibilityDelegate(editButtonRoleDescription),
+ )
+ }
}
}
resetScope().launch {
@@ -568,7 +580,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
PluralsMessageFormatter.format(
itemView.context.resources,
mapOf(PLURALS_COUNT to count),
- R.string.other_files
+ R.string.other_files,
)
}
@@ -611,7 +623,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
state: State,
viewHolder: RecyclerView.ViewHolder,
changeFlags: Int,
- payloads: MutableList<Any>
+ payloads: MutableList<Any>,
): ItemHolderInfo {
return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads).let {
holderInfo ->
@@ -626,7 +638,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
override fun animateDisappearance(
viewHolder: RecyclerView.ViewHolder,
preLayoutInfo: ItemHolderInfo,
- postLayoutInfo: ItemHolderInfo?
+ postLayoutInfo: ItemHolderInfo?,
): Boolean {
if (viewHolder is LoadingItemViewHolder && preLayoutInfo is LoadingItemHolderInfo) {
val view = viewHolder.itemView
@@ -647,10 +659,8 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
super.onRemoveFinished(viewHolder)
}
- private inner class LoadingItemHolderInfo(
- holderInfo: ItemHolderInfo,
- val parentLeft: Int,
- ) : ItemHolderInfo() {
+ private inner class LoadingItemHolderInfo(holderInfo: ItemHolderInfo, val parentLeft: Int) :
+ ItemHolderInfo() {
init {
left = holderInfo.left
top = holderInfo.top
diff --git a/java/src/com/android/intentresolver/widget/ViewRoleDescriptionAccessibilityDelegate.kt b/java/src/com/android/intentresolver/widget/ViewRoleDescriptionAccessibilityDelegate.kt
new file mode 100644
index 00000000..8fe7144a
--- /dev/null
+++ b/java/src/com/android/intentresolver/widget/ViewRoleDescriptionAccessibilityDelegate.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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
+ *
+ * https://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.intentresolver.widget
+
+import android.view.View
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+
+class ViewRoleDescriptionAccessibilityDelegate(private val roleDescription: CharSequence) :
+ AccessibilityDelegateCompat() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.roleDescription = roleDescription
+ }
+}
diff --git a/tests/activity/Android.bp b/tests/activity/Android.bp
index 9d673b4c..2e66a84d 100644
--- a/tests/activity/Android.bp
+++ b/tests/activity/Android.bp
@@ -57,7 +57,6 @@ android_test {
"mockito-kotlin-nodeps",
"testables",
"truth",
- "truth-java8-extension",
"flag-junit",
"platform-test-annotations",
],
diff --git a/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
index 22633085..0d317dc3 100644
--- a/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
+++ b/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
@@ -160,7 +160,7 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
}
- private static class TargetDataLoaderWrapper extends TargetDataLoader {
+ private static class TargetDataLoaderWrapper implements TargetDataLoader {
private final TargetDataLoader mTargetDataLoader;
private final CountingIdlingResource mLabelIdlingResource;
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index c968c128..9109507a 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -39,7 +39,6 @@ android_test {
"IntentResolver-tests-shared",
"junit",
"truth",
- "truth-java8-extension",
],
test_suites: ["general-tests"],
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 850c447f..a3b30a3a 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -59,7 +59,6 @@ android_test {
"platform-compat-test-rules", // PlatformCompatChangeRule
"testables", // TestableContext/TestableResources
"truth",
- "truth-java8-extension",
"flag-junit",
"platform-test-annotations",
],
diff --git a/tests/unit/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt b/tests/unit/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
index e26dffb8..d591d928 100644
--- a/tests/unit/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
@@ -21,13 +21,18 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.service.chooser.ChooserTarget
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.intentresolver.Flags.FLAG_REBUILD_ADAPTERS_ON_TARGET_PINNING
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.chooser.TargetInfo
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@@ -36,10 +41,12 @@ private const val PACKAGE_A = "package.a"
private const val PACKAGE_B = "package.b"
private const val CLASS_NAME = "./MainActivity"
+private val PERSONAL_USER_HANDLE: UserHandle =
+ InstrumentationRegistry.getInstrumentation().targetContext.user
+
@SmallTest
class ShortcutSelectionLogicTest {
- private val PERSONAL_USER_HANDLE: UserHandle =
- InstrumentationRegistry.getInstrumentation().getTargetContext().getUser()
+ @get:Rule val flagRule = SetFlagsRule()
private val packageTargets =
HashMap<String, Array<ChooserTarget>>().apply {
@@ -57,6 +64,14 @@ class ShortcutSelectionLogicTest {
this[pkg] = targets
}
}
+ private val targetInfoChooserTargetCorrespondence =
+ Correspondence.from<TargetInfo, ChooserTarget>(
+ { actual, expected ->
+ actual.chooserTargetComponentName == expected.componentName &&
+ actual.displayLabel == expected.title
+ },
+ "",
+ )
private val baseDisplayInfo =
DisplayResolveInfo.newDisplayResolveInfo(
@@ -64,7 +79,7 @@ class ShortcutSelectionLogicTest {
ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
"label",
"extended info",
- Intent()
+ Intent(),
)
private val otherBaseDisplayInfo =
@@ -73,7 +88,7 @@ class ShortcutSelectionLogicTest {
ResolverDataProvider.createResolveInfo(4, 0, PERSONAL_USER_HANDLE),
"label 2",
"extended info 2",
- Intent()
+ Intent(),
)
private operator fun Map<String, Array<ChooserTarget>>.get(pkg: String, idx: Int) =
@@ -87,7 +102,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ false
+ /* applySharingAppLimits = */ false,
)
val isUpdated =
@@ -102,15 +117,15 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertTrue("Updates are expected", isUpdated)
- assertShortcutsInOrder(
- listOf(sc2, sc1),
- serviceResults,
- "Two shortcuts are expected as we do not apply per-app shortcut limit"
- )
+ assertWithMessage("Updates are expected").that(isUpdated).isTrue()
+ assertWithMessage("Two shortcuts are expected as we do not apply per-app shortcut limit")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc2, sc1)
+ .inOrder()
}
@Test
@@ -121,7 +136,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ true
+ /* applySharingAppLimits = */ true,
)
val isUpdated =
@@ -136,15 +151,15 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertTrue("Updates are expected", isUpdated)
- assertShortcutsInOrder(
- listOf(sc2),
- serviceResults,
- "One shortcut is expected as we apply per-app shortcut limit"
- )
+ assertWithMessage("Updates are expected").that(isUpdated).isTrue()
+ assertWithMessage("One shortcut is expected as we apply per-app shortcut limit")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc2)
+ .inOrder()
}
@Test
@@ -155,7 +170,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ false
+ /* applySharingAppLimits = */ false,
)
val isUpdated =
@@ -170,15 +185,15 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 1,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertTrue("Updates are expected", isUpdated)
- assertShortcutsInOrder(
- listOf(sc2),
- serviceResults,
- "One shortcut is expected as we apply overall shortcut limit"
- )
+ assertWithMessage("Updates are expected").that(isUpdated).isTrue()
+ assertWithMessage("One shortcut is expected as we apply overall shortcut limit")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc2)
+ .inOrder()
}
@Test
@@ -191,7 +206,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ true
+ /* applySharingAppLimits = */ true,
)
testSubject.addServiceResults(
@@ -205,7 +220,7 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
testSubject.addServiceResults(
/* origTarget = */ otherBaseDisplayInfo,
@@ -218,14 +233,14 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertShortcutsInOrder(
- listOf(pkgBsc2, pkgAsc2),
- serviceResults,
- "Two shortcuts are expected as we apply per-app shortcut limit"
- )
+ assertWithMessage("Two shortcuts are expected as we apply per-app shortcut limit")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(pkgBsc2, pkgAsc2)
+ .inOrder()
}
@Test
@@ -236,7 +251,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ false
+ /* applySharingAppLimits = */ false,
)
val isUpdated =
@@ -256,15 +271,15 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertTrue("Updates are expected", isUpdated)
- assertShortcutsInOrder(
- listOf(sc1, sc2),
- serviceResults,
- "Two shortcuts are expected as we do not apply per-app shortcut limit"
- )
+ assertWithMessage("Updates are expected").that(isUpdated).isTrue()
+ assertWithMessage("Two shortcuts are expected as we do not apply per-app shortcut limit")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc1, sc2)
+ .inOrder()
}
@Test
@@ -276,7 +291,7 @@ class ShortcutSelectionLogicTest {
val testSubject =
ShortcutSelectionLogic(
/* maxShortcutTargetsPerApp = */ 1,
- /* applySharingAppLimits = */ true
+ /* applySharingAppLimits = */ true,
)
val context = mock<Context> { on { packageManager } doReturn (mock()) }
@@ -291,36 +306,82 @@ class ShortcutSelectionLogicTest {
/* targetIntent = */ mock(),
/* refererFillInIntent = */ mock(),
/* maxRankedTargets = */ 4,
- /* serviceTargets = */ serviceResults
+ /* serviceTargets = */ serviceResults,
)
- assertShortcutsInOrder(
- listOf(sc3, sc2),
- serviceResults,
- "At most two caller-provided shortcuts are allowed"
- )
+ assertWithMessage("At most two caller-provided shortcuts are allowed")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc3, sc2)
+ .inOrder()
}
- // TODO: consider renaming. Not all `ChooserTarget`s are "shortcuts" and many of our test cases
- // add results with `isShortcutResult = false` and `directShareToShortcutInfos = emptyMap()`.
- private fun assertShortcutsInOrder(
- expected: List<ChooserTarget>,
- actual: List<TargetInfo>,
- msg: String? = ""
- ) {
- assertEquals(msg, expected.size, actual.size)
- for (i in expected.indices) {
- assertEquals(
- "Unexpected item at position $i",
- expected[i].componentName,
- actual[i].chooserTargetComponentName
+ @Test
+ @EnableFlags(FLAG_REBUILD_ADAPTERS_ON_TARGET_PINNING)
+ fun addServiceResults_sameShortcutWithDifferentPinnedStatus_shortcutUpdated() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 =
+ createChooserTarget(
+ title = "Shortcut",
+ score = 1f,
+ ComponentName(PACKAGE_A, CLASS_NAME),
+ PACKAGE_A.shortcutId(0),
)
- assertEquals(
- "Unexpected item at position $i",
- expected[i].title,
- actual[i].displayLabel
+ val sc2 =
+ createChooserTarget(
+ title = "Shortcut",
+ score = 1f,
+ ComponentName(PACKAGE_A, CLASS_NAME),
+ PACKAGE_A.shortcutId(0),
)
- }
+ val testSubject =
+ ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ false,
+ )
+
+ testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ mapOf(
+ sc1 to createShortcutInfo(PACKAGE_A.shortcutId(1), sc1.componentName, 1)
+ ),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults,
+ )
+ val isUpdated =
+ testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ mapOf(
+ sc1 to
+ createShortcutInfo(PACKAGE_A.shortcutId(1), sc1.componentName, 1).apply {
+ addFlags(ShortcutInfo.FLAG_PINNED)
+ }
+ ),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults,
+ )
+
+ assertWithMessage("Updates are expected").that(isUpdated).isTrue()
+ assertWithMessage("Updated shortcut is expected")
+ .that(serviceResults)
+ .comparingElementsUsing(targetInfoChooserTargetCorrespondence)
+ .containsExactly(sc2)
+ .inOrder()
+ assertThat(serviceResults[0].isPinned).isTrue()
}
private fun String.shortcutId(id: Int) = "$this.$id"
diff --git a/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt b/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt
index 92848b2c..b5b05eb9 100644
--- a/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt
+++ b/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt
@@ -32,32 +32,42 @@ class TargetPresentationGetterTest {
withSubstitutePermission: Boolean,
appLabel: String,
activityLabel: String,
- resolveInfoLabel: String
+ resolveInfoLabel: String,
): TargetPresentationGetter {
val testPackageInfo =
ResolverDataProvider.createPackageManagerMockedInfo(
withSubstitutePermission,
appLabel,
activityLabel,
- resolveInfoLabel
+ resolveInfoLabel,
+ )
+ val factory =
+ TargetPresentationGetter.Factory(
+ { SimpleIconFactory.obtain(testPackageInfo.ctx) },
+ testPackageInfo.ctx.packageManager,
+ 100,
)
- val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
return factory.makePresentationGetter(testPackageInfo.resolveInfo)
}
fun makeActivityInfoPresentationGetter(
withSubstitutePermission: Boolean,
appLabel: String?,
- activityLabel: String?
+ activityLabel: String?,
): TargetPresentationGetter {
val testPackageInfo =
ResolverDataProvider.createPackageManagerMockedInfo(
withSubstitutePermission,
appLabel,
activityLabel,
- ""
+ "",
+ )
+ val factory =
+ TargetPresentationGetter.Factory(
+ { SimpleIconFactory.obtain(testPackageInfo.ctx) },
+ testPackageInfo.ctx.packageManager,
+ 100,
)
- val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
return factory.makePresentationGetter(testPackageInfo.activityInfo)
}
@@ -158,7 +168,7 @@ class TargetPresentationGetterTest {
false,
"app_label",
"activity_label",
- "resolve_info_label"
+ "resolve_info_label",
)
assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
@@ -192,7 +202,7 @@ class TargetPresentationGetterTest {
true,
"app_label",
"activity_label",
- "resolve_info_label"
+ "resolve_info_label",
)
assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
index 905c8517..ef0703e6 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
@@ -61,11 +61,7 @@ class ChooserContentPreviewUiTest {
private val transitionCallback = mock<ImagePreviewView.TransitionElementStatusCallback>()
@get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- private fun createContentPreviewUi(
- action: String,
- sharedText: CharSequence? = null,
- isPayloadTogglingEnabled: Boolean = false
- ) =
+ private fun createContentPreviewUi(action: String, sharedText: CharSequence? = null) =
ChooserContentPreviewUi(
testScope,
previewData,
@@ -81,7 +77,6 @@ class ChooserContentPreviewUiTest {
headlineGenerator,
ContentTypeHint.NONE,
testMetadataText,
- isPayloadTogglingEnabled,
)
@Test
@@ -114,10 +109,7 @@ class ChooserContentPreviewUiTest {
.thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build())
whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow())
val testSubject =
- createContentPreviewUi(
- action = Intent.ACTION_SEND,
- sharedText = "Shared text",
- )
+ createContentPreviewUi(action = Intent.ACTION_SEND, sharedText = "Shared text")
assertThat(testSubject.mContentPreviewUi)
.isInstanceOf(FilesPlusTextContentPreviewUi::class.java)
verify(previewData, times(1)).imagePreviewFileInfoFlow
@@ -150,11 +142,7 @@ class ChooserContentPreviewUiTest {
whenever(previewData.firstFileInfo)
.thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build())
whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow())
- val testSubject =
- createContentPreviewUi(
- action = Intent.ACTION_SEND,
- isPayloadTogglingEnabled = true,
- )
+ val testSubject = createContentPreviewUi(action = Intent.ACTION_SEND)
assertThat(testSubject.mContentPreviewUi)
.isInstanceOf(ShareouselContentPreviewUi::class.java)
assertThat(testSubject.preferredContentPreview)
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt
index 3dae760c..9884a675 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt
@@ -21,14 +21,12 @@ import android.content.Intent
import android.database.MatrixCursor
import android.media.MediaMetadata
import android.net.Uri
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.DocumentsContract
import android.provider.Downloads
import android.provider.OpenableColumns
-import android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING
import com.android.intentresolver.Flags.FLAG_INDIVIDUAL_METADATA_TITLE_READ
import com.google.common.truth.Truth.assertThat
import kotlin.coroutines.EmptyCoroutineContext
@@ -493,7 +491,6 @@ class PreviewDataProviderTest(flags: FlagsParameterization) {
}
@Test
- @EnableFlags(FLAG_CHOOSER_PAYLOAD_TOGGLING)
fun sendImageWithAdditionalContentUri_showPayloadTogglingUi() {
val uri = Uri.parse("content://org.pkg.app/image.png")
val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) }
@@ -513,26 +510,6 @@ class PreviewDataProviderTest(flags: FlagsParameterization) {
}
@Test
- @DisableFlags(FLAG_CHOOSER_PAYLOAD_TOGGLING)
- fun sendImageWithAdditionalContentUriAndDisabledFlag_showImagePreviewUi() {
- val uri = Uri.parse("content://org.pkg.app/image.png")
- val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) }
- whenever(contentResolver.getType(uri)).thenReturn("image/png")
- val testSubject =
- createDataProvider(
- targetIntent,
- additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"),
- )
-
- assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
- assertThat(testSubject.uriCount).isEqualTo(1)
- assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri)
- assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri)
- verify(contentResolver, times(1)).getType(any())
- }
-
- @Test
- @EnableFlags(FLAG_CHOOSER_PAYLOAD_TOGGLING)
fun sendItemsWithAdditionalContentUriWithSameAuthority_showImagePreviewUi() {
val uri = Uri.parse("content://org.pkg.app/image.png")
val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) }
diff --git a/tests/unit/src/com/android/intentresolver/icons/CachingTargetDataLoaderTest.kt b/tests/unit/src/com/android/intentresolver/icons/CachingTargetDataLoaderTest.kt
index a36b512b..2f0ed423 100644
--- a/tests/unit/src/com/android/intentresolver/icons/CachingTargetDataLoaderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/icons/CachingTargetDataLoaderTest.kt
@@ -21,11 +21,16 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.graphics.Bitmap
+import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.UserHandle
+import com.android.intentresolver.ResolverDataProvider.createResolveInfo
+import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.chooser.SelectableTargetInfo
+import com.android.intentresolver.chooser.TargetInfo
import java.util.function.Consumer
import org.junit.Test
import org.mockito.kotlin.any
@@ -37,6 +42,7 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class CachingTargetDataLoaderTest {
+ private val context = mock<Context>()
private val userHandle = UserHandle.of(1)
@Test
@@ -61,7 +67,7 @@ class CachingTargetDataLoaderTest {
on { getOrLoadDirectShareIcon(eq(callerTarget), eq(userHandle), any()) } doReturn
null
}
- val testSubject = CachingTargetDataLoader(targetDataLoader)
+ val testSubject = CachingTargetDataLoader(context, targetDataLoader)
val callback = Consumer<Drawable> {}
testSubject.getOrLoadDirectShareIcon(callerTarget, userHandle, callback)
@@ -102,7 +108,7 @@ class CachingTargetDataLoaderTest {
}
.whenever(targetDataLoader)
.getOrLoadDirectShareIcon(eq(targetInfo), eq(userHandle), any())
- val testSubject = CachingTargetDataLoader(targetDataLoader)
+ val testSubject = CachingTargetDataLoader(context, targetDataLoader)
val callback = Consumer<Drawable> {}
testSubject.getOrLoadDirectShareIcon(targetInfo, userHandle, callback)
@@ -112,6 +118,70 @@ class CachingTargetDataLoaderTest {
1 * { getOrLoadDirectShareIcon(eq(targetInfo), eq(userHandle), any()) }
}
}
+
+ @Test
+ fun onlyBitmapsAreCached() {
+ val context =
+ mock<Context> {
+ on { userId } doReturn 1
+ on { packageName } doReturn "package"
+ }
+ val colorTargetInfo =
+ DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ createResolveInfo(1, userHandle.identifier),
+ Intent(),
+ ) as DisplayResolveInfo
+ val bitmapTargetInfo =
+ DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ createResolveInfo(2, userHandle.identifier),
+ Intent(),
+ ) as DisplayResolveInfo
+ val hoverBitmapTargetInfo =
+ DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ createResolveInfo(3, userHandle.identifier),
+ Intent(),
+ ) as DisplayResolveInfo
+
+ val targetDataLoader = mock<TargetDataLoader>()
+ doAnswer {
+ val target = it.arguments[0] as TargetInfo
+ val callback = it.arguments[2] as Consumer<Drawable>
+ val drawable =
+ if (target === bitmapTargetInfo) {
+ BitmapDrawable(createBitmap())
+ } else if (target === hoverBitmapTargetInfo) {
+ HoverBitmapDrawable(createBitmap())
+ } else {
+ ColorDrawable(Color.RED)
+ }
+ callback.accept(drawable)
+ null
+ }
+ .whenever(targetDataLoader)
+ .getOrLoadAppTargetIcon(any(), eq(userHandle), any())
+ val testSubject = CachingTargetDataLoader(context, targetDataLoader)
+ val callback = Consumer<Drawable> {}
+
+ testSubject.getOrLoadAppTargetIcon(colorTargetInfo, userHandle, callback)
+ testSubject.getOrLoadAppTargetIcon(colorTargetInfo, userHandle, callback)
+ testSubject.getOrLoadAppTargetIcon(bitmapTargetInfo, userHandle, callback)
+ testSubject.getOrLoadAppTargetIcon(bitmapTargetInfo, userHandle, callback)
+ testSubject.getOrLoadAppTargetIcon(hoverBitmapTargetInfo, userHandle, callback)
+ testSubject.getOrLoadAppTargetIcon(hoverBitmapTargetInfo, userHandle, callback)
+
+ verify(targetDataLoader) {
+ 2 * { getOrLoadAppTargetIcon(eq(colorTargetInfo), eq(userHandle), any()) }
+ }
+ verify(targetDataLoader) {
+ 1 * { getOrLoadAppTargetIcon(eq(bitmapTargetInfo), eq(userHandle), any()) }
+ }
+ verify(targetDataLoader) {
+ 1 * { getOrLoadAppTargetIcon(eq(hoverBitmapTargetInfo), eq(userHandle), any()) }
+ }
+ }
}
private fun createBitmap() = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
diff --git a/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt b/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt
index 6e5c97c2..a4bcad38 100644
--- a/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt
+++ b/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt
@@ -23,7 +23,7 @@ import android.provider.Settings
import android.testing.TestableResources
import androidx.test.platform.app.InstrumentationRegistry
import com.android.intentresolver.R
-import com.google.common.truth.Truth8.assertThat
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -34,7 +34,7 @@ class NearbyShareModuleTest {
/** Create Resources with overridden values. */
private fun Context.fakeResources(
config: Configuration? = null,
- block: TestableResources.() -> Unit
+ block: TestableResources.() -> Unit,
) =
TestableResources(resources)
.apply { config?.let { overrideConfiguration(it) } }
@@ -64,7 +64,7 @@ class NearbyShareModuleTest {
context.fakeResources {
addOverride(
R.string.config_defaultNearbySharingComponent,
- "com.example/.ComponentName"
+ "com.example/.ComponentName",
)
}
@@ -83,7 +83,7 @@ class NearbyShareModuleTest {
context.fakeResources {
addOverride(
R.string.config_defaultNearbySharingComponent,
- "com.example/.AComponent"
+ "com.example/.AComponent",
)
}
diff --git a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
index 7bd4edee..71f28950 100644
--- a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
@@ -28,12 +28,10 @@ import android.content.Intent.EXTRA_REFERRER
import android.content.Intent.EXTRA_TEXT
import android.content.Intent.EXTRA_TITLE
import android.net.Uri
-import android.service.chooser.Flags
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import com.android.intentresolver.ContentTypeHint
import com.android.intentresolver.data.model.ChooserRequest
-import com.android.intentresolver.inject.FakeChooserServiceFlags
import com.android.intentresolver.shared.model.ActivityModel
import com.android.intentresolver.validation.Importance
import com.android.intentresolver.validation.Invalid
@@ -59,13 +57,10 @@ private fun createActivityModel(
class ChooserRequestTest {
- private val fakeChooserServiceFlags =
- FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) }
-
@Test
fun missingIntent() {
val model = createActivityModel(targetIntent = null)
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Invalid::class.java)
result as Invalid<ChooserRequest>
@@ -80,7 +75,7 @@ class ChooserRequestTest {
val model = createActivityModel(targetIntent = Intent(ACTION_SEND), referrer)
model.intent.putExtras(bundleOf(EXTRA_REFERRER to referrer))
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -97,7 +92,7 @@ class ChooserRequestTest {
val model = createActivityModel(targetIntent = intent, referrer = referrer)
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -112,7 +107,7 @@ class ChooserRequestTest {
model.intent.putExtras(bundleOf(EXTRA_REFERRER to referrer))
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -126,7 +121,7 @@ class ChooserRequestTest {
val intent2 = Intent(ACTION_SEND_MULTIPLE)
val model = createActivityModel(targetIntent = intent1, additionalIntents = listOf(intent2))
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -139,7 +134,7 @@ class ChooserRequestTest {
val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
val model = createActivityModel(targetIntent = intent)
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -149,7 +144,6 @@ class ChooserRequestTest {
@Test
fun testRequest_actionSendWithAdditionalContentUri() {
- fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true)
val uri = Uri.parse("content://org.pkg/path")
val position = 10
val model =
@@ -158,7 +152,7 @@ class ChooserRequestTest {
intent.putExtra(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION, position)
}
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -168,35 +162,14 @@ class ChooserRequestTest {
}
@Test
- fun testRequest_actionSendWithAdditionalContentUri_parametersIgnoredWhenFlagDisabled() {
- fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false)
- val uri = Uri.parse("content://org.pkg/path")
- val position = 10
- val model =
- createActivityModel(targetIntent = Intent(ACTION_SEND)).apply {
- intent.putExtra(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI, uri)
- intent.putExtra(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION, position)
- }
- val result = readChooserRequest(model, fakeChooserServiceFlags)
-
- assertThat(result).isInstanceOf(Valid::class.java)
- result as Valid<ChooserRequest>
-
- assertThat(result.value.additionalContentUri).isNull()
- assertThat(result.value.focusedItemPosition).isEqualTo(0)
- assertThat(result.warnings).isEmpty()
- }
-
- @Test
fun testRequest_actionSendWithInvalidAdditionalContentUri() {
- fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true)
val model =
createActivityModel(targetIntent = Intent(ACTION_SEND)).apply {
intent.putExtra(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI, "__invalid__")
intent.putExtra(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION, "__invalid__")
}
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -207,10 +180,9 @@ class ChooserRequestTest {
@Test
fun testRequest_actionSendWithoutAdditionalContentUri() {
- fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true)
val model = createActivityModel(targetIntent = Intent(ACTION_SEND))
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -221,7 +193,6 @@ class ChooserRequestTest {
@Test
fun testRequest_actionViewWithAdditionalContentUri() {
- fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true)
val uri = Uri.parse("content://org.pkg/path")
val position = 10
val model =
@@ -230,7 +201,7 @@ class ChooserRequestTest {
intent.putExtra(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION, position)
}
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -248,7 +219,7 @@ class ChooserRequestTest {
Intent.CHOOSER_CONTENT_TYPE_ALBUM,
)
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -266,7 +237,7 @@ class ChooserRequestTest {
intent.putExtra(Intent.EXTRA_METADATA_TEXT, metadataText)
}
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<ChooserRequest>
@@ -285,7 +256,7 @@ class ChooserRequestTest {
}
val model = createActivityModel(targetIntent)
- val result = readChooserRequest(model, fakeChooserServiceFlags)
+ val result = readChooserRequest(model)
assertThat(result).isInstanceOf(Valid::class.java)
(result as Valid<ChooserRequest>).value.let { request ->
diff --git a/tests/unit/src/com/android/intentresolver/ui/viewmodel/IntentExtTest.kt b/tests/unit/src/com/android/intentresolver/ui/viewmodel/IntentExtTest.kt
new file mode 100644
index 00000000..8fc162ca
--- /dev/null
+++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/IntentExtTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.intentresolver.ui.viewmodel
+
+import android.content.Intent
+import android.content.Intent.ACTION_SEND
+import android.content.Intent.EXTRA_STREAM
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class IntentExtTest {
+
+ @Test
+ fun noActionOrUris() {
+ val intent = Intent()
+
+ assertThat(intent.createIntentFilter()).isNull()
+ }
+
+ @Test
+ fun uriInData() {
+ val intent = Intent(ACTION_SEND)
+ intent.setDataAndType(
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ "image/png",
+ )
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.actionsIterator().next()).isEqualTo(ACTION_SEND)
+ assertThat(filter.schemesIterator().next()).isEqualTo("scheme1")
+ assertThat(filter.authoritiesIterator().next().host).isEqualTo("auth1")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ }
+
+ @Test
+ fun noAction() {
+ val intent = Intent()
+ intent.setDataAndType(
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ "image/png",
+ )
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.countActions()).isEqualTo(0)
+ assertThat(filter.schemesIterator().next()).isEqualTo("scheme1")
+ assertThat(filter.authoritiesIterator().next().host).isEqualTo("auth1")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ }
+
+ @Test
+ fun singleUriInExtraStream() {
+ val intent = Intent(ACTION_SEND)
+ intent.type = "image/png"
+ intent.putExtra(
+ EXTRA_STREAM,
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ )
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.actionsIterator().next()).isEqualTo(ACTION_SEND)
+ assertThat(filter.schemesIterator().next()).isEqualTo("scheme1")
+ assertThat(filter.authoritiesIterator().next().host).isEqualTo("auth1")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ }
+
+ @Test
+ fun uriInDataAndStream() {
+ val intent = Intent(ACTION_SEND)
+ intent.setDataAndType(
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ "image/png",
+ )
+
+ intent.putExtra(
+ EXTRA_STREAM,
+ Uri.Builder().scheme("scheme2").encodedAuthority("auth2").path("path2").build(),
+ )
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.actionsIterator().next()).isEqualTo(ACTION_SEND)
+ assertThat(filter.getDataScheme(0)).isEqualTo("scheme1")
+ assertThat(filter.getDataScheme(1)).isEqualTo("scheme2")
+ assertThat(filter.getDataAuthority(0).host).isEqualTo("auth1")
+ assertThat(filter.getDataAuthority(1).host).isEqualTo("auth2")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ assertThat(filter.getDataPath(1).path).isEqualTo("/path2")
+ }
+
+ @Test
+ fun multipleUris() {
+ val intent = Intent(ACTION_SEND)
+ intent.type = "image/png"
+ val uris =
+ arrayListOf(
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ Uri.Builder().scheme("scheme2").encodedAuthority("auth2").path("path2").build(),
+ )
+ intent.putExtra(EXTRA_STREAM, uris)
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.actionsIterator().next()).isEqualTo(ACTION_SEND)
+ assertThat(filter.getDataScheme(0)).isEqualTo("scheme1")
+ assertThat(filter.getDataScheme(1)).isEqualTo("scheme2")
+ assertThat(filter.getDataAuthority(0).host).isEqualTo("auth1")
+ assertThat(filter.getDataAuthority(1).host).isEqualTo("auth2")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ assertThat(filter.getDataPath(1).path).isEqualTo("/path2")
+ }
+
+ @Test
+ fun multipleUrisWithNullValues() {
+ val intent = Intent(ACTION_SEND)
+ intent.type = "image/png"
+ val uris =
+ arrayListOf(
+ null,
+ Uri.Builder().scheme("scheme1").encodedAuthority("auth1").path("path1").build(),
+ null,
+ )
+ intent.putExtra(EXTRA_STREAM, uris)
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.dataTypes()[0]).isEqualTo("image/png")
+ assertThat(filter.actionsIterator().next()).isEqualTo(ACTION_SEND)
+ assertThat(filter.getDataScheme(0)).isEqualTo("scheme1")
+ assertThat(filter.getDataAuthority(0).host).isEqualTo("auth1")
+ assertThat(filter.getDataPath(0).path).isEqualTo("/path1")
+ }
+
+ @Test
+ fun badMimeType() {
+ val intent = Intent(ACTION_SEND)
+ intent.type = "badType"
+ intent.putExtra(
+ EXTRA_STREAM,
+ Uri.Builder().scheme("scheme1").encodedAuthority("authority1").path("path1").build(),
+ )
+
+ val filter = intent.createIntentFilter()
+
+ assertThat(filter).isNotNull()
+ assertThat(filter!!.countDataTypes()).isEqualTo(0)
+ }
+}