diff options
134 files changed, 1321 insertions, 421 deletions
@@ -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) + } +} |