From 53d920eccd9efeddae4a36c41ab31717cbc330ec Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Mon, 17 Jun 2024 17:32:10 -0700 Subject: Reclalculate drawer offset even when the targets list has not changed Shareousel has a variable-sized preview that affects the overall drawer offset. Therefore, we cannot early return from the offset calcualtion based solely on the condition that the targets list has not changed. Empirically, this introduces a minor overhead of the offset recalculation when the drawor is snapped to/from the top. Fix: 347316548 Test: manual testing with injected debug logging Flag: com.android.intentresolver.fix_missing_drawer_offset_calculation Change-Id: I69ec7a5e05fad0d6607e3614208b30910a3a17ba --- java/src/com/android/intentresolver/ChooserActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index a5516fde..0fa5e758 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2258,7 +2258,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (isLayoutUpdated || insetsChanged - || mLastNumberOfChildren != recyclerView.getChildCount()) { + || mLastNumberOfChildren != recyclerView.getChildCount() + || mFeatureFlags.fixMissingDrawerOffsetCalculation()) { mCurrAvailableWidth = availableWidth; if (isLayoutUpdated) { // It is very important we call setAdapter from here. Otherwise in some cases @@ -2277,7 +2278,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return; } - if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) { + if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged + && !mFeatureFlags.fixMissingDrawerOffsetCalculation()) { return; } -- cgit v1.2.3-59-g8ed1b From 1a31f34ea6a09e268deeba0bec970629b80d6a90 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 25 Jun 2024 17:28:30 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I9d6692a8d6a2c08b8f44b51e48ae19ce917c37b1 --- java/res/values-pt-rPT/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java') diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml index 52b62fe6..2e6d65e6 100644 --- a/java/res/values-pt-rPT/strings.xml +++ b/java/res/values-pt-rPT/strings.xml @@ -77,7 +77,7 @@ "Esta app não recebeu autorização de gravação, mas pode capturar áudio através deste dispositivo USB." "Pessoal" "Trabalho" - "Privada" + "Privado" "Vista pessoal" "Vista de trabalho" "Vista privada" -- cgit v1.2.3-59-g8ed1b From ee8e0bd6e2c21b6b1b28d3a81b73803c3b791342 Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Fri, 21 Jun 2024 17:45:41 +0000 Subject: Remove chooser_album_text flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It has launched. Bug: 323380224 Test: Test album text functionality. Flag:  android.service.chooser.chooser_album_text Change-Id: I5c03dce5b7d362f008f4ab834fb6631cecdee386 --- .../intentresolver/ui/viewmodel/ChooserRequestReader.kt | 13 ++++--------- .../domain/update/SelectionChangeCallbackImplTest.kt | 1 - .../intentresolver/ui/viewmodel/ChooserRequestTest.kt | 2 -- 3 files changed, 4 insertions(+), 12 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt index a9b6de7e..cb47c3de 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt @@ -95,8 +95,7 @@ fun readChooserRequest( val initialIntents = optional(array(EXTRA_INITIAL_INTENTS))?.take(MAX_INITIAL_INTENTS)?.map { it.maybeAddSendActionFlags() - } - ?: emptyList() + } ?: emptyList() val chosenComponentSender = optional(value(EXTRA_CHOOSER_RESULT_INTENT_SENDER)) @@ -132,13 +131,9 @@ fun readChooserRequest( } val contentTypeHint = - if (flags.chooserAlbumText()) { - when (optional(value(Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT))) { - Intent.CHOOSER_CONTENT_TYPE_ALBUM -> ContentTypeHint.ALBUM - else -> ContentTypeHint.NONE - } - } else { - ContentTypeHint.NONE + when (optional(value(Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT))) { + Intent.CHOOSER_CONTENT_TYPE_ALBUM -> ContentTypeHint.ALBUM + else -> ContentTypeHint.NONE } val metadataText = diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt index 91bbd151..9d36aa8f 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt @@ -69,7 +69,6 @@ class SelectionChangeCallbackImplTest { private val flags = FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) - setFlag(Flags.FLAG_CHOOSER_ALBUM_TEXT, false) setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, false) } 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 56c019fd..2b4a23e4 100644 --- a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt +++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt @@ -60,7 +60,6 @@ class ChooserRequestTest { private val fakeChooserServiceFlags = FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) - setFlag(Flags.FLAG_CHOOSER_ALBUM_TEXT, false) setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, false) } @@ -244,7 +243,6 @@ class ChooserRequestTest { @Test fun testAlbumType() { - fakeChooserServiceFlags.setFlag(Flags.FLAG_CHOOSER_ALBUM_TEXT, true) val model = createActivityModel(Intent(ACTION_SEND)) model.intent.putExtra( Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT, -- cgit v1.2.3-59-g8ed1b From bf864bdea2e89198f28c6f7e44ed0bde576bcb7d Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Wed, 26 Jun 2024 17:28:03 +0000 Subject: Remove enable_sharesheet_metadata_extra Test: atest ChooserRequestTest Test: atest SelectionChangeCallbackImplTest Bug: 318942069 Flag: android.service.chooser.enable_sharesheet_metadata_extra Change-Id: If7efc671a836723041905cb13467b71ac2cc39dd --- .../intentresolver/ChooserRequestParameters.java | 6 +---- .../domain/update/SelectionChangeCallback.kt | 8 ++----- .../ui/viewmodel/ChooserRequestReader.kt | 7 +----- .../update/SelectionChangeCallbackImplTest.kt | 28 +--------------------- .../ui/viewmodel/ChooserRequestTest.kt | 25 ++----------------- 5 files changed, 7 insertions(+), 67 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserRequestParameters.java b/java/src/com/android/intentresolver/ChooserRequestParameters.java index 06f56e3b..66bb66ef 100644 --- a/java/src/com/android/intentresolver/ChooserRequestParameters.java +++ b/java/src/com/android/intentresolver/ChooserRequestParameters.java @@ -159,11 +159,7 @@ public class ChooserRequestParameters { mChooserActions = getChooserActions(clientIntent); mModifyShareAction = getModifyShareAction(clientIntent); - if (android.service.chooser.Flags.enableSharesheetMetadataExtra()) { - mMetadataText = clientIntent.getCharSequenceExtra(Intent.EXTRA_METADATA_TEXT); - } else { - mMetadataText = null; - } + mMetadataText = clientIntent.getCharSequenceExtra(Intent.EXTRA_METADATA_TEXT); } public Intent getTargetIntent() { diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt index 1d34dc75..479bff77 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt @@ -136,12 +136,8 @@ private fun readCallbackResponse( optional(value(key)) } val metadataText = - if (flags.enableSharesheetMetadataExtra()) { - bundle.readValueUpdate(EXTRA_METADATA_TEXT) { key -> - optional(value(key)) - } - } else { - ValueUpdate.Absent + bundle.readValueUpdate(EXTRA_METADATA_TEXT) { key -> + optional(value(key)) } ShareouselUpdate( diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt index cb47c3de..f0f72489 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt @@ -136,12 +136,7 @@ fun readChooserRequest( else -> ContentTypeHint.NONE } - val metadataText = - if (flags.enableSharesheetMetadataExtra()) { - optional(value(EXTRA_METADATA_TEXT)) - } else { - null - } + val metadataText = optional(value(EXTRA_METADATA_TEXT)) ChooserRequest( targetIntent = targetIntent, diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt index 9d36aa8f..fd21ea3f 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt @@ -67,10 +67,7 @@ class SelectionChangeCallbackImplTest { private val contentResolver = mock() private val context = InstrumentationRegistry.getInstrumentation().context private val flags = - FakeChooserServiceFlags().apply { - setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) - setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, false) - } + FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) } @Test fun testPayloadChangeCallbackContact() = runTest { @@ -376,31 +373,9 @@ class SelectionChangeCallbackImplTest { assertThat(result.metadataText).isEqualTo(Absent) } - @Test - fun testPayloadChangeCallbackUpdatesMetadataTextWithDisabledFlag_noUpdates() = runTest { - val metadataText = "[Metadata]" - whenever(contentResolver.call(any(), any(), any(), any())) - .thenReturn(Bundle().apply { putCharSequence(EXTRA_METADATA_TEXT, metadataText) }) - - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) - - val targetIntent = Intent(ACTION_SEND) - val result = testSubject.onSelectionChanged(targetIntent) - assertWithMessage("Callback result should not be null").that(result).isNotNull() - requireNotNull(result) - assertThat(result.customActions).isEqualTo(Absent) - assertThat(result.modifyShareAction).isEqualTo(Absent) - assertThat(result.alternateIntents).isEqualTo(Absent) - assertThat(result.callerTargets).isEqualTo(Absent) - assertThat(result.refinementIntentSender).isEqualTo(Absent) - assertThat(result.resultIntentSender).isEqualTo(Absent) - assertThat(result.metadataText).isEqualTo(Absent) - } - @Test fun testPayloadChangeCallbackUpdatesMetadataTextWithEnabledFlag_valueUpdated() = runTest { val metadataText = "[Metadata]" - flags.setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, true) whenever(contentResolver.call(any(), any(), any(), any())) .thenReturn(Bundle().apply { putCharSequence(EXTRA_METADATA_TEXT, metadataText) }) @@ -421,7 +396,6 @@ class SelectionChangeCallbackImplTest { @Test fun testPayloadChangeCallbackProvidesInvalidData_invalidDataIgnored() = runTest { - flags.setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, true) whenever(contentResolver.call(any(), any(), any(), any())) .thenReturn( Bundle().apply { 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 2b4a23e4..2d5a44ed 100644 --- a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt +++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt @@ -58,10 +58,7 @@ private fun createActivityModel( class ChooserRequestTest { private val fakeChooserServiceFlags = - FakeChooserServiceFlags().apply { - setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) - setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, false) - } + FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) } @Test fun missingIntent() { @@ -259,26 +256,8 @@ class ChooserRequestTest { } @Test - fun metadataText_whenFlagFalse_isNull() { - fakeChooserServiceFlags.setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, false) - val metadataText: CharSequence = "Test metadata text" - val model = - createActivityModel(targetIntent = Intent()).apply { - intent.putExtra(Intent.EXTRA_METADATA_TEXT, metadataText) - } - - val result = readChooserRequest(model, fakeChooserServiceFlags) - - assertThat(result).isInstanceOf(Valid::class.java) - result as Valid - - assertThat(result.value.metadataText).isNull() - } - - @Test - fun metadataText_whenFlagTrue_isPassedText() { + fun metadataText_isPassedText() { // Arrange - fakeChooserServiceFlags.setFlag(Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA, true) val metadataText: CharSequence = "Test metadata text" val model = createActivityModel(targetIntent = Intent()).apply { -- cgit v1.2.3-59-g8ed1b From cdb3bf20eaf3d970f166497cae4fa27b90ca94d8 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 8 Jul 2024 01:56:10 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: If36f929d8388ca1ef961ae4aa44d2d625e801665 --- java/res/values-bs/strings.xml | 2 +- java/res/values-fa/strings.xml | 4 ++-- java/res/values-fr-rCA/strings.xml | 26 +++++++++++++------------- java/res/values-pt-rPT/strings.xml | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) (limited to 'java') diff --git a/java/res/values-bs/strings.xml b/java/res/values-bs/strings.xml index ec642454..97d3e7cf 100644 --- a/java/res/values-bs/strings.xml +++ b/java/res/values-bs/strings.xml @@ -57,7 +57,7 @@ "{count,plural, =1{i još # fajl}one{i još # fajl}few{i još # fajla}other{i još # fajlova}}" "Dijeljenje teksta" "Dijeljenje linka" - "{count,plural, =1{Dijeli se slika}one{Dijeli se # slika}few{Dijele se # slike}other{Dijeli se # slika}}" + "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeljenje videozapisa}one{Dijeljenje # videozapisa}few{Dijeljenje # videozapisa}other{Dijeljenje # videozapisa}}" "{count,plural, =1{Dijeljenje # fajla}one{Dijeljenje # fajla}few{Dijeljenje # fajla}other{Dijeljenje # fajlova}}" "{count,plural, =1{Dijeljenje slike putem poruke}one{Dijeljenje # slike putem poruke}few{Dijeljenje # slike putem poruke}other{Dijeljenje # slika putem poruke}}" diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index 735248d1..0119fe69 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -98,9 +98,9 @@ "استفاده از مرورگر شخصی" "استفاده از مرورگر کاری" "مستثنی کردن نوشتار" - "لحاظ کردن نوشتار" + "گنجاندن نوشتار" "مستثنی کردن پیوند" - "لحاظ کردن پیوند" + "گنجاندن پیوند" "سنجاق‌شده" "تصویر قابل‌انتخاب" "ویدیو قابل‌انتخاب" diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml index 41b79692..7d2716c2 100644 --- a/java/res/values-fr-rCA/strings.xml +++ b/java/res/values-fr-rCA/strings.xml @@ -36,17 +36,17 @@ "Envoyer avec" "Envoyer avec %1$s" "Envoyer" - "Sélectionner une application pour l\'écran d\'accueil" - "Utiliser %1$s comme application sur la page d\'accueil" + "Sélectionner une appli pour l\'écran d\'accueil" + "Utiliser %1$s comme appli sur la page d\'accueil" "Enregistrer l\'image" "Enregistrer l\'image avec" "Enregistrer l\'image avec %1$s" "Enregistrer l\'image" "Utiliser une appli différente" "Sélectionner une action" - "Aucune application ne peut effectuer cette action." - "Vous utilisez cette application en dehors de votre profil professionnel" - "Vous utilisez cette application dans votre profil professionnel" + "Aucune appli ne peut effectuer cette action." + "Vous utilisez cette appli en dehors de votre profil professionnel" + "Vous utilisez cette appli dans votre profil professionnel" "Toujours" "Une seule fois" "%1$s ne prend pas en charge le profil professionnel" @@ -74,7 +74,7 @@ "Miniature d\'aperçu de la vidéo" "Miniature d\'aperçu du fichier" "Aucune recommandation de personnes avec lesquelles effectuer un partage" - "Cette application n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait capturer du contenu audio par l\'intermédiaire de cet appareil USB." + "Cette appli n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait capturer du contenu audio par l\'intermédiaire de cet appareil USB." "Personnel" "Professionnel" "Privé" @@ -82,16 +82,16 @@ "Affichage professionnel" "Affichage privé" "Bloqué par votre administrateur informatique" - "Impossible de partager ce contenu avec des applications professionnelles" - "Impossible d\'ouvrir ce contenu avec des applications professionnelles" - "Impossible de partager ce contenu avec des applications personnelles" - "Impossible d\'ouvrir ce contenu avec des applications personnelles" + "Impossible de partager ce contenu avec des applis professionnelles" + "Impossible d\'ouvrir ce contenu avec des applis professionnelles" + "Impossible de partager ce contenu avec des applis personnelles" + "Impossible d\'ouvrir ce contenu avec des applis personnelles" "Impossible de partager ce contenu avec des applis privées" "Impossible d\'ouvrir ce contenu avec des applis privées" - "Les applications professionnelles sont interrompues" + "Les applis professionnelles sont interrompues" "Réactiver" - "Aucune application professionnelle" - "Aucune application personnelle" + "Aucune appli professionnelle" + "Aucune appli personnelle" "Aucune appli privée" "Ouvrir %s dans votre profil personnel?" "Ouvrir %s dans votre profil professionnel?" diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml index 2e6d65e6..0abb79be 100644 --- a/java/res/values-pt-rPT/strings.xml +++ b/java/res/values-pt-rPT/strings.xml @@ -73,7 +73,7 @@ "Miniatura de pré-visualização da imagem" "Miniatura de pré-visualização do vídeo" "Miniatura de pré-visualização do ficheiro" - "Não existem pessoas recomendadas com quem partilhar" + "Sem pessoas recomendadas com quem partilhar" "Esta app não recebeu autorização de gravação, mas pode capturar áudio através deste dispositivo USB." "Pessoal" "Trabalho" -- cgit v1.2.3-59-g8ed1b From 864d466559a5e576bbf02ba96b2850a8a30291e8 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Thu, 20 Jun 2024 16:30:13 -0700 Subject: Delete PreviewViewModel Merge PreviewViewModel functionality into ChooserViewModel. Make ImageLoader injectable; remove obsolete code. Bug: 348665058 Test: atest IntentResolver-tests-activity Test: atest IntentResolver-tests-unit Flag: EXEMPT refactor Change-Id: Icb18362763c19e97ac39cfa8112c9d0357f24e1e --- .../android/intentresolver/ChooserActivity.java | 18 +--- .../contentpreview/BasePreviewViewModel.kt | 35 -------- .../contentpreview/ImageLoaderModule.kt | 13 +-- .../contentpreview/PreviewDataProvider.kt | 13 +-- .../contentpreview/PreviewViewModel.kt | 98 ---------------------- .../ui/viewmodel/ChooserViewModel.kt | 17 ++++ .../ChooserActivityOverrideData.java | 3 - .../intentresolver/ChooserActivityTest.java | 3 - .../intentresolver/ChooserWrapperActivity.java | 9 -- .../intentresolver/TestContentPreviewViewModel.kt | 64 -------------- .../contentpreview/PreviewDataProviderTest.kt | 15 ++-- 11 files changed, 37 insertions(+), 251 deletions(-) delete mode 100644 java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt delete mode 100644 java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt delete mode 100644 tests/shared/src/com/android/intentresolver/TestContentPreviewViewModel.kt (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 0fa5e758..4d70f550 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -96,10 +96,8 @@ import com.android.intentresolver.ChooserRefinementManager.RefinementType; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.contentpreview.BasePreviewViewModel; import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl; -import com.android.intentresolver.contentpreview.PreviewViewModel; import com.android.intentresolver.data.model.ChooserRequest; import com.android.intentresolver.data.repository.DevicePolicyResources; import com.android.intentresolver.domain.interactor.UserInteractor; @@ -633,21 +631,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements finish(); } }); - BasePreviewViewModel previewViewModel = - new ViewModelProvider(this, createPreviewViewModelFactory()) - .get(BasePreviewViewModel.class); - previewViewModel.init( - mRequest.getTargetIntent(), - mRequest.getAdditionalContentUri(), - mChooserServiceFeatureFlags.chooserPayloadToggling()); ChooserContentPreviewUi.ActionFactory actionFactory = decorateActionFactoryWithRefinement( createChooserActionFactory(mRequest.getTargetIntent())); mChooserContentPreviewUi = new ChooserContentPreviewUi( getCoroutineScope(getLifecycle()), - previewViewModel.getPreviewDataProvider(), + mViewModel.getPreviewDataProvider(), mRequest.getTargetIntent(), - previewViewModel.getImageLoader(), + mViewModel.getImageLoader(), actionFactory, createModifyShareActionFactory(), mEnterTransitionAnimationDelegate, @@ -2108,11 +2099,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mPinnedSharedPrefs); } - @VisibleForTesting - protected ViewModelProvider.Factory createPreviewViewModelFactory() { - return PreviewViewModel.Companion.getFactory(); - } - private ChooserContentPreviewUi.ActionFactory decorateActionFactoryWithRefinement( ChooserContentPreviewUi.ActionFactory originalFactory) { if (!mFeatureFlags.refineSystemActions()) { diff --git a/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt deleted file mode 100644 index dc36e584..00000000 --- a/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 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.contentpreview - -import android.content.Intent -import android.net.Uri -import androidx.annotation.MainThread -import androidx.lifecycle.ViewModel - -/** A contract for the preview view model. Added for testing. */ -abstract class BasePreviewViewModel : ViewModel() { - @get:MainThread abstract val previewDataProvider: PreviewDataProvider - @get:MainThread abstract val imageLoader: ImageLoader - - @MainThread - abstract fun init( - targetIntent: Intent, - additionalContentUri: Uri?, - isPayloadTogglingEnabled: Boolean, - ) -} diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt index 7035f765..17d05099 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt @@ -23,19 +23,14 @@ import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityRetainedComponent -import dagger.hilt.android.scopes.ActivityRetainedScoped +import dagger.hilt.android.components.ViewModelComponent @Module -@InstallIn(ActivityRetainedComponent::class) +@InstallIn(ViewModelComponent::class) interface ImageLoaderModule { - @Binds - @ActivityRetainedScoped - fun imageLoader(previewImageLoader: ImagePreviewImageLoader): ImageLoader + @Binds fun imageLoader(previewImageLoader: ImagePreviewImageLoader): ImageLoader - @Binds - @ActivityRetainedScoped - fun thumbnailLoader(thumbnailLoader: ThumbnailLoaderImpl): ThumbnailLoader + @Binds fun thumbnailLoader(thumbnailLoader: ThumbnailLoaderImpl): ThumbnailLoader companion object { @Provides diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt index 96bb8258..9b2dbebf 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt @@ -32,6 +32,7 @@ import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREV import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_TEXT +import com.android.intentresolver.inject.ChooserServiceFlags import com.android.intentresolver.measurements.runTracing import com.android.intentresolver.util.ownedByCurrentUser import java.util.concurrent.atomic.AtomicInteger @@ -76,9 +77,7 @@ constructor( private val targetIntent: Intent, private val additionalContentUri: Uri?, private val contentResolver: ContentInterface, - // TODO: replace with the ChooserServiceFlags ref when PreviewViewModel dependencies are sorted - // out - private val isPayloadTogglingEnabled: Boolean, + private val featureFlags: ChooserServiceFlags, private val typeClassifier: MimeTypeClassifier = DefaultMimeTypeClassifier, ) { @@ -129,7 +128,7 @@ constructor( * IMAGE, FILE, TEXT. */ if (!targetIntent.isSend || records.isEmpty()) { CONTENT_PREVIEW_TEXT - } else if (isPayloadTogglingEnabled && shouldShowPayloadSelection()) { + } else if (featureFlags.chooserPayloadToggling() && shouldShowPayloadSelection()) { // TODO: replace with the proper flags injection CONTENT_PREVIEW_PAYLOAD_SELECTION } else { @@ -275,13 +274,16 @@ constructor( val mimeType: String? by lazy { contentResolver.getTypeSafe(uri) } val isImageType: Boolean get() = typeClassifier.isImageType(mimeType) + val supportsImageType: Boolean by lazy { contentResolver.getStreamTypesSafe(uri).firstOrNull(typeClassifier::isImageType) != null } val supportsThumbnail: Boolean get() = query.supportsThumbnail + val title: String get() = query.title + val iconUri: Uri? get() = query.iconUri @@ -326,8 +328,7 @@ constructor( } QueryResult(supportsThumbnail, title, iconUri) - } - ?: QueryResult() + } ?: QueryResult() } private class QueryResult( diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt deleted file mode 100644 index 6a729945..00000000 --- a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2023 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.contentpreview - -import android.app.Application -import android.content.ContentResolver -import android.content.Intent -import android.net.Uri -import androidx.annotation.MainThread -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY -import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.CreationExtras -import com.android.intentresolver.R -import com.android.intentresolver.inject.Background -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.plus - -/** A view model for the preview logic */ -class PreviewViewModel( - private val contentResolver: ContentResolver, - // TODO: inject ImageLoader instead - private val thumbnailSize: Int, - @Background private val dispatcher: CoroutineDispatcher = Dispatchers.IO, -) : BasePreviewViewModel() { - private var targetIntent: Intent? = null - private var additionalContentUri: Uri? = null - private var isPayloadTogglingEnabled = false - - override val previewDataProvider by lazy { - val targetIntent = requireNotNull(this.targetIntent) { "Not initialized" } - PreviewDataProvider( - viewModelScope + dispatcher, - targetIntent, - additionalContentUri, - contentResolver, - isPayloadTogglingEnabled, - ) - } - - override val imageLoader by lazy { - ImagePreviewImageLoader( - viewModelScope + dispatcher, - thumbnailSize, - contentResolver, - cacheSize = 16 - ) - } - - // TODO: make the view model injectable and inject these dependencies instead - @MainThread - override fun init( - targetIntent: Intent, - additionalContentUri: Uri?, - isPayloadTogglingEnabled: Boolean, - ) { - if (this.targetIntent != null) return - this.targetIntent = targetIntent - this.additionalContentUri = additionalContentUri - this.isPayloadTogglingEnabled = isPayloadTogglingEnabled - } - - companion object { - val Factory: ViewModelProvider.Factory = - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: Class, - extras: CreationExtras - ): T { - val application: Application = checkNotNull(extras[APPLICATION_KEY]) - return PreviewViewModel( - application.contentResolver, - application.resources.getDimensionPixelSize( - R.dimen.chooser_preview_image_max_dimen - ) - ) - as T - } - } - } -} diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt index c9cae3db..619e118a 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt @@ -15,10 +15,13 @@ */ package com.android.intentresolver.ui.viewmodel +import android.content.ContentInterface import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.android.intentresolver.contentpreview.ImageLoader +import com.android.intentresolver.contentpreview.PreviewDataProvider import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.FetchPreviewsInteractor import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.ProcessTargetIntentUpdatesInteractor import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel @@ -38,6 +41,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.plus private const val TAG = "ChooserViewModel" @@ -58,6 +62,8 @@ constructor( */ val initialRequest: ValidationResult, private val chooserRequestRepository: Lazy, + private val contentResolver: ContentInterface, + val imageLoader: ImageLoader, ) : ViewModel() { /** Parcelable-only references provided from the creating Activity */ @@ -86,6 +92,17 @@ constructor( val request: StateFlow get() = chooserRequestRepository.get().chooserRequest.asStateFlow() + val previewDataProvider by lazy { + val chooserRequest = (initialRequest as Valid).value + PreviewDataProvider( + viewModelScope + bgDispatcher, + chooserRequest.targetIntent, + chooserRequest.additionalContentUri, + contentResolver, + flags, + ) + } + init { if (initialRequest is Invalid) { Log.w(TAG, "initialRequest is Invalid, initialization failed") diff --git a/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java b/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java index 507ce3d7..311201cf 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java +++ b/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java @@ -26,7 +26,6 @@ import android.database.Cursor; import android.os.UserHandle; import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; import com.android.intentresolver.shortcuts.ShortcutLoader; @@ -58,7 +57,6 @@ public class ChooserActivityOverrideData { public Boolean isVoiceInteraction; public Cursor resolverCursor; public boolean resolverForceException; - public ImageLoader imageLoader; public Resources resources; public boolean hasCrossProfileIntents; public boolean isQuietModeEnabled; @@ -68,7 +66,6 @@ public class ChooserActivityOverrideData { public void reset() { onSafelyStartInternalCallback = null; isVoiceInteraction = null; - imageLoader = null; resolverCursor = null; resolverForceException = false; resolverListController = mock(ChooserListController.class); diff --git a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java index a8b8b2e9..620ac555 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java +++ b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java @@ -305,9 +305,6 @@ public class ChooserActivityTest { // values to the dependency graph at activity launch time. This allows replacing // arbitrary bindings per-test case if needed. mPackageManager = mContext.getPackageManager(); - - // TODO: inject image loader in the prod code and remove this override - ChooserActivityOverrideData.getInstance().imageLoader = mFakeImageLoader; } public ChooserActivityTest(boolean appPredictionAvailable) { diff --git a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java index 4b71aa29..8a8fe355 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -30,8 +30,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; -import androidx.lifecycle.ViewModelProvider; - import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; @@ -151,13 +149,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW return super.getResources(); } - @Override - protected ViewModelProvider.Factory createPreviewViewModelFactory() { - return TestContentPreviewViewModel.Companion.wrap( - super.createPreviewViewModelFactory(), - sOverrides.imageLoader); - } - @Override public Cursor queryResolver(ContentResolver resolver, Uri uri) { if (sOverrides.resolverCursor != null) { diff --git a/tests/shared/src/com/android/intentresolver/TestContentPreviewViewModel.kt b/tests/shared/src/com/android/intentresolver/TestContentPreviewViewModel.kt deleted file mode 100644 index 8f246424..00000000 --- a/tests/shared/src/com/android/intentresolver/TestContentPreviewViewModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2023 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 - -import android.content.Intent -import android.net.Uri -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.CreationExtras -import com.android.intentresolver.contentpreview.BasePreviewViewModel -import com.android.intentresolver.contentpreview.ImageLoader - -/** A test content preview model that supports image loader override. */ -class TestContentPreviewViewModel( - private val viewModel: BasePreviewViewModel, - override val imageLoader: ImageLoader, -) : BasePreviewViewModel() { - - override val previewDataProvider - get() = viewModel.previewDataProvider - - override fun init( - targetIntent: Intent, - additionalContentUri: Uri?, - isPayloadTogglingEnabled: Boolean, - ) { - viewModel.init(targetIntent, additionalContentUri, isPayloadTogglingEnabled) - } - - companion object { - fun wrap( - factory: ViewModelProvider.Factory, - imageLoader: ImageLoader?, - ): ViewModelProvider.Factory = - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: Class, - extras: CreationExtras - ): T { - val wrapped = factory.create(modelClass, extras) as BasePreviewViewModel - return TestContentPreviewViewModel( - wrapped, - imageLoader ?: wrapped.imageLoader, - ) - as T - } - } - } -} diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt index a2fb9693..370ee044 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt @@ -21,9 +21,9 @@ import android.content.Intent import android.database.MatrixCursor import android.media.MediaMetadata import android.net.Uri -import android.platform.test.flag.junit.CheckFlagsRule -import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.DocumentsContract +import android.service.chooser.FakeFeatureFlagsImpl +import android.service.chooser.Flags import com.google.common.truth.Truth.assertThat import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineScope @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -46,7 +45,8 @@ class PreviewDataProviderTest { private val contentResolver = mock() private val mimeTypeClassifier = DefaultMimeTypeClassifier private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private val featureFlags = + FakeFeatureFlagsImpl().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) } private fun createDataProvider( targetIntent: Intent, @@ -54,14 +54,13 @@ class PreviewDataProviderTest { additionalContentUri: Uri? = null, resolver: ContentInterface = contentResolver, typeClassifier: MimeTypeClassifier = mimeTypeClassifier, - isPayloadTogglingEnabled: Boolean = false ) = PreviewDataProvider( scope, targetIntent, additionalContentUri, resolver, - isPayloadTogglingEnabled, + featureFlags, typeClassifier, ) @@ -377,11 +376,11 @@ class PreviewDataProviderTest { 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") + featureFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true) val testSubject = createDataProvider( targetIntent, additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"), - isPayloadTogglingEnabled = true, ) assertThat(testSubject.previewType) @@ -415,11 +414,11 @@ class PreviewDataProviderTest { 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") + featureFlags.setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, true) val testSubject = createDataProvider( targetIntent, additionalContentUri = Uri.parse("content://org.pkg.app/extracontent"), - isPayloadTogglingEnabled = true, ) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) -- cgit v1.2.3-59-g8ed1b From a70571727f7e0a4f88c43f087787b6dbdfacbf3a Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Wed, 26 Jun 2024 11:36:47 -0700 Subject: Add default implementation of the loadImage method to ImageLoader Bug: 348665058 Test: atest IntentResolver-tests-unit Test: atest IntentResolver-tests-activity Flag: EXEMPT refactor Change-Id: Ie72638cf841f7402aec0df93907a81cb5825d076 --- .../contentpreview/CachingImagePreviewImageLoader.kt | 6 ------ .../com/android/intentresolver/contentpreview/ImageLoader.kt | 11 ++++++++++- .../intentresolver/contentpreview/ImagePreviewImageLoader.kt | 11 ----------- 3 files changed, 10 insertions(+), 18 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt index 2e2aa938..c9deec1b 100644 --- a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt @@ -22,7 +22,6 @@ import android.util.Log import androidx.core.util.lruCache import com.android.intentresolver.inject.Background import com.android.intentresolver.inject.ViewModelOwned -import java.util.function.Consumer import javax.inject.Inject import javax.inject.Qualifier import kotlinx.coroutines.CoroutineDispatcher @@ -31,7 +30,6 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext @@ -74,10 +72,6 @@ constructor( } ) - override fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) { - callerScope.launch { callback.accept(loadCachedImage(uri)) } - } - override fun prePopulate(uris: List) { uris.take(cache.maxSize()).map { cache[it] } } diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt index 81913a8e..b4d03ac9 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt @@ -20,6 +20,8 @@ import android.graphics.Bitmap import android.net.Uri import java.util.function.Consumer import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch /** A content preview image loader. */ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitmap? { @@ -30,7 +32,14 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm * @param callback a callback that will be invoked with the loaded image or null if loading has * failed. */ - fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) + fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) { + callerScope.launch { + val bitmap = invoke(uri) + if (isActive) { + callback.accept(bitmap) + } + } + } /** Prepopulate the image loader cache. */ fun prePopulate(uris: List) diff --git a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt index fab7203e..7cf9a8c9 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt @@ -25,7 +25,6 @@ import androidx.annotation.GuardedBy import androidx.annotation.VisibleForTesting import androidx.collection.LruCache import com.android.intentresolver.inject.Background -import java.util.function.Consumer import javax.inject.Inject import javax.inject.Qualifier import kotlinx.coroutines.CancellationException @@ -36,7 +35,6 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore @@ -102,15 +100,6 @@ constructor( override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = loadImageAsync(uri, caching) - override fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) { - callerScope.launch { - val image = loadImageAsync(uri, caching = true) - if (isActive) { - callback.accept(image) - } - } - } - override fun prePopulate(uris: List) { uris.asSequence().take(cache.maxSize()).forEach { uri -> scope.launch { loadImageAsync(uri, caching = true) } -- cgit v1.2.3-59-g8ed1b From 8cd6138cca567e14e2cdfa132a8686c4eb324d52 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Fri, 7 Jun 2024 14:33:28 -0700 Subject: Delete unused class ChooserRequestParameters Bug: N/A Test: presubmits Flag: EXEMPT refactor Change-Id: I84573929396492bfe10a2f835783c9edd58f8c5c --- .../intentresolver/ChooserRequestParameters.java | 500 --------------------- 1 file changed, 500 deletions(-) delete mode 100644 java/src/com/android/intentresolver/ChooserRequestParameters.java (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserRequestParameters.java b/java/src/com/android/intentresolver/ChooserRequestParameters.java deleted file mode 100644 index 66bb66ef..00000000 --- a/java/src/com/android/intentresolver/ChooserRequestParameters.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2008 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; - - -import android.content.ComponentName; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.IntentSender; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.os.PatternMatcher; -import android.service.chooser.ChooserAction; -import android.service.chooser.ChooserTarget; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.intentresolver.util.UriFilters; - -import com.google.common.collect.ImmutableList; - -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Utility to parse and validate parameters from the client-supplied {@link Intent} that launched - * the Sharesheet {@link ChooserActivity}. The validated parameters are stored as immutable ivars. - * - * TODO: field nullability in this class reflects legacy use, and typically would indicate that the - * client's intent didn't provide the respective data. In some cases we may be able to provide - * defaults instead of nulls -- especially for methods that return nullable lists or arrays, if the - * client code could instead handle empty collections equally well. - * - * TODO: some of these fields (especially getTargetIntent() and any other getters that delegate to - * it internally) differ from the legacy model because they're computed directly from the initial - * Chooser intent, where in the past they've been relayed up to ResolverActivity and then retrieved - * through methods on the base class. The base always seems to return them exactly as they were - * provided, so this should be safe -- and clients can reasonably switch to retrieving through these - * parameters instead. For now, the other convention is still used in some places. Ideally we'd like - * to normalize on a single source of truth, but we'll have to clean up the delegation up to the - * resolver (or perhaps this needs to be a subclass of some `ResolverRequestParameters` class?). - */ -public class ChooserRequestParameters { - private static final String TAG = "ChooserActivity"; - - private static final int LAUNCH_FLAGS_FOR_SEND_ACTION = - Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; - private static final int MAX_CHOOSER_ACTIONS = 5; - - private final Intent mTarget; - private final String mReferrerPackageName; - private final Pair mTitleSpec; - private final Intent mReferrerFillInIntent; - private final ImmutableList mFilteredComponentNames; - private final ImmutableList mCallerChooserTargets; - private final @NonNull ImmutableList mChooserActions; - private final ChooserAction mModifyShareAction; - private final boolean mRetainInOnStop; - - @Nullable - private final ImmutableList mAdditionalTargets; - - @Nullable - private final Bundle mReplacementExtras; - - @Nullable - private final ImmutableList mInitialIntents; - - @Nullable - private final IntentSender mChosenComponentSender; - - @Nullable - private final IntentSender mRefinementIntentSender; - - @Nullable - private final String mSharedText; - - @Nullable - private final IntentFilter mTargetIntentFilter; - - @Nullable - private final CharSequence mMetadataText; - - public ChooserRequestParameters( - final Intent clientIntent, - String referrerPackageName, - final Uri referrer) { - final Intent requestedTarget = parseTargetIntentExtra( - clientIntent.getParcelableExtra(Intent.EXTRA_INTENT)); - mTarget = intentWithModifiedLaunchFlags(requestedTarget); - - mReferrerPackageName = referrerPackageName; - - mAdditionalTargets = intentsWithModifiedLaunchFlagsFromExtraIfPresent( - clientIntent, Intent.EXTRA_ALTERNATE_INTENTS); - - mReplacementExtras = clientIntent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); - - mTitleSpec = makeTitleSpec( - clientIntent.getCharSequenceExtra(Intent.EXTRA_TITLE), - isSendAction(mTarget.getAction())); - - mInitialIntents = intentsWithModifiedLaunchFlagsFromExtraIfPresent( - clientIntent, Intent.EXTRA_INITIAL_INTENTS); - - mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, referrer); - - mChosenComponentSender = - Optional.ofNullable( - clientIntent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, - IntentSender.class)) - .orElse(clientIntent.getParcelableExtra( - Intent.EXTRA_CHOOSER_RESULT_INTENT_SENDER, - IntentSender.class)); - - mRefinementIntentSender = clientIntent.getParcelableExtra( - Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); - - ComponentName[] filteredComponents = clientIntent.getParcelableArrayExtra( - Intent.EXTRA_EXCLUDE_COMPONENTS, ComponentName.class); - mFilteredComponentNames = filteredComponents != null - ? ImmutableList.copyOf(filteredComponents) - : ImmutableList.of(); - - mCallerChooserTargets = parseCallerTargetsFromClientIntent(clientIntent); - - mRetainInOnStop = clientIntent.getBooleanExtra( - ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false); - - mSharedText = mTarget.getStringExtra(Intent.EXTRA_TEXT); - - mTargetIntentFilter = getTargetIntentFilter(mTarget); - - mChooserActions = getChooserActions(clientIntent); - mModifyShareAction = getModifyShareAction(clientIntent); - - mMetadataText = clientIntent.getCharSequenceExtra(Intent.EXTRA_METADATA_TEXT); - } - - public Intent getTargetIntent() { - return mTarget; - } - - @Nullable - public String getTargetAction() { - return getTargetIntent().getAction(); - } - - public boolean isSendActionTarget() { - return isSendAction(getTargetAction()); - } - - @Nullable - public String getTargetType() { - return getTargetIntent().getType(); - } - - public String getReferrerPackageName() { - return mReferrerPackageName; - } - - @Nullable - public CharSequence getTitle() { - return mTitleSpec.first; - } - - public int getDefaultTitleResource() { - return mTitleSpec.second; - } - - public Intent getReferrerFillInIntent() { - return mReferrerFillInIntent; - } - - public ImmutableList getFilteredComponentNames() { - return mFilteredComponentNames; - } - - public ImmutableList getCallerChooserTargets() { - return mCallerChooserTargets; - } - - @NonNull - public ImmutableList getChooserActions() { - return mChooserActions; - } - - @Nullable - public ChooserAction getModifyShareAction() { - return mModifyShareAction; - } - - /** - * Whether the {@link ChooserActivity#EXTRA_PRIVATE_RETAIN_IN_ON_STOP} behavior was requested. - */ - public boolean shouldRetainInOnStop() { - return mRetainInOnStop; - } - - /** - * TODO: this returns a nullable array for convenience, but if the legacy APIs can be - * refactored, returning {@link #mAdditionalTargets} directly is simpler and safer. - */ - @Nullable - public Intent[] getAdditionalTargets() { - return (mAdditionalTargets == null) ? null : mAdditionalTargets.toArray(new Intent[0]); - } - - @Nullable - public Bundle getReplacementExtras() { - return mReplacementExtras; - } - - /** - * TODO: this returns a nullable array for convenience, but if the legacy APIs can be - * refactored, returning {@link #mInitialIntents} directly is simpler and safer. - */ - @Nullable - public Intent[] getInitialIntents() { - return (mInitialIntents == null) ? null : mInitialIntents.toArray(new Intent[0]); - } - - @Nullable - public IntentSender getChosenComponentSender() { - return mChosenComponentSender; - } - - @Nullable - public IntentSender getRefinementIntentSender() { - return mRefinementIntentSender; - } - - @Nullable - public String getSharedText() { - return mSharedText; - } - - @Nullable - public IntentFilter getTargetIntentFilter() { - return mTargetIntentFilter; - } - - @Nullable - public CharSequence getMetadataText() { - return mMetadataText; - } - - private static boolean isSendAction(@Nullable String action) { - return (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)); - } - - private static Intent parseTargetIntentExtra(@Nullable Parcelable targetParcelable) { - if (targetParcelable instanceof Uri) { - try { - targetParcelable = Intent.parseUri(targetParcelable.toString(), - Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException("Failed to parse EXTRA_INTENT from URI", ex); - } - } - - if (!(targetParcelable instanceof Intent)) { - throw new IllegalArgumentException( - "EXTRA_INTENT is neither an Intent nor a Uri: " + targetParcelable); - } - - return ((Intent) targetParcelable); - } - - private static Intent intentWithModifiedLaunchFlags(Intent intent) { - if (isSendAction(intent.getAction())) { - intent.addFlags(LAUNCH_FLAGS_FOR_SEND_ACTION); - } - return intent; - } - - /** - * Build a pair of values specifying the title to use from the client request. The first - * ({@link CharSequence}) value is the client-specified title, if there was one and their - * requested target wasn't a send action; otherwise it is null. The second value is - * the resource ID of a default title string; this is nonzero only if the first value is null. - * - * TODO: change the API for how these are passed up to {@link ResolverActivity#onCreate}, or - * create a real type (not {@link Pair}) to express the semantics described in this comment. - */ - private static Pair makeTitleSpec( - @Nullable CharSequence requestedTitle, boolean hasSendActionTarget) { - if (hasSendActionTarget && (requestedTitle != null)) { - // Do not allow the title to be changed when sharing content - Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a" - + " preview title by using EXTRA_TITLE property of the wrapped" - + " EXTRA_INTENT."); - requestedTitle = null; - } - - int defaultTitleRes = (requestedTitle == null) ? R.string.chooseActivity : 0; - - return Pair.create(requestedTitle, defaultTitleRes); - } - - private static ImmutableList parseCallerTargetsFromClientIntent( - Intent clientIntent) { - return - streamParcelableArrayExtra( - clientIntent, Intent.EXTRA_CHOOSER_TARGETS, ChooserTarget.class, true, true) - .collect(toImmutableList()); - } - - @NonNull - private static ImmutableList getChooserActions(Intent intent) { - return streamParcelableArrayExtra( - intent, - Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, - ChooserAction.class, - true, - true) - .filter(UriFilters::hasValidIcon) - .limit(MAX_CHOOSER_ACTIONS) - .collect(toImmutableList()); - } - - @Nullable - private static ChooserAction getModifyShareAction(Intent intent) { - try { - return intent.getParcelableExtra( - Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION, - ChooserAction.class); - } catch (Throwable t) { - Log.w( - TAG, - "Unable to retrieve Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION argument", - t); - return null; - } - } - - private static Collector> toImmutableList() { - return Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf); - } - - @Nullable - private static ImmutableList intentsWithModifiedLaunchFlagsFromExtraIfPresent( - Intent clientIntent, String extra) { - Stream intents = - streamParcelableArrayExtra(clientIntent, extra, Intent.class, true, false); - if (intents == null) { - return null; - } - return intents - .map(ChooserRequestParameters::intentWithModifiedLaunchFlags) - .collect(toImmutableList()); - } - - /** - * Make a {@link Stream} of the {@link Parcelable} objects given in the provided {@link Intent} - * as the optional parcelable array extra with key {@code extra}. The stream elements, if any, - * are all of the type specified by {@code clazz}. - * - * @param intent The intent that may contain the optional extras. - * @param extra The extras key to identify the parcelable array. - * @param clazz A class that is assignable from any elements in the result stream. - * @param warnOnTypeError Whether to log a warning (and ignore) if the client extra doesn't have - * the required type. If false, throw an {@link IllegalArgumentException} if the extra is - * non-null but can't be assigned to variables of type {@code T}. - * @param streamEmptyIfNull Whether to return an empty stream if the optional extra isn't - * present in the intent (or if it had the wrong type, but warnOnTypeError is true). - * If false, return null in these cases, and only return an empty stream if the intent - * explicitly provided an empty array for the specified extra. - */ - @Nullable - private static Stream streamParcelableArrayExtra( - final Intent intent, - String extra, - @NonNull Class clazz, - boolean warnOnTypeError, - boolean streamEmptyIfNull) { - T[] result = null; - - try { - result = getParcelableArrayExtraIfPresent(intent, extra, clazz); - } catch (IllegalArgumentException e) { - if (warnOnTypeError) { - Log.w(TAG, "Ignoring client-requested " + extra, e); - } else { - throw e; - } - } - - if (result != null) { - return Arrays.stream(result); - } else if (streamEmptyIfNull) { - return Stream.empty(); - } else { - return null; - } - } - - /** - * If the specified {@code extra} is provided in the {@code intent}, cast it to type {@code T[]} - * or throw an {@code IllegalArgumentException} if the cast fails. If the {@code extra} isn't - * present in the {@code intent}, return null. - */ - @Nullable - private static T[] getParcelableArrayExtraIfPresent( - final Intent intent, String extra, @NonNull Class clazz) throws - IllegalArgumentException { - if (!intent.hasExtra(extra)) { - return null; - } - - T[] castResult = intent.getParcelableArrayExtra(extra, clazz); - if (castResult == null) { - Parcelable[] actualExtrasArray = intent.getParcelableArrayExtra(extra); - if (actualExtrasArray != null) { - throw new IllegalArgumentException( - String.format( - "%s is not of type %s[]: %s", - extra, - clazz.getSimpleName(), - Arrays.toString(actualExtrasArray))); - } else if (intent.getParcelableExtra(extra) != null) { - throw new IllegalArgumentException( - String.format( - "%s is not of type %s[] (or any array type): %s", - extra, - clazz.getSimpleName(), - intent.getParcelableExtra(extra))); - } else { - throw new IllegalArgumentException( - String.format( - "%s is not of type %s (or any Parcelable type): %s", - extra, - clazz.getSimpleName(), - intent.getExtras().get(extra))); - } - } - - return castResult; - } - - private static IntentFilter getTargetIntentFilter(final Intent intent) { - try { - String dataString = intent.getDataString(); - if (intent.getType() == null) { - if (!TextUtils.isEmpty(dataString)) { - return new IntentFilter(intent.getAction(), dataString); - } - Log.e(TAG, "Failed to get target intent filter: intent data and type are null"); - return null; - } - IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType()); - List contentUris = new ArrayList<>(); - if (Intent.ACTION_SEND.equals(intent.getAction())) { - Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); - if (uri != null) { - contentUris.add(uri); - } - } else { - List uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); - if (uris != null) { - contentUris.addAll(uris); - } - } - for (Uri uri : contentUris) { - intentFilter.addDataScheme(uri.getScheme()); - intentFilter.addDataAuthority(uri.getAuthority(), null); - intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL); - } - return intentFilter; - } catch (Exception e) { - Log.e(TAG, "Failed to get target intent filter", e); - return null; - } - } -} -- cgit v1.2.3-59-g8ed1b From fcb5a63feb6dd82a712c0b83b325f7b478976cc1 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Wed, 10 Jul 2024 14:25:59 -0700 Subject: Remove fix_target_list_footer Launched. Bug: 324011248 Test: presubmits Flag: EXEMPT removing com.android.intentresolver.fix_target_list_footer Change-Id: Id18ee5168b9adbb6b8c383312dbaef0603fc31a2 --- aconfig/FeatureFlags.aconfig | 10 ---------- .../com/android/intentresolver/grid/ChooserGridAdapter.java | 8 +++----- 2 files changed, 3 insertions(+), 15 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 9c5e5eaa..0cadaab1 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -5,16 +5,6 @@ container: "system" # namespace: intentresolver # bug: "Feature_Bug_#" or "" -flag { - name: "fix_target_list_footer" - namespace: "intentresolver" - description: "Update app target grid footer on window insets change" - bug: "324011248" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "target_data_caching" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java index 7cf9d2e9..1dd83566 100644 --- a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java +++ b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java @@ -150,11 +150,9 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter Date: Wed, 10 Jul 2024 15:14:34 -0700 Subject: Remove bespoke_label_view Launched. Bug: 302188527 Test: presubmits Flag: EXEMPT removing com.android.intentresolver.bespoke_label_view Change-Id: Ia3f3b80b6e854f4ecd569936e144815e5ab592d7 --- aconfig/FeatureFlags.aconfig | 7 ----- .../android/intentresolver/ChooserActivity.java | 3 +-- .../android/intentresolver/ChooserListAdapter.java | 30 +++++----------------- .../intentresolver/ChooserWrapperActivity.java | 3 +-- .../intentresolver/ChooserListAdapterDataTest.kt | 4 --- .../intentresolver/ChooserListAdapterTest.kt | 15 +++-------- 6 files changed, 13 insertions(+), 49 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 0cadaab1..a95a9ea8 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -19,13 +19,6 @@ flag { bug: "302113519" } -flag { - name: "bespoke_label_view" - namespace: "intentresolver" - description: "Use a custom view to draw target labels" - bug: "302188527" -} - flag { name: "enable_private_profile" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 4d70f550..cdf1a8ad 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2043,8 +2043,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (record != null && record.shortcutLoader != null) { record.shortcutLoader.reset(); } - }, - mFeatureFlags); + }); } private void onWorkProfileStatusUpdated() { diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index ff0c40d7..07a615a5 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -111,7 +111,6 @@ public class ChooserListAdapter extends ResolverListAdapter { // Reserve spots for incoming direct share targets by adding placeholders private final TargetInfo mPlaceHolderTargetInfo; private final TargetDataLoader mTargetDataLoader; - private final boolean mUseBadgeTextViewForLabels; private final List mServiceTargets = new ArrayList<>(); private final List mCallerTargets = new ArrayList<>(); @@ -171,8 +170,7 @@ public class ChooserListAdapter extends ResolverListAdapter { int maxRankedTargets, UserHandle initialIntentsUserSpace, TargetDataLoader targetDataLoader, - @Nullable PackageChangeCallback packageChangeCallback, - FeatureFlags featureFlags) { + @Nullable PackageChangeCallback packageChangeCallback) { this( context, payloadIntents, @@ -191,8 +189,8 @@ public class ChooserListAdapter extends ResolverListAdapter { targetDataLoader, packageChangeCallback, AsyncTask.SERIAL_EXECUTOR, - context.getMainExecutor(), - featureFlags); + context.getMainExecutor() + ); } @VisibleForTesting @@ -214,8 +212,7 @@ public class ChooserListAdapter extends ResolverListAdapter { TargetDataLoader targetDataLoader, @Nullable PackageChangeCallback packageChangeCallback, Executor bgExecutor, - Executor mainExecutor, - FeatureFlags featureFlags) { + Executor mainExecutor) { // Don't send the initial intents through the shared ResolverActivity path, // we want to separate them into a different section. super( @@ -239,7 +236,6 @@ public class ChooserListAdapter extends ResolverListAdapter { mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context); mTargetDataLoader = targetDataLoader; mPackageChangeCallback = packageChangeCallback; - mUseBadgeTextViewForLabels = featureFlags.bespokeLabelView(); createPlaceHolders(); mEventLog = eventLog; mShortcutSelectionLogic = new ShortcutSelectionLogic( @@ -345,12 +341,7 @@ public class ChooserListAdapter extends ResolverListAdapter { @Override View onCreateView(ViewGroup parent) { - return mInflater.inflate( - mUseBadgeTextViewForLabels - ? R.layout.chooser_grid_item - : R.layout.resolve_grid_item, - parent, - false); + return mInflater.inflate(R.layout.chooser_grid_item, parent, false); } @Override @@ -448,9 +439,7 @@ public class ChooserListAdapter extends ResolverListAdapter { holder.reset(); holder.itemView.setBackground(holder.defaultItemViewBackground); - if (mUseBadgeTextViewForLabels) { - ((BadgeTextView) holder.text).setBadgeDrawable(null); - } + ((BadgeTextView) holder.text).setBadgeDrawable(null); holder.text.setBackground(null); holder.text.setPaddingRelative(0, 0, 0, 0); } @@ -464,12 +453,7 @@ public class ChooserListAdapter extends ResolverListAdapter { } private void bindGroupIndicator(ViewHolder holder, Drawable indicator) { - if (mUseBadgeTextViewForLabels) { - ((BadgeTextView) holder.text).setBadgeDrawable(indicator); - } else { - holder.text.setPaddingRelative(0, 0, /*end = */indicator.getIntrinsicWidth(), 0); - holder.text.setBackground(indicator); - } + ((BadgeTextView) holder.text).setBadgeDrawable(indicator); } private void bindPinnedIndicator(ViewHolder holder, Drawable indicator) { diff --git a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java index 8a8fe355..930c84f0 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -75,8 +75,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW maxTargetsPerRow, userHandle, mTargetDataLoader, - null, - mFeatureFlags); + null); } @Override diff --git a/tests/unit/src/com/android/intentresolver/ChooserListAdapterDataTest.kt b/tests/unit/src/com/android/intentresolver/ChooserListAdapterDataTest.kt index df0c5e5e..a0f733ab 100644 --- a/tests/unit/src/com/android/intentresolver/ChooserListAdapterDataTest.kt +++ b/tests/unit/src/com/android/intentresolver/ChooserListAdapterDataTest.kt @@ -66,8 +66,6 @@ class ChooserListAdapterDataTest { private val immediateExecutor = TestExecutor(immediate = true) private val referrerFillInIntent = Intent().putExtra(Intent.EXTRA_REFERRER, "org.referrer.package") - private val featureFlags = - FakeFeatureFlagsImpl().apply { setFlag(Flags.FLAG_BESPOKE_LABEL_VIEW, false) } @Test fun test_twoTargetsWithNonOverlappingInitialIntent_threeTargetsInResolverAdapter() { @@ -119,7 +117,6 @@ class ChooserListAdapterDataTest { null, backgroundExecutor, immediateExecutor, - featureFlags, ) val doPostProcessing = true @@ -183,7 +180,6 @@ class ChooserListAdapterDataTest { null, backgroundExecutor, immediateExecutor, - featureFlags, ) val doPostProcessing = true diff --git a/tests/unit/src/com/android/intentresolver/ChooserListAdapterTest.kt b/tests/unit/src/com/android/intentresolver/ChooserListAdapterTest.kt index bad3b18c..cdc84ba8 100644 --- a/tests/unit/src/com/android/intentresolver/ChooserListAdapterTest.kt +++ b/tests/unit/src/com/android/intentresolver/ChooserListAdapterTest.kt @@ -61,7 +61,6 @@ class ChooserListAdapterTest { private val mEventLog = mock() private val mTargetDataLoader = mock() private val mPackageChangeCallback = mock() - private val featureFlags = FeatureFlagsImpl() private val testSubject by lazy { ChooserListAdapter( @@ -81,7 +80,6 @@ class ChooserListAdapterTest { null, mTargetDataLoader, mPackageChangeCallback, - featureFlags, ) } @@ -222,15 +220,10 @@ class ChooserListAdapterTest { private fun createView(): View { val view = FrameLayout(context) - if (featureFlags.bespokeLabelView()) { - BadgeTextView(context) - } else { - TextView(context) - } - .apply { - id = R.id.text1 - view.addView(this) - } + BadgeTextView(context).apply { + id = R.id.text1 + view.addView(this) + } TextView(context).apply { id = R.id.text2 view.addView(this) -- cgit v1.2.3-59-g8ed1b From 875c53f91e3bdc0814f25208b9b291809b3b6bcc Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Wed, 10 Jul 2024 15:45:16 -0700 Subject: Remove fix_partial_image_edit_transition Launched. Bug: 339583191 Test: presubmits Flag: EXEMPT removing com.android.intentresolver.fix_partial_image_edit_transition Change-Id: I8e826f05690dcfdf4bed1cfcdf036218fe3b708e --- aconfig/FeatureFlags.aconfig | 10 ---------- .../src/com/android/intentresolver/ChooserActionFactory.java | 12 ++++-------- java/src/com/android/intentresolver/ChooserActivity.java | 3 +-- .../com/android/intentresolver/ChooserActionFactoryTest.kt | 6 ------ 4 files changed, 5 insertions(+), 26 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index a95a9ea8..87ae2f1d 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -63,16 +63,6 @@ flag { } } -flag { - name: "fix_partial_image_edit_transition" - namespace: "intentresolver" - description: "Do not run the shared element transition animation for a partially visible image" - bug: "339583191" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "fix_private_space_locked_on_restart" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java index cc7091e4..21ca3b73 100644 --- a/java/src/com/android/intentresolver/ChooserActionFactory.java +++ b/java/src/com/android/intentresolver/ChooserActionFactory.java @@ -133,8 +133,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio ActionActivityStarter activityStarter, @Nullable ShareResultSender shareResultSender, Consumer finishCallback, - ClipboardManager clipboardManager, - FeatureFlags featureFlags) { + ClipboardManager clipboardManager) { this( context, makeCopyButtonRunnable( @@ -150,8 +149,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio imageEditor), firstVisibleImageQuery, activityStarter, - log, - featureFlags.fixPartialImageEditTransition()), + log), chooserActions, onUpdateSharedTextIsExcluded, log, @@ -340,8 +338,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio @Nullable TargetInfo editSharingTarget, Callable firstVisibleImageQuery, ActionActivityStarter activityStarter, - EventLog log, - boolean requireFullVisibility) { + EventLog log) { if (editSharingTarget == null) return null; return () -> { // Log share completion via edit. @@ -352,8 +349,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio firstImageView = firstVisibleImageQuery.call(); } catch (Exception e) { /* ignore */ } // Action bar is user-independent; always start as primary. - if (firstImageView == null - || (requireFullVisibility && !isFullyVisible(firstImageView))) { + if (firstImageView == null || !isFullyVisible(firstImageView)) { activityStarter.safelyStartActivityAsPersonalProfileUser(editSharingTarget); } else { activityStarter.safelyStartActivityAsPersonalProfileUserWithSharedElementTransition( diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index cdf1a8ad..7e0f148d 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2193,8 +2193,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements }, mShareResultSender, this::finishWithStatus, - mClipboardManager, - mFeatureFlags); + mClipboardManager); } private Supplier createModifyShareActionFactory() { diff --git a/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt index c8e17de4..8dfbdbdd 100644 --- a/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt +++ b/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt @@ -69,8 +69,6 @@ class ChooserActionFactoryTest { latestReturn = resultCode } } - private val featureFlags = - FakeFeatureFlagsImpl().apply { setFlag(Flags.FLAG_FIX_PARTIAL_IMAGE_EDIT_TRANSITION, true) } @Before fun setup() { @@ -121,7 +119,6 @@ class ChooserActionFactoryTest { /* shareResultSender = */ null, /* finishCallback = */ {}, /* clipboardManager = */ mock(), - /* featureFlags = */ featureFlags, ) assertThat(testSubject.copyButtonRunnable).isNull() } @@ -143,7 +140,6 @@ class ChooserActionFactoryTest { /* shareResultSender = */ null, /* finishCallback = */ {}, /* clipboardManager = */ mock(), - /* featureFlags = */ featureFlags, ) assertThat(testSubject.copyButtonRunnable).isNull() } @@ -166,7 +162,6 @@ class ChooserActionFactoryTest { /* shareResultSender = */ resultSender, /* finishCallback = */ {}, /* clipboardManager = */ mock(), - /* featureFlags = */ featureFlags, ) assertThat(testSubject.copyButtonRunnable).isNotNull() @@ -199,7 +194,6 @@ class ChooserActionFactoryTest { /* shareResultSender = */ null, /* finishCallback = */ resultConsumer, /* clipboardManager = */ mock(), - /* featureFlags = */ featureFlags, ) } } -- cgit v1.2.3-59-g8ed1b From c59c3bdb6262228d9135fb545ab42340a9ec8f7f Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Fri, 12 Jul 2024 16:20:54 -0700 Subject: Request preview size, WIDTH and HEIGHT columns for additional content Fix: 352818692 Test: atest IntentResolver-tests-unit Flag: EXEMPT bugfix Change-Id: I0fb0c7916a428eca064bc08def049ae4bc59818a --- .../payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt | 5 +++-- .../payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt index 148310e6..2b14cdea 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt @@ -20,6 +20,8 @@ import android.content.ContentInterface import android.content.Intent import android.database.Cursor import android.net.Uri +import android.provider.MediaStore.MediaColumns.HEIGHT +import android.provider.MediaStore.MediaColumns.WIDTH import android.service.chooser.AdditionalContentContract.Columns.URI import androidx.core.os.bundleOf import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.CursorRow @@ -48,8 +50,7 @@ constructor( runCatching { contentResolver.query( cursorUri, - // TODO: uncomment to start using that data - arrayOf(URI /*, WIDTH, HEIGHT*/), + arrayOf(URI, WIDTH, HEIGHT), bundleOf(Intent.EXTRA_INTENT to chooserIntent), signal, ) diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt index 5d81ec2a..f0813623 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt @@ -30,9 +30,12 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.capture import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.verify class PayloadToggleCursorResolverTest { private val cursorUri = Uri.parse("content://org.pkg.app.extra") @@ -101,6 +104,9 @@ class PayloadToggleCursorResolverTest { assertThat(row!!.uri).isEqualTo(uri) assertThat(row.previewSize).isEqualTo(Size(100, 50)) } + val columnsCaptor = argumentCaptor>() + verify(fakeContentProvider).query(eq(cursorUri), columnsCaptor.capture(), any(), any()) + assertThat(columnsCaptor.firstValue.toList()).containsExactly(URI, WIDTH, HEIGHT) } @Test -- cgit v1.2.3-59-g8ed1b From af0301b4c0ecee359d729d8d072973752e2a2bcf Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Thu, 11 Jul 2024 23:14:25 -0700 Subject: Limit max item aspect ratio Limit max item aspect ratio to make sure it is always visible on the screen (incliding paddings) Fix: 351094699 Test: manual test Flag: EXEMPT bugfix Change-Id: I8748bb7ee1b9cde03da1f6d15075d04e806197db --- .../ui/composable/ShareouselComposable.kt | 91 +++++++++++++++------- 1 file changed, 64 insertions(+), 27 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index c40ed266..3c3381a2 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -46,13 +47,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layout import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription @@ -106,42 +110,76 @@ private fun PreviewCarousel( initialFirstVisibleItemIndex = centerIdx, prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() } ) - // TODO: start item needs to be centered, check out ScalingLazyColumn impl or see if - // HorizontalPager works for our use-case - LazyRow( - state = carouselState, - horizontalArrangement = Arrangement.spacedBy(4.dp), - contentPadding = PaddingValues(start = 16.dp, end = 16.dp), + var maxAspectRatio by remember { mutableStateOf(0f) } + + val horizontalPadding = 16.dp + Box( modifier = Modifier.fillMaxWidth() .height(dimensionResource(R.dimen.chooser_preview_image_height_tall)) - .systemGestureExclusion() - ) { - itemsIndexed(previews.previewModels, key = { _, model -> model.uri }) { index, model -> - - // Index if this is the element in the center of the viewing area, otherwise null - val previewIndex by remember { - derivedStateOf { - carouselState.layoutInfo.visibleItemsInfo - .firstOrNull { it.index == index } - ?.let { - val viewportCenter = carouselState.layoutInfo.viewportEndOffset / 2 - val halfPreviewWidth = it.size / 2 - val previewCenter = it.offset + halfPreviewWidth - val previewDistanceToViewportCenter = - abs(previewCenter - viewportCenter) - if (previewDistanceToViewportCenter <= halfPreviewWidth) index else null + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val aspectRatio = + if (placeable.height <= 0) { + 0f + } else { + val maxItemWidth = + maxOf(0, placeable.width - 2 * horizontalPadding.roundToPx()) + (maxItemWidth.toFloat() / placeable.height).coerceIn( + 0f, + MAX_ASPECT_RATIO + ) } + if (maxAspectRatio != aspectRatio) { + maxAspectRatio = aspectRatio + } + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + }, + ) { + if (maxAspectRatio <= 0) { + // Do not compose the list until we know the viewport size + return@Box + } + // TODO: start item needs to be centered, check out ScalingLazyColumn impl or see if + // HorizontalPager works for our use-case + LazyRow( + state = carouselState, + horizontalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(start = horizontalPadding, end = horizontalPadding), + modifier = Modifier.fillMaxSize().systemGestureExclusion(), + ) { + itemsIndexed(previews.previewModels, key = { _, model -> model.uri }) { index, model -> + // Index if this is the element in the center of the viewing area, otherwise null + val previewIndex by remember { + derivedStateOf { + carouselState.layoutInfo.visibleItemsInfo + .firstOrNull { it.index == index } + ?.let { + val viewportCenter = carouselState.layoutInfo.viewportEndOffset / 2 + val halfPreviewWidth = it.size / 2 + val previewCenter = it.offset + halfPreviewWidth + val previewDistanceToViewportCenter = + abs(previewCenter - viewportCenter) + if (previewDistanceToViewportCenter <= halfPreviewWidth) { + index + } else { + null + } + } + } } - } - ShareouselCard(viewModel.preview(model, previewIndex, rememberCoroutineScope())) + ShareouselCard( + viewModel.preview(model, previewIndex, rememberCoroutineScope()), + maxAspectRatio, + ) + } } } } @Composable -private fun ShareouselCard(viewModel: ShareouselPreviewViewModel) { +private fun ShareouselCard(viewModel: ShareouselPreviewViewModel, maxAspectRatio: Float) { val bitmapLoadState by viewModel.bitmapLoadState.collectAsStateWithLifecycle() val selected by viewModel.isSelected.collectAsStateWithLifecycle(initialValue = false) val borderColor = MaterialTheme.colorScheme.primary @@ -162,8 +200,7 @@ private fun ShareouselCard(viewModel: ShareouselPreviewViewModel) { onValueChange = { scope.launch { viewModel.setSelected(it) } }, ) ) { state -> - // TODO: max ratio is actually equal to the viewport ratio - val aspectRatio = viewModel.aspectRatio.coerceIn(MIN_ASPECT_RATIO, MAX_ASPECT_RATIO) + val aspectRatio = viewModel.aspectRatio.coerceIn(MIN_ASPECT_RATIO, maxAspectRatio) if (state is ValueUpdate.Value) { state.getOrDefault(null).let { bitmap -> ShareouselCard( -- cgit v1.2.3-59-g8ed1b From e48daa217dc397cce855a5357ee11a87b0c7bce4 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Tue, 25 Jun 2024 12:48:32 -0700 Subject: Update ImageLoader interface to receive preview sizes along with the URI The UI calculates and provides view sizes but they are not used by the existing ImageLoader implementations thus no functional change is expected. Bug: 348665058 Test: atest IntentResolver-tests-unit Test: atest IntentResolver-tests-activity Test: inject debug logging and verify preview values being requested for app preview types Flag: EXEMPT refactor Change-Id: I0e282f773c424b6fe81587a71e1b8630452ac63c --- .../CachingImagePreviewImageLoader.kt | 7 +- .../FilesPlusTextContentPreviewUi.java | 5 +- .../intentresolver/contentpreview/ImageLoader.kt | 14 ++-- .../contentpreview/ImagePreviewImageLoader.kt | 7 +- .../contentpreview/TextContentPreviewUi.java | 9 ++- .../contentpreview/UnifiedContentPreviewUi.java | 17 +++-- .../ui/composable/ShareouselComposable.kt | 13 ++-- .../ui/viewmodel/ShareouselViewModel.kt | 21 ++++-- .../widget/ScrollableImagePreviewView.kt | 42 ++++++++++-- .../com/android/intentresolver/FakeImageLoader.kt | 12 +++- .../CachingImagePreviewImageLoaderTest.kt | 36 ++++++----- .../contentpreview/ImagePreviewImageLoaderTest.kt | 48 +++++++------- .../ui/viewmodel/ShareouselViewModelTest.kt | 75 ++++++++++++---------- .../widget/BatchPreviewLoaderTest.kt | 28 ++++++-- 14 files changed, 219 insertions(+), 115 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt index c9deec1b..f60f550e 100644 --- a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt @@ -19,6 +19,7 @@ package com.android.intentresolver.contentpreview import android.graphics.Bitmap import android.net.Uri import android.util.Log +import android.util.Size import androidx.core.util.lruCache import com.android.intentresolver.inject.Background import com.android.intentresolver.inject.ViewModelOwned @@ -72,11 +73,11 @@ constructor( } ) - override fun prePopulate(uris: List) { - uris.take(cache.maxSize()).map { cache[it] } + override fun prePopulate(uriSizePairs: List>) { + uriSizePairs.take(cache.maxSize()).map { cache[it.first] } } - override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? { + override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? { return if (caching) { loadCachedImage(uri) } else { diff --git a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java index b50f5bc8..30161cfb 100644 --- a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.net.Uri; import android.text.util.Linkify; import android.util.PluralsMessageFormatter; +import android.util.Size; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -68,6 +69,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi { private Uri mFirstFilePreviewUri; private boolean mAllImages; private boolean mAllVideos; + private int mPreviewSize; // TODO(b/285309527): make this a flag private static final boolean SHOW_TOGGLE_CHECKMARK = false; @@ -109,6 +111,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi { LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent) { + mPreviewSize = resources.getDimensionPixelSize(R.dimen.width_text_image_preview_size); return displayInternal(layoutInflater, parent, headlineViewParent); } @@ -164,12 +167,12 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi { private void updateUiWithMetadata(ViewGroup contentPreviewView, View headlineView) { prepareTextPreview(contentPreviewView, headlineView, mActionFactory); updateHeadline(headlineView, mFileCount, mAllImages, mAllVideos); - ImageView imagePreview = mContentPreviewView.requireViewById(R.id.image_view); if (mIsSingleImage && mFirstFilePreviewUri != null) { mImageLoader.loadImage( mScope, mFirstFilePreviewUri, + new Size(mPreviewSize, mPreviewSize), bitmap -> { if (bitmap == null) { imagePreview.setVisibility(View.GONE); diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt index b4d03ac9..ac34f552 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt @@ -18,23 +18,25 @@ package com.android.intentresolver.contentpreview import android.graphics.Bitmap import android.net.Uri +import android.util.Size import java.util.function.Consumer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.isActive import kotlinx.coroutines.launch /** A content preview image loader. */ -interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitmap? { +interface ImageLoader : suspend (Uri, Size) -> Bitmap?, suspend (Uri, Size, Boolean) -> Bitmap? { /** * Load preview image asynchronously; caching is allowed. * * @param uri content URI + * @param size target bitmap size * @param callback a callback that will be invoked with the loaded image or null if loading has * failed. */ - fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) { + fun loadImage(callerScope: CoroutineScope, uri: Uri, size: Size, callback: Consumer) { callerScope.launch { - val bitmap = invoke(uri) + val bitmap = invoke(uri, size) if (isActive) { callback.accept(bitmap) } @@ -42,13 +44,13 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm } /** Prepopulate the image loader cache. */ - fun prePopulate(uris: List) + fun prePopulate(uriSizePairs: List>) /** Returns a bitmap for the given URI if it's already cached, otherwise null */ fun getCachedBitmap(uri: Uri): Bitmap? = null /** Load preview image; caching is allowed. */ - override suspend fun invoke(uri: Uri) = invoke(uri, true) + override suspend fun invoke(uri: Uri, size: Size) = invoke(uri, size, true) /** * Load preview image. @@ -56,5 +58,5 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm * @param uri content URI * @param caching indicates if the loaded image could be cached. */ - override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? + override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? } diff --git a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt index 7cf9a8c9..379bdb37 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt @@ -98,10 +98,11 @@ constructor( @GuardedBy("lock") private val cache = LruCache(cacheSize) @GuardedBy("lock") private val runningRequests = HashMap() - override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = loadImageAsync(uri, caching) + override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? = + loadImageAsync(uri, caching) - override fun prePopulate(uris: List) { - uris.asSequence().take(cache.maxSize()).forEach { uri -> + override fun prePopulate(uriSizePairs: List>) { + uriSizePairs.asSequence().take(cache.maxSize()).forEach { (uri, _) -> scope.launch { loadImageAsync(uri, caching = true) } } } diff --git a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java index ae7ddcd9..b12eb8cf 100644 --- a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java @@ -22,6 +22,7 @@ import android.content.res.Resources; import android.net.Uri; import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.util.Size; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -50,6 +51,7 @@ class TextContentPreviewUi extends ContentPreviewUi { private final ChooserContentPreviewUi.ActionFactory mActionFactory; private final HeadlineGenerator mHeadlineGenerator; private final ContentTypeHint mContentTypeHint; + private int mPreviewSize; TextContentPreviewUi( CoroutineScope scope, @@ -83,6 +85,7 @@ class TextContentPreviewUi extends ContentPreviewUi { LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent) { + mPreviewSize = resources.getDimensionPixelSize(R.dimen.width_text_image_preview_size); return displayInternal(layoutInflater, parent, headlineViewParent); } @@ -119,7 +122,7 @@ class TextContentPreviewUi extends ContentPreviewUi { previewTitleView.setText(mPreviewTitle); } - ImageView previewThumbnailView = contentPreviewLayout.findViewById( + final ImageView previewThumbnailView = contentPreviewLayout.requireViewById( com.android.internal.R.id.content_preview_thumbnail); if (!isOwnedByCurrentUser(mPreviewThumbnail)) { previewThumbnailView.setVisibility(View.GONE); @@ -127,9 +130,9 @@ class TextContentPreviewUi extends ContentPreviewUi { mImageLoader.loadImage( mScope, mPreviewThumbnail, + new Size(mPreviewSize, mPreviewSize), (bitmap) -> updateViewWithImage( - contentPreviewLayout.findViewById( - com.android.internal.R.id.content_preview_thumbnail), + previewThumbnailView, bitmap)); } diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java index 88311016..7de988c4 100644 --- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java @@ -20,6 +20,7 @@ import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTE import android.content.res.Resources; import android.util.Log; +import android.util.Size; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -31,6 +32,8 @@ import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; import com.android.intentresolver.widget.ScrollableImagePreviewView; +import kotlin.Pair; + import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.flow.Flow; @@ -55,6 +58,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { @Nullable private ViewGroup mContentPreviewView; private View mHeadlineView; + private int mPreviewSize; UnifiedContentPreviewUi( CoroutineScope scope, @@ -93,14 +97,18 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent) { + mPreviewSize = resources.getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen); return displayInternal(layoutInflater, parent, headlineViewParent); } private void setFiles(List files) { - mImageLoader.prePopulate(files.stream() - .map(FileInfo::getPreviewUri) - .filter(Objects::nonNull) - .toList()); + Size previewSize = new Size(mPreviewSize, mPreviewSize); + mImageLoader.prePopulate( + files.stream() + .map(FileInfo::getPreviewUri) + .filter(Objects::nonNull) + .map((uri -> new Pair<>(uri, previewSize))) + .toList()); mFiles = files; if (mContentPreviewView != null) { updatePreviewWithFiles(mContentPreviewView, mHeadlineView, files); @@ -121,6 +129,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { ScrollableImagePreviewView imagePreview = mContentPreviewView.requireViewById(R.id.scrollable_image_preview); + imagePreview.setPreviewHeight(mPreviewSize); imagePreview.setImageLoader(mImageLoader); imagePreview.setOnNoPreviewCallback(() -> imagePreview.setVisibility(View.GONE)); imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback); diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index 3c3381a2..9ac36a87 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -111,6 +111,7 @@ private fun PreviewCarousel( prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() } ) var maxAspectRatio by remember { mutableStateOf(0f) } + var viewportHeight by remember { mutableStateOf(0) } val horizontalPadding = 16.dp Box( @@ -130,9 +131,8 @@ private fun PreviewCarousel( MAX_ASPECT_RATIO ) } - if (maxAspectRatio != aspectRatio) { - maxAspectRatio = aspectRatio - } + maxAspectRatio = aspectRatio + viewportHeight = placeable.height layout(placeable.width, placeable.height) { placeable.place(0, 0) } }, ) { @@ -170,7 +170,12 @@ private fun PreviewCarousel( } ShareouselCard( - viewModel.preview(model, previewIndex, rememberCoroutineScope()), + viewModel.preview( + model, + viewportHeight, + previewIndex, + rememberCoroutineScope() + ), maxAspectRatio, ) } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt index d0b89860..f1e65f73 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt @@ -15,6 +15,7 @@ */ package com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel +import android.util.Size import com.android.intentresolver.contentpreview.CachingImagePreviewImageLoader import com.android.intentresolver.contentpreview.HeadlineGenerator import com.android.intentresolver.contentpreview.ImageLoader @@ -57,7 +58,9 @@ data class ShareouselViewModel( val actions: Flow>, /** Creates a [ShareouselPreviewViewModel] for a [PreviewModel] present in [previews]. */ val preview: - (key: PreviewModel, index: Int?, scope: CoroutineScope) -> ShareouselPreviewViewModel, + ( + key: PreviewModel, previewHeight: Int, index: Int?, scope: CoroutineScope + ) -> ShareouselPreviewViewModel, ) @Module @@ -114,7 +117,7 @@ interface ShareouselViewModelModule { } } }, - preview = { key, index, previewScope -> + preview = { key, previewHeight, index, previewScope -> keySet.value?.maybeLoad(index) val previewInteractor = interactor.preview(key) val contentType = @@ -130,9 +133,19 @@ interface ShareouselViewModelModule { ShareouselPreviewViewModel( bitmapLoadState = flow { + val previewWidth = + if (key.aspectRatio > 0) { + previewHeight.toFloat() / key.aspectRatio + } else { + previewHeight + } + .toInt() emit( - key.previewUri?.let { ValueUpdate.Value(imageLoader(it)) } - ?: ValueUpdate.Absent + key.previewUri?.let { + ValueUpdate.Value( + imageLoader(it, Size(previewWidth, previewHeight)) + ) + } ?: ValueUpdate.Absent ) } .stateIn(previewScope, SharingStarted.Eagerly, initialBitmapValue), diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt index 7fe16091..c706e3ee 100644 --- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt +++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt @@ -22,6 +22,7 @@ import android.graphics.Rect import android.net.Uri import android.util.AttributeSet import android.util.PluralsMessageFormatter +import android.util.Size import android.util.TypedValue import android.view.LayoutInflater import android.view.View @@ -60,11 +61,13 @@ private const val MIN_ASPECT_RATIO_STRING = "2:5" private const val MAX_ASPECT_RATIO = 2.5f private const val MAX_ASPECT_RATIO_STRING = "5:2" -private typealias CachingImageLoader = suspend (Uri, Boolean) -> Bitmap? +private typealias CachingImageLoader = suspend (Uri, Size, Boolean) -> Bitmap? class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { constructor(context: Context) : this(context, null) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor( context: Context, attrs: AttributeSet?, @@ -121,12 +124,19 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { * A hint about the maximum width this view can grow to, this helps to optimize preview loading. */ 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 private var outerSpacing: Int = 0 + var previewHeight: Int + get() = previewAdapter.previewHeight + set(value) { + previewAdapter.previewHeight = value + } + override fun onMeasure(widthSpec: Int, heightSpec: Int) { super.onMeasure(widthSpec, heightSpec) if (!isMeasured) { @@ -198,6 +208,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { BatchPreviewLoader( previewAdapter.imageLoader ?: error("Image loader is not set"), previews, + Size(previewHeight, previewHeight), totalItemCount, onUpdate = previewAdapter::addPreviews, onCompletion = { @@ -303,11 +314,19 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { private var isLoading = false private val hasOtherItem get() = previews.size < totalItemCount + val hasPreviews: Boolean get() = previews.isNotEmpty() var transitionStatusElementCallback: TransitionElementStatusCallback? = null + private var previewSize: Size = Size(0, 0) + var previewHeight: Int + get() = previewSize.height + set(value) { + previewSize = Size(value, value) + } + fun reset(totalItemCount: Int) { firstImagePos = -1 previews.clear() @@ -387,6 +406,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { vh.bind( previews[position], imageLoader ?: error("ImageLoader is missing"), + previewSize, fadeInDurationMs, isSharedTransitionElement = position == firstImagePos, previewReadyCallback = @@ -438,6 +458,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { fun bind( preview: Preview, imageLoader: CachingImageLoader, + previewSize: Size, fadeInDurationMs: Long, isSharedTransitionElement: Boolean, previewReadyCallback: ((String) -> Unit)? @@ -477,7 +498,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { } } resetScope().launch { - loadImage(preview, imageLoader) + loadImage(preview, previewSize, imageLoader) if (preview.type == PreviewType.Image && previewReadyCallback != null) { image.waitForPreDraw() previewReadyCallback(TRANSITION_NAME) @@ -487,12 +508,16 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { } } - private suspend fun loadImage(preview: Preview, imageLoader: CachingImageLoader) { + private suspend fun loadImage( + preview: Preview, + previewSize: Size, + imageLoader: CachingImageLoader, + ) { val bitmap = runCatching { // it's expected for all loading/caching optimizations to be implemented by // the loader - imageLoader(preview.uri, true) + imageLoader(preview.uri, previewSize, true) } .getOrNull() image.setImageBitmap(bitmap) @@ -507,6 +532,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { setAnimationListener( object : AnimationListener { override fun onAnimationStart(animation: Animation?) = Unit + override fun onAnimationRepeat(animation: Animation?) = Unit override fun onAnimationEnd(animation: Animation?) { @@ -551,6 +577,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { private class LoadingItemViewHolder(view: View) : ViewHolder(view) { fun bind() = Unit + override fun unbind() = Unit } @@ -638,6 +665,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { class BatchPreviewLoader( private val imageLoader: CachingImageLoader, private val previews: Flow, + private val previewSize: Size, val totalItemCount: Int, private val onUpdate: (List) -> Unit, private val onCompletion: () -> Unit, @@ -701,10 +729,10 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { // imagine is one of the first images never loads so we never // fill the initial viewport and does not show the previews at // all. - imageLoader(preview.uri, isFirstBlock)?.let { bitmap -> + imageLoader(preview.uri, previewSize, isFirstBlock)?.let { + bitmap -> previewSizeUpdater(preview, bitmap.width, bitmap.height) - } - ?: 0 + } ?: 0 } .getOrDefault(0) diff --git a/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt b/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt index c57ea78b..76eb5e0d 100644 --- a/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt +++ b/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt @@ -18,6 +18,7 @@ package com.android.intentresolver import android.graphics.Bitmap import android.net.Uri +import android.util.Size import com.android.intentresolver.contentpreview.ImageLoader import java.util.function.Consumer import kotlinx.coroutines.CoroutineScope @@ -25,13 +26,18 @@ import kotlinx.coroutines.CoroutineScope class FakeImageLoader(initialBitmaps: Map = emptyMap()) : ImageLoader { private val bitmaps = HashMap().apply { putAll(initialBitmaps) } - override fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer) { + override fun loadImage( + callerScope: CoroutineScope, + uri: Uri, + size: Size, + callback: Consumer, + ) { callback.accept(bitmaps[uri]) } - override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = bitmaps[uri] + override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? = bitmaps[uri] - override fun prePopulate(uris: List) = Unit + override fun prePopulate(uriSizePairs: List>) = Unit fun setBitmap(uri: Uri, bitmap: Bitmap) { bitmaps[uri] = bitmap diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt index 331f9f64..d5a569aa 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt @@ -18,6 +18,7 @@ package com.android.intentresolver.contentpreview import android.graphics.Bitmap import android.net.Uri +import android.util.Size import com.google.common.truth.Truth.assertThat import kotlin.math.ceil import kotlin.math.roundToInt @@ -43,6 +44,7 @@ class CachingImagePreviewImageLoaderTest { testJobTime * ceil((testCacheSize).toFloat() / testMaxConcurrency.toFloat()).roundToInt() private val testUris = List(5) { Uri.fromParts("TestScheme$it", "TestSsp$it", "TestFragment$it") } + private val previewSize = Size(500, 500) private val testTimeToLoadAllUris = testJobTime * ceil((testUris.size).toFloat() / testMaxConcurrency.toFloat()).roundToInt() private val testBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8) @@ -72,7 +74,7 @@ class CachingImagePreviewImageLoaderTest { var result: Bitmap? = null // Act - imageLoader.loadImage(testScope, testUris[0]) { result = it } + imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } advanceTimeBy(testJobTime) runCurrent() @@ -85,14 +87,14 @@ class CachingImagePreviewImageLoaderTest { fun loadImage_cached_usesCachedValue() = testScope.runTest { // Arrange - imageLoader.loadImage(testScope, testUris[0]) {} + imageLoader.loadImage(testScope, testUris[0], previewSize) {} advanceTimeBy(testJobTime) runCurrent() fakeThumbnailLoader.invokeCalls.clear() var result: Bitmap? = null // Act - imageLoader.loadImage(testScope, testUris[0]) { result = it } + imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } advanceTimeBy(testJobTime) runCurrent() @@ -112,7 +114,7 @@ class CachingImagePreviewImageLoaderTest { var result: Bitmap? = testBitmap // Act - imageLoader.loadImage(testScope, testUris[0]) { result = it } + imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } advanceTimeBy(testJobTime) runCurrent() @@ -130,7 +132,7 @@ class CachingImagePreviewImageLoaderTest { // Act testUris.take(testMaxConcurrency + 1).forEach { uri -> - imageLoader.loadImage(testScope, uri) { results.add(it) } + imageLoader.loadImage(testScope, uri, previewSize) { results.add(it) } } // Assert @@ -153,10 +155,10 @@ class CachingImagePreviewImageLoaderTest { assertThat(testUris.size).isGreaterThan(testCacheSize) // Act - imageLoader.loadImage(testScope, testUris[0]) { results[0] = it } + imageLoader.loadImage(testScope, testUris[0], previewSize) { results[0] = it } runCurrent() testUris.indices.drop(1).take(testCacheSize).forEach { i -> - imageLoader.loadImage(testScope, testUris[i]) { results[i] = it } + imageLoader.loadImage(testScope, testUris[i], previewSize) { results[i] = it } } advanceTimeBy(testTimeToFillCache) runCurrent() @@ -179,7 +181,7 @@ class CachingImagePreviewImageLoaderTest { assertThat(fullCacheUris).hasSize(testCacheSize) // Act - imageLoader.prePopulate(fullCacheUris) + imageLoader.prePopulate(fullCacheUris.map { it to previewSize }) advanceTimeBy(testTimeToFillCache) runCurrent() @@ -188,7 +190,7 @@ class CachingImagePreviewImageLoaderTest { // Act fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(fullCacheUris) + imageLoader.prePopulate(fullCacheUris.map { it to previewSize }) advanceTimeBy(testTimeToFillCache) runCurrent() @@ -203,7 +205,7 @@ class CachingImagePreviewImageLoaderTest { assertThat(testUris.size).isGreaterThan(testCacheSize) // Act - imageLoader.prePopulate(testUris) + imageLoader.prePopulate(testUris.map { it to previewSize }) advanceTimeBy(testTimeToLoadAllUris) runCurrent() @@ -213,7 +215,7 @@ class CachingImagePreviewImageLoaderTest { // Act fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(testUris) + imageLoader.prePopulate(testUris.map { it to previewSize }) advanceTimeBy(testTimeToLoadAllUris) runCurrent() @@ -229,7 +231,7 @@ class CachingImagePreviewImageLoaderTest { assertThat(unfilledCacheUris.size).isLessThan(testCacheSize) // Act - imageLoader.prePopulate(unfilledCacheUris) + imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize }) advanceTimeBy(testJobTime) runCurrent() @@ -238,7 +240,7 @@ class CachingImagePreviewImageLoaderTest { // Act fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(unfilledCacheUris) + imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize }) advanceTimeBy(testJobTime) runCurrent() @@ -252,8 +254,8 @@ class CachingImagePreviewImageLoaderTest { // Arrange // Act - imageLoader.invoke(testUris[0], caching = false) - imageLoader.invoke(testUris[0], caching = false) + imageLoader.invoke(testUris[0], previewSize, caching = false) + imageLoader.invoke(testUris[0], previewSize, caching = false) advanceTimeBy(testJobTime) runCurrent() @@ -267,8 +269,8 @@ class CachingImagePreviewImageLoaderTest { // Arrange // Act - imageLoader.invoke(testUris[0], caching = true) - imageLoader.invoke(testUris[0], caching = true) + imageLoader.invoke(testUris[0], previewSize, caching = true) + imageLoader.invoke(testUris[0], previewSize, caching = true) advanceTimeBy(testJobTime) runCurrent() diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt index 3a45e2f6..d78e6665 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt @@ -77,24 +77,25 @@ class ImagePreviewImageLoaderTest { contentResolver, cacheSize = 1, ) + private val previewSize = Size(500, 500) @Test fun prePopulate_cachesImagesUpToTheCacheSize() = scope.runTest { - testSubject.prePopulate(listOf(uriOne, uriTwo)) + testSubject.prePopulate(listOf(uriOne to previewSize, uriTwo to previewSize)) verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) verify(contentResolver, never()).loadThumbnail(uriTwo, imageSize, null) - testSubject(uriOne) + testSubject(uriOne, previewSize) verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) } @Test fun invoke_returnCachedImageWhenCalledTwice() = scope.runTest { - testSubject(uriOne) - testSubject(uriOne) + testSubject(uriOne, previewSize) + testSubject(uriOne, previewSize) verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) } @@ -102,8 +103,8 @@ class ImagePreviewImageLoaderTest { @Test fun invoke_whenInstructed_doesNotCache() = scope.runTest { - testSubject(uriOne, false) - testSubject(uriOne, false) + testSubject(uriOne, previewSize, false) + testSubject(uriOne, previewSize, false) verify(contentResolver, times(2)).loadThumbnail(any(), any(), anyOrNull()) } @@ -120,8 +121,8 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, ) coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } + launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } + launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } scheduler.advanceUntilIdle() } @@ -131,10 +132,10 @@ class ImagePreviewImageLoaderTest { @Test fun invoke_oldRecordsEvictedFromTheCache() = scope.runTest { - testSubject(uriOne) - testSubject(uriTwo) - testSubject(uriTwo) - testSubject(uriOne) + testSubject(uriOne, previewSize) + testSubject(uriTwo, previewSize) + testSubject(uriTwo, previewSize) + testSubject(uriOne, previewSize) verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) verify(contentResolver, times(1)).loadThumbnail(uriTwo, imageSize, null) @@ -144,8 +145,8 @@ class ImagePreviewImageLoaderTest { fun invoke_doNotCacheNulls() = scope.runTest { whenever(contentResolver.loadThumbnail(any(), any(), anyOrNull())).thenReturn(null) - testSubject(uriOne) - testSubject(uriOne) + testSubject(uriOne, previewSize) + testSubject(uriOne, previewSize) verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) } @@ -162,7 +163,7 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, ) imageLoaderScope.cancel() - testSubject(uriOne) + testSubject(uriOne, previewSize) } @Test(expected = CancellationException::class) @@ -178,7 +179,8 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, ) coroutineScope { - val deferred = async(start = UNDISPATCHED) { testSubject(uriOne, false) } + val deferred = + async(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } imageLoaderScope.cancel() scheduler.advanceUntilIdle() deferred.await() @@ -198,11 +200,11 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, ) coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, true) } + launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } + launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, true) } scheduler.advanceUntilIdle() } - testSubject(uriOne, true) + testSubject(uriOne, previewSize, true) verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) } @@ -243,7 +245,7 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, testSemaphore, ) - testSubject(uriOne, false) + testSubject(uriOne, previewSize, false) verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) assertThat(acquireCount.get()).isEqualTo(1) @@ -281,7 +283,7 @@ class ImagePreviewImageLoaderTest { cacheSize = 1, testSemaphore, ) - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } + launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } verify(contentResolver, never()).loadThumbnail(any(), any(), anyOrNull()) @@ -324,7 +326,9 @@ class ImagePreviewImageLoaderTest { ) coroutineScope { repeat(requestCount) { - launch { testSubject(Uri.parse("content://org.pkg.app/image-$it.png")) } + launch { + testSubject(Uri.parse("content://org.pkg.app/image-$it.png"), previewSize) + } } yield() // wait for all requests to be dispatched diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt index bb67e084..1047d145 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt @@ -76,23 +76,25 @@ class ShareouselViewModelTest { scope = viewModelScope, ) } + private val previewHeight = 500 @Test fun headline_images() = runTest { assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 1") previewSelectionsRepository.selections.value = listOf( - PreviewModel( - uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = "image/png", - order = 0, - ), - PreviewModel( - uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = "image/jpeg", - order = 1, + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = "image/png", + order = 0, + ), + PreviewModel( + uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), + mimeType = "image/jpeg", + order = 1, + ) ) - ).associateBy { it.uri } + .associateBy { it.uri } runCurrent() assertThat(shareouselViewModel.headline.first()).isEqualTo("IMAGES: 2") } @@ -101,17 +103,18 @@ class ShareouselViewModelTest { fun headline_videos() = runTest { previewSelectionsRepository.selections.value = listOf( - PreviewModel( - uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = "video/mpeg", - order = 0, - ), - PreviewModel( - uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = "video/mpeg", - order = 1, + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = "video/mpeg", + order = 0, + ), + PreviewModel( + uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), + mimeType = "video/mpeg", + order = 1, + ) ) - ).associateBy { it.uri } + .associateBy { it.uri } runCurrent() assertThat(shareouselViewModel.headline.first()).isEqualTo("VIDEOS: 2") } @@ -120,17 +123,18 @@ class ShareouselViewModelTest { fun headline_mixed() = runTest { previewSelectionsRepository.selections.value = listOf( - PreviewModel( - uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = "image/jpeg", - order = 0, - ), - PreviewModel( - uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), - mimeType = "video/mpeg", - order = 1, + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = "image/jpeg", + order = 0, + ), + PreviewModel( + uri = Uri.fromParts("scheme1", "ssp1", "fragment1"), + mimeType = "video/mpeg", + order = 1, + ) ) - ).associateBy { it.uri } + .associateBy { it.uri } runCurrent() assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 2") } @@ -194,6 +198,7 @@ class ShareouselViewModelTest { mimeType = "video/mpeg", order = 0, ), + previewHeight, /* index = */ 1, viewModelScope, ) @@ -245,6 +250,7 @@ class ShareouselViewModelTest { mimeType = "video/mpeg", order = 1, ), + previewHeight, /* index = */ 1, viewModelScope, ) @@ -308,10 +314,11 @@ class ShareouselViewModelTest { this.targetIntentModifier = targetIntentModifier previewSelectionsRepository.selections.value = PreviewModel( - uri = Uri.fromParts("scheme", "ssp", "fragment"), - mimeType = null, - order = 0, - ).let { mapOf(it.uri to it) } + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = null, + order = 0, + ) + .let { mapOf(it.uri to it) } payloadToggleImageLoader = FakeImageLoader( initialBitmaps = diff --git a/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt b/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt index 4f4223c0..b1e8593d 100644 --- a/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt @@ -18,6 +18,7 @@ package com.android.intentresolver.widget import android.graphics.Bitmap import android.net.Uri +import android.util.Size import com.android.intentresolver.captureMany import com.android.intentresolver.mock import com.android.intentresolver.widget.ScrollableImagePreviewView.BatchPreviewLoader @@ -49,6 +50,7 @@ class BatchPreviewLoaderTest { private val testScope = CoroutineScope(dispatcher) private val onCompletion = mock<() -> Unit>() private val onUpdate = mock<(List) -> Unit>() + private val previewSize = Size(500, 500) @Before fun setup() { @@ -71,6 +73,7 @@ class BatchPreviewLoaderTest { BatchPreviewLoader( imageLoader, previews(uriOne, uriTwo), + previewSize, totalItemCount = 2, onUpdate, onCompletion @@ -94,6 +97,7 @@ class BatchPreviewLoaderTest { BatchPreviewLoader( imageLoader, previews(uriOne, uriTwo, uriThree), + previewSize, totalItemCount = 3, onUpdate, onCompletion @@ -122,7 +126,14 @@ class BatchPreviewLoaderTest { } imageLoader.setUriLoadingOrder(*loadingOrder) val testSubject = - BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion) + BatchPreviewLoader( + imageLoader, + previews(*uris), + previewSize, + uris.size, + onUpdate, + onCompletion + ) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() @@ -151,7 +162,14 @@ class BatchPreviewLoaderTest { val expectedUris = Array(uris.size / 2) { createUri(it * 2 + 1) } imageLoader.setUriLoadingOrder(*loadingOrder) val testSubject = - BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion) + BatchPreviewLoader( + imageLoader, + previews(*uris), + previewSize, + uris.size, + onUpdate, + onCompletion + ) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() @@ -166,7 +184,9 @@ class BatchPreviewLoaderTest { private fun createUri(idx: Int): Uri = Uri.parse("content://org.pkg.app/image-$idx.png") private fun fail(uri: Uri) = uri to false + private fun succeed(uri: Uri) = uri to true + private fun previews(vararg uris: Uri) = uris .fold(ArrayList(uris.size)) { acc, uri -> @@ -175,7 +195,7 @@ class BatchPreviewLoaderTest { .asFlow() } -private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) -> Bitmap? { +private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Size, Boolean) -> Bitmap? { private val loadingOrder = ArrayDeque>() private val pendingRequests = LinkedHashMap>() private val flow = MutableSharedFlow(replay = 1) @@ -203,7 +223,7 @@ private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) -> loadingOrder.addAll(uris) } - override suspend fun invoke(uri: Uri, cache: Boolean): Bitmap? { + override suspend fun invoke(uri: Uri, size: Size, cache: Boolean): Bitmap? { val deferred = pendingRequests.getOrPut(uri) { CompletableDeferred() } flow.tryEmit(Unit) return deferred.await() -- cgit v1.2.3-59-g8ed1b From 88c000d153a9cb0dacee0ca6ffeed196cdbf1d56 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 22 Jul 2024 15:43:08 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I9a2c47097565f3ec77fdb6ba5e8e4d1594214d74 --- java/res/values-de/strings.xml | 2 +- java/res/values-es-rUS/strings.xml | 2 +- java/res/values-es/strings.xml | 2 +- java/res/values-fr-rCA/strings.xml | 2 +- java/res/values-it/strings.xml | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'java') diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml index c6d26eb2..e8066dcb 100644 --- a/java/res/values-de/strings.xml +++ b/java/res/values-de/strings.xml @@ -46,7 +46,7 @@ "Aktion auswählen" "Diese Aktion kann von keiner App ausgeführt werden." "Du verwendest diese App außerhalb deines Arbeitsprofils" - "Du verwendest diese App in deinem Arbeitsprofil." + "Du verwendest diese App in deinem Arbeitsprofil" "Immer" "Nur diesmal" "%1$s unterstützt das Arbeitsprofil nicht" diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml index a76fba3a..7c96aa65 100644 --- a/java/res/values-es-rUS/strings.xml +++ b/java/res/values-es-rUS/strings.xml @@ -59,7 +59,7 @@ "Compartir vínculo" "{count,plural, =1{Compartir la imagen}many{Compartir # de imágenes}other{Compartir # imágenes}}" "{count,plural, =1{Compartiendo video}many{Compartiendo # de videos}other{Compartiendo # videos}}" - "{count,plural, =1{Se compartirá # archivo}many{Se compartirán # de archivos}other{Se compartirán # archivos}}" + "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # de archivos}other{Compartiendo # archivos}}" "{count,plural, =1{Compartir imagen con texto}many{Compartir # de imágenes con texto}other{Compartir # imágenes con texto}}" "{count,plural, =1{Compartir imagen con vínculo}many{Compartir # de imágenes con vínculo}other{Compartir # imágenes con vínculo}}" "{count,plural, =1{Compartir video con texto}many{Compartir # de videos con texto}other{Compartir # videos con texto}}" diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml index 5e63be7e..ca392a1f 100644 --- a/java/res/values-es/strings.xml +++ b/java/res/values-es/strings.xml @@ -57,7 +57,7 @@ "{count,plural, =1{y # archivo más}many{y # archivos más}other{y # archivos más}}" "Compartiendo texto" "Compartiendo enlace" - "{count,plural, =1{Compartir imagen}many{Compartir # imágenes}other{Compartir # imágenes}}" + "{count,plural, =1{Compartiendo imagen}many{Compartiendo # imágenes}other{Compartiendo # imágenes}}" "{count,plural, =1{Compartiendo vídeo}many{Compartiendo # vídeos}other{Compartiendo # vídeos}}" "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # archivos}other{Compartiendo # archivos}}" "{count,plural, =1{Compartiendo imagen con texto}many{Compartiendo # imágenes con texto}other{Compartiendo # imágenes con texto}}" diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml index 7d2716c2..9d1a8bb9 100644 --- a/java/res/values-fr-rCA/strings.xml +++ b/java/res/values-fr-rCA/strings.xml @@ -53,7 +53,7 @@ "Épingler %1$s" "Annuler l\'épinglage de %1$s" "Modifier" - "{count,plural, =1{+ # fichier}one{+ # fichier}many{+ # de fichiers}other{+ # fichiers}}" + "{count,plural, =1{+ # fichier}one{+ # fichier}many{+ # de fichiers}other{+ # fichiers}}" "{count,plural, =1{et # fichier supplémentaire}one{et # fichier supplémentaire}many{et # de fichiers supplémentaires}other{et # fichiers supplémentaires}}" "Partage de texte" "Partage d\'un lien" diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml index 75fe0b77..702dc48e 100644 --- a/java/res/values-it/strings.xml +++ b/java/res/values-it/strings.xml @@ -53,8 +53,8 @@ "Fissa %1$s" "Sblocca %1$s" "Modifica" - "{count,plural, =1{+ # file}many{+ # file}other{+ # file}}" - "{count,plural, =1{+ # altro file}many{+ altri # file}other{+ altri # file}}" + "{count,plural, =1{+ # file}many{+ # di file}other{+ # file}}" + "{count,plural, =1{+ # altro file}many{+ altri # di file}other{+ altri # file}}" "Condivisione del testo" "Condivisione del link" "{count,plural, =1{Condivisione dell\'immagine}many{Condivisione di # immagini}other{Condivisione di # immagini}}" @@ -65,7 +65,7 @@ "{count,plural, =1{Condivisione video con messaggio in corso…}many{Condivisione # video con messaggio in corso…}other{Condivisione # video con messaggio in corso…}}" "{count,plural, =1{Condivisione video con link in corso…}many{Condivisione # video con link in corso…}other{Condivisione # video con link in corso…}}" "{count,plural, =1{Condivisione file con messaggio in corso…}many{Condivisione # file con messaggio in corso…}other{Condivisione # file con messaggio in corso…}}" - "{count,plural, =1{Condivisione file con link in corso…}many{Condivisione # file con link in corso…}other{Condivisione # file con link in corso…}}" + "{count,plural, =1{Condivisione file con link in corso…}many{Condivisione # di file con link in corso…}other{Condivisione # file con link in corso…}}" "Condivisione album" "{count,plural, =1{Soltanto l\'immagine}many{Soltanto le immagini}other{Soltanto le immagini}}" "{count,plural, =1{Soltanto il video}many{Soltanto i video}other{Soltanto i video}}" -- cgit v1.2.3-59-g8ed1b From a7b90069598ad145a0a2a33a83af23122433a045 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Mon, 22 Jul 2024 18:59:10 -0700 Subject: Fix drawer offset calculation when rotating from a non-initial profile Fix: 344057117 Test: manual testing Flag: com.android.intentresolver.fix_drawer_offset_on_config_change Change-Id: I0fb633b02299c0a4845bfa09bbbb234915ccb487 --- aconfig/FeatureFlags.aconfig | 10 ++++++++++ .../com/android/intentresolver/ChooserActivity.java | 18 ++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 87ae2f1d..df1f0030 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -36,6 +36,16 @@ flag { } } +flag { + name: "fix_drawer_offset_on_config_change" + namespace: "intentresolver" + description: "Fix drawer offset calculation after rotating when in a non-initial tab" + bug: "344057117" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "fix_empty_state_padding" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 7e0f148d..84f29e21 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -204,7 +204,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private static final String TAB_TAG_PERSONAL = "personal"; private static final String TAB_TAG_WORK = "work"; - private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key"; + private static final String LAST_SHOWN_PROFILE = "last_shown_tab_key"; public static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; private int mLayoutId; @@ -349,6 +349,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserHelper.setOnPendingSelection(this::onPendingSelection); } } + private int mInitialProfile = -1; @Override protected final void onStart() { @@ -410,7 +411,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements protected final void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mViewPager != null) { - outState.putInt(LAST_SHOWN_TAB_KEY, mViewPager.getCurrentItem()); + outState.putInt( + LAST_SHOWN_PROFILE, mChooserMultiProfilePagerAdapter.getActiveProfile()); } } @@ -679,6 +681,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mRequest.getModifyShareAction() != null ); mEnterTransitionAnimationDelegate.postponeTransition(); + mInitialProfile = findSelectedProfile(); Tracer.INSTANCE.markLaunched(); } @@ -828,7 +831,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { if (mViewPager != null) { - mViewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); + int profile = savedInstanceState.getInt(LAST_SHOWN_PROFILE); + int profileNumber = mChooserMultiProfilePagerAdapter.getPageNumberForProfile(profile); + if (profileNumber != -1) { + mViewPager.setCurrentItem(profileNumber); + mInitialProfile = profile; + } } mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); } @@ -2257,7 +2265,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } int currentProfile = mChooserMultiProfilePagerAdapter.getActiveProfile(); - int initialProfile = findSelectedProfile(); + int initialProfile = Flags.fixDrawerOffsetOnConfigChange() + ? mInitialProfile + : findSelectedProfile(); if (currentProfile != initialProfile) { return; } -- cgit v1.2.3-59-g8ed1b From 78e45e942cce4144fe772955631794389a41c156 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Tue, 23 Jul 2024 13:56:03 -0700 Subject: Do not crash if max aspect ratio falls below min aspect ratio The effective aspect ratio for the item will be in the range of [minOf(MIN_ASPECT_RATIO, maxAspectRatio), maxOf(MIN_ASPECT_RATIO, maxAspectRatio)] i.e. we always try to fit items into the view port even if the effective AR will be lower than the minimum AR. Bug: 351094699 Test: manual testing Flag: EXEMPT bugfix Change-Id: I6ae117ff816f47185caebc68c7fd3c670eacff33 --- .../contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index 9ac36a87..e8d75c8f 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -205,7 +205,7 @@ private fun ShareouselCard(viewModel: ShareouselPreviewViewModel, maxAspectRatio onValueChange = { scope.launch { viewModel.setSelected(it) } }, ) ) { state -> - val aspectRatio = viewModel.aspectRatio.coerceIn(MIN_ASPECT_RATIO, maxAspectRatio) + val aspectRatio = minOf(maxAspectRatio, maxOf(MIN_ASPECT_RATIO, viewModel.aspectRatio)) if (state is ValueUpdate.Value) { state.getOrDefault(null).let { bitmap -> ShareouselCard( -- cgit v1.2.3-59-g8ed1b From 826936d3e69a4fccb56a5b1f7038d266f7ffb7bf Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Tue, 2 Jul 2024 09:35:27 -0700 Subject: Unified preview image loader. Replace ImagePreviewImageLoader and CachingPreviewImageLoader with the new class, PreviewImageLoader. Fix: 348665058 Fix: 343819590 Test: atest IntentResolver-tests-unit Test: atest IntentResolver-tests-activity Test: inject debugging logs for image requests, loadings, and cancellations and verify common scenarios. Flag: com.android.intentresolver.preview_image_loader Change-Id: Ib54ea0d538cd2cbaef6041f09eeb2b9eb6ada4cf --- aconfig/FeatureFlags.aconfig | 7 + .../CachingImagePreviewImageLoader.kt | 2 +- .../contentpreview/ImageLoaderModule.kt | 15 +- .../contentpreview/PreviewImageLoader.kt | 197 ++++++++ .../contentpreview/ThumbnailLoader.kt | 26 +- .../ui/viewmodel/ShareouselViewModel.kt | 186 ++++---- .../intentresolver/ChooserActivityTest.java | 5 + .../contentpreview/FakeThumbnailLoader.kt | 13 +- .../contentpreview/PreviewImageLoaderTest.kt | 497 +++++++++++++++++++++ 9 files changed, 850 insertions(+), 98 deletions(-) create mode 100644 java/src/com/android/intentresolver/contentpreview/PreviewImageLoader.kt create mode 100644 tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 87ae2f1d..2c3f66fb 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -72,3 +72,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "preview_image_loader" + namespace: "intentresolver" + description: "Use the unified preview image loader for all preview variations; support variable preview sizes." + bug: "348665058" +} diff --git a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt index f60f550e..847fcc82 100644 --- a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt @@ -87,7 +87,7 @@ constructor( private suspend fun loadUncachedImage(uri: Uri): Bitmap? = withContext(bgDispatcher) { - runCatching { semaphore.withPermit { thumbnailLoader.invoke(uri) } } + runCatching { semaphore.withPermit { thumbnailLoader.loadThumbnail(uri) } } .onFailure { ensureActive() Log.d(TAG, "Failed to load preview for $uri", it) diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt index 17d05099..27e817db 100644 --- a/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt +++ b/java/src/com/android/intentresolver/contentpreview/ImageLoaderModule.kt @@ -17,6 +17,7 @@ package com.android.intentresolver.contentpreview import android.content.res.Resources +import com.android.intentresolver.Flags import com.android.intentresolver.R import com.android.intentresolver.inject.ApplicationOwned import dagger.Binds @@ -24,15 +25,25 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent +import javax.inject.Provider @Module @InstallIn(ViewModelComponent::class) interface ImageLoaderModule { - @Binds fun imageLoader(previewImageLoader: ImagePreviewImageLoader): ImageLoader - @Binds fun thumbnailLoader(thumbnailLoader: ThumbnailLoaderImpl): ThumbnailLoader companion object { + @Provides + fun imageLoader( + imagePreviewImageLoader: Provider, + previewImageLoader: Provider + ): ImageLoader = + if (Flags.previewImageLoader()) { + previewImageLoader.get() + } else { + imagePreviewImageLoader.get() + } + @Provides @ThumbnailSize fun thumbnailSize(@ApplicationOwned resources: Resources): Int = diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/PreviewImageLoader.kt new file mode 100644 index 00000000..b10f7ef9 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/PreviewImageLoader.kt @@ -0,0 +1,197 @@ +/* + * 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 + * + * 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.contentpreview + +import android.graphics.Bitmap +import android.net.Uri +import android.util.Log +import android.util.Size +import androidx.collection.lruCache +import com.android.intentresolver.inject.Background +import com.android.intentresolver.inject.ViewModelOwned +import javax.annotation.concurrent.GuardedBy +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit + +private const val TAG = "PayloadSelImageLoader" + +/** + * Implements preview image loading for the payload selection UI. Cancels preview loading for items + * that has been evicted from the cache at the expense of a possible request duplication (deemed + * unlikely). + */ +class PreviewImageLoader +@Inject +constructor( + @ViewModelOwned private val scope: CoroutineScope, + @PreviewCacheSize private val cacheSize: Int, + @ThumbnailSize private val defaultPreviewSize: Int, + private val thumbnailLoader: ThumbnailLoader, + @Background private val bgDispatcher: CoroutineDispatcher, + @PreviewMaxConcurrency maxSimultaneousRequests: Int = 4, +) : ImageLoader { + + private val contentResolverSemaphore = Semaphore(maxSimultaneousRequests) + + private val lock = Any() + @GuardedBy("lock") private val runningRequests = hashMapOf() + @GuardedBy("lock") + private val cache = + lruCache( + maxSize = cacheSize, + onEntryRemoved = { _, _, oldRec, newRec -> + if (oldRec !== newRec) { + onRecordEvictedFromCache(oldRec) + } + } + ) + + override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? = + loadImageInternal(uri, size, caching) + + override fun prePopulate(uriSizePairs: List>) { + uriSizePairs.asSequence().take(cacheSize).forEach { uri -> + scope.launch { loadImageInternal(uri.first, uri.second, caching = true) } + } + } + + private suspend fun loadImageInternal(uri: Uri, size: Size, caching: Boolean): Bitmap? { + return withRequestRecord(uri, caching) { record -> + val newSize = sanitize(size) + val newMetric = newSize.metric + record + .also { + // set the requested size to the max of the new and the previous value; input + // will emit if the resulted value is greater than the old one + it.input.update { oldSize -> + if (oldSize == null || oldSize.metric < newSize.metric) newSize else oldSize + } + } + .output + // filter out bitmaps of a lower resolution than that we're requesting + .filter { it is BitmapLoadingState.Loaded && newMetric <= it.size.metric } + .firstOrNull() + ?.let { (it as BitmapLoadingState.Loaded).bitmap } + } + } + + private suspend fun withRequestRecord( + uri: Uri, + caching: Boolean, + block: suspend (RequestRecord) -> Bitmap? + ): Bitmap? { + val record = trackRecordRunning(uri, caching) + return try { + block(record) + } finally { + untrackRecordRunning(uri, record) + } + } + + private fun trackRecordRunning(uri: Uri, caching: Boolean): RequestRecord = + synchronized(lock) { + runningRequests + .getOrPut(uri) { cache[uri] ?: createRecord(uri) } + .also { record -> + record.clientCount++ + if (caching) { + cache.put(uri, record) + } + } + } + + private fun untrackRecordRunning(uri: Uri, record: RequestRecord) { + synchronized(lock) { + record.clientCount-- + if (record.clientCount <= 0) { + runningRequests.remove(uri) + val result = record.output.value + if (cache[uri] == null) { + record.loadingJob.cancel() + } else if (result is BitmapLoadingState.Loaded && result.bitmap == null) { + cache.remove(uri) + } + } + } + } + + private fun onRecordEvictedFromCache(record: RequestRecord) { + synchronized(lock) { + if (record.clientCount <= 0) { + record.loadingJob.cancel() + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun createRecord(uri: Uri): RequestRecord { + // use a StateFlow with sentinel values to avoid using SharedFlow that is deemed dangerous + val input = MutableStateFlow(null) + val output = MutableStateFlow(BitmapLoadingState.Loading) + val job = + scope.launch(bgDispatcher) { + // the image loading pipeline: input -- a desired image size, output -- a bitmap + input + .filterNotNull() + .mapLatest { size -> BitmapLoadingState.Loaded(size, loadBitmap(uri, size)) } + .collect { output.tryEmit(it) } + } + return RequestRecord(input, output, job, clientCount = 0) + } + + private suspend fun loadBitmap(uri: Uri, size: Size): Bitmap? = + contentResolverSemaphore.withPermit { + runCatching { thumbnailLoader.loadThumbnail(uri, size) } + .onFailure { Log.d(TAG, "failed to load $uri preview", it) } + .getOrNull() + } + + private class RequestRecord( + /** The image loading pipeline input: desired preview size */ + val input: MutableStateFlow, + /** The image loading pipeline output */ + val output: MutableStateFlow, + /** The image loading pipeline job */ + val loadingJob: Job, + @GuardedBy("lock") var clientCount: Int, + ) + + private sealed interface BitmapLoadingState { + data object Loading : BitmapLoadingState + + data class Loaded(val size: Size, val bitmap: Bitmap?) : BitmapLoadingState + } + + private fun sanitize(size: Size?): Size = + size?.takeIf { it.width > 0 && it.height > 0 } + ?: Size(defaultPreviewSize, defaultPreviewSize) +} + +private val Size.metric + get() = maxOf(width, height) diff --git a/java/src/com/android/intentresolver/contentpreview/ThumbnailLoader.kt b/java/src/com/android/intentresolver/contentpreview/ThumbnailLoader.kt index 9f1d50da..e8afa480 100644 --- a/java/src/com/android/intentresolver/contentpreview/ThumbnailLoader.kt +++ b/java/src/com/android/intentresolver/contentpreview/ThumbnailLoader.kt @@ -20,10 +20,25 @@ import android.content.ContentResolver import android.graphics.Bitmap import android.net.Uri import android.util.Size +import com.android.intentresolver.util.withCancellationSignal import javax.inject.Inject /** Interface for objects that can attempt load a [Bitmap] from a [Uri]. */ -interface ThumbnailLoader : suspend (Uri) -> Bitmap? +interface ThumbnailLoader { + /** + * Loads a thumbnail for the given [uri]. + * + * The size of the thumbnail is determined by the implementation. + */ + suspend fun loadThumbnail(uri: Uri): Bitmap? + + /** + * Loads a thumbnail for the given [uri] and [size]. + * + * The [size] is the size of the thumbnail in pixels. + */ + suspend fun loadThumbnail(uri: Uri, size: Size): Bitmap? +} /** Default implementation of [ThumbnailLoader]. */ class ThumbnailLoaderImpl @@ -35,6 +50,11 @@ constructor( private val size = Size(thumbnailSize, thumbnailSize) - override suspend fun invoke(uri: Uri): Bitmap = - contentResolver.loadThumbnail(uri, size, /* signal = */ null) + override suspend fun loadThumbnail(uri: Uri): Bitmap = + contentResolver.loadThumbnail(uri, size, /* signal= */ null) + + override suspend fun loadThumbnail(uri: Uri, size: Size): Bitmap = + withCancellationSignal { signal -> + contentResolver.loadThumbnail(uri, size, signal) + } } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt index f1e65f73..6f8be1ff 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt @@ -16,10 +16,12 @@ package com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel import android.util.Size +import com.android.intentresolver.Flags import com.android.intentresolver.contentpreview.CachingImagePreviewImageLoader import com.android.intentresolver.contentpreview.HeadlineGenerator import com.android.intentresolver.contentpreview.ImageLoader import com.android.intentresolver.contentpreview.MimeTypeClassifier +import com.android.intentresolver.contentpreview.PreviewImageLoader import com.android.intentresolver.contentpreview.payloadtoggle.domain.cursor.PayloadToggle import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.ChooserRequestInteractor import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.CustomActionsInteractor @@ -30,11 +32,11 @@ import com.android.intentresolver.contentpreview.payloadtoggle.shared.ContentTyp import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel import com.android.intentresolver.inject.ViewModelOwned -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent +import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -65,98 +67,106 @@ data class ShareouselViewModel( @Module @InstallIn(ViewModelComponent::class) -interface ShareouselViewModelModule { +object ShareouselViewModelModule { - @Binds @PayloadToggle fun imageLoader(imageLoader: CachingImagePreviewImageLoader): ImageLoader + @Provides + @PayloadToggle + fun imageLoader( + cachingImageLoader: Provider, + previewImageLoader: Provider + ): ImageLoader = + if (Flags.previewImageLoader()) { + previewImageLoader.get() + } else { + cachingImageLoader.get() + } - companion object { - @Provides - fun create( - interactor: SelectablePreviewsInteractor, - @PayloadToggle imageLoader: ImageLoader, - actionsInteractor: CustomActionsInteractor, - headlineGenerator: HeadlineGenerator, - selectionInteractor: SelectionInteractor, - chooserRequestInteractor: ChooserRequestInteractor, - mimeTypeClassifier: MimeTypeClassifier, - // TODO: remove if possible - @ViewModelOwned scope: CoroutineScope, - ): ShareouselViewModel { - val keySet = - interactor.previews.stateIn( - scope, - SharingStarted.Eagerly, - initialValue = null, - ) - return ShareouselViewModel( - headline = - selectionInteractor.aggregateContentType.zip( - selectionInteractor.amountSelected - ) { contentType, numItems -> - when (contentType) { - ContentType.Other -> headlineGenerator.getFilesHeadline(numItems) - ContentType.Image -> headlineGenerator.getImagesHeadline(numItems) - ContentType.Video -> headlineGenerator.getVideosHeadline(numItems) + @Provides + fun create( + interactor: SelectablePreviewsInteractor, + @PayloadToggle imageLoader: ImageLoader, + actionsInteractor: CustomActionsInteractor, + headlineGenerator: HeadlineGenerator, + selectionInteractor: SelectionInteractor, + chooserRequestInteractor: ChooserRequestInteractor, + mimeTypeClassifier: MimeTypeClassifier, + // TODO: remove if possible + @ViewModelOwned scope: CoroutineScope, + ): ShareouselViewModel { + val keySet = + interactor.previews.stateIn( + scope, + SharingStarted.Eagerly, + initialValue = null, + ) + return ShareouselViewModel( + headline = + selectionInteractor.aggregateContentType.zip(selectionInteractor.amountSelected) { + contentType, + numItems -> + when (contentType) { + ContentType.Other -> headlineGenerator.getFilesHeadline(numItems) + ContentType.Image -> headlineGenerator.getImagesHeadline(numItems) + ContentType.Video -> headlineGenerator.getVideosHeadline(numItems) + } + }, + metadataText = chooserRequestInteractor.metadataText, + previews = keySet, + actions = + actionsInteractor.customActions.map { actions -> + actions.mapIndexedNotNull { i, model -> + val icon = model.icon + val label = model.label + if (icon == null && label.isBlank()) { + null + } else { + ActionChipViewModel( + label = label.toString(), + icon = model.icon, + onClicked = { model.performAction(i) }, + ) } - }, - metadataText = chooserRequestInteractor.metadataText, - previews = keySet, - actions = - actionsInteractor.customActions.map { actions -> - actions.mapIndexedNotNull { i, model -> - val icon = model.icon - val label = model.label - if (icon == null && label.isBlank()) { - null - } else { - ActionChipViewModel( - label = label.toString(), - icon = model.icon, - onClicked = { model.performAction(i) }, + } + }, + preview = { key, previewHeight, index, previewScope -> + keySet.value?.maybeLoad(index) + val previewInteractor = interactor.preview(key) + val contentType = + when { + mimeTypeClassifier.isImageType(key.mimeType) -> ContentType.Image + mimeTypeClassifier.isVideoType(key.mimeType) -> ContentType.Video + else -> ContentType.Other + } + val initialBitmapValue = + key.previewUri?.let { + imageLoader.getCachedBitmap(it)?.let { ValueUpdate.Value(it) } + } ?: ValueUpdate.Absent + ShareouselPreviewViewModel( + bitmapLoadState = + flow { + val previewWidth = + if (key.aspectRatio > 0) { + previewHeight.toFloat() / key.aspectRatio + } else { + previewHeight + } + .toInt() + emit( + key.previewUri?.let { + ValueUpdate.Value( + imageLoader(it, Size(previewWidth, previewHeight)) + ) + } ?: ValueUpdate.Absent ) } - } - }, - preview = { key, previewHeight, index, previewScope -> - keySet.value?.maybeLoad(index) - val previewInteractor = interactor.preview(key) - val contentType = - when { - mimeTypeClassifier.isImageType(key.mimeType) -> ContentType.Image - mimeTypeClassifier.isVideoType(key.mimeType) -> ContentType.Video - else -> ContentType.Other - } - val initialBitmapValue = - key.previewUri?.let { - imageLoader.getCachedBitmap(it)?.let { ValueUpdate.Value(it) } - } ?: ValueUpdate.Absent - ShareouselPreviewViewModel( - bitmapLoadState = - flow { - val previewWidth = - if (key.aspectRatio > 0) { - previewHeight.toFloat() / key.aspectRatio - } else { - previewHeight - } - .toInt() - emit( - key.previewUri?.let { - ValueUpdate.Value( - imageLoader(it, Size(previewWidth, previewHeight)) - ) - } ?: ValueUpdate.Absent - ) - } - .stateIn(previewScope, SharingStarted.Eagerly, initialBitmapValue), - contentType = contentType, - isSelected = previewInteractor.isSelected, - setSelected = previewInteractor::setSelected, - aspectRatio = key.aspectRatio, - ) - }, - ) - } + .stateIn(previewScope, SharingStarted.Eagerly, initialBitmapValue), + contentType = contentType, + isSelected = previewInteractor.isSelected, + setSelected = previewInteractor::setSelected, + aspectRatio = key.aspectRatio, + ) + }, + ) } } diff --git a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java index 620ac555..e103e57b 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java +++ b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java @@ -124,6 +124,7 @@ import com.android.intentresolver.contentpreview.ImageLoaderModule; import com.android.intentresolver.contentpreview.PreviewCacheSize; import com.android.intentresolver.contentpreview.PreviewMaxConcurrency; import com.android.intentresolver.contentpreview.ThumbnailLoader; +import com.android.intentresolver.contentpreview.ThumbnailSize; import com.android.intentresolver.data.repository.FakeUserRepository; import com.android.intentresolver.data.repository.UserRepository; import com.android.intentresolver.data.repository.UserRepositoryModule; @@ -284,6 +285,10 @@ public class ChooserActivityTest { @PreviewMaxConcurrency int mPreviewMaxConcurrency = 4; + @BindValue + @ThumbnailSize + int mPreviewThumbnailSize = 500; + @BindValue ThumbnailLoader mThumbnailLoader = new FakeThumbnailLoader(); diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt b/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt index d3fdf17d..33969eb7 100644 --- a/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt +++ b/tests/shared/src/com/android/intentresolver/contentpreview/FakeThumbnailLoader.kt @@ -18,18 +18,23 @@ package com.android.intentresolver.contentpreview import android.graphics.Bitmap import android.net.Uri +import android.util.Size /** Fake implementation of [ThumbnailLoader] for use in testing. */ -class FakeThumbnailLoader : ThumbnailLoader { +class FakeThumbnailLoader(private val defaultSize: Size = Size(100, 100)) : ThumbnailLoader { - val fakeInvoke = mutableMapOf Bitmap?>() + val fakeInvoke = mutableMapOf Bitmap?>() val invokeCalls = mutableListOf() var unfinishedInvokeCount = 0 - override suspend fun invoke(uri: Uri): Bitmap? { + override suspend fun loadThumbnail(uri: Uri): Bitmap? = getBitmap(uri, defaultSize) + + override suspend fun loadThumbnail(uri: Uri, size: Size): Bitmap? = getBitmap(uri, size) + + private suspend fun getBitmap(uri: Uri, size: Size): Bitmap? { invokeCalls.add(uri) unfinishedInvokeCount++ - val result = fakeInvoke[uri]?.invoke() + val result = fakeInvoke[uri]?.invoke(size) unfinishedInvokeCount-- return result } diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt new file mode 100644 index 00000000..8293264c --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt @@ -0,0 +1,497 @@ +/* + * 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 + * + * 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.contentpreview + +import android.graphics.Bitmap +import android.net.Uri +import android.util.Size +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class PreviewImageLoaderTest { + private val scope = TestScope() + + @Test + fun test_cachingImageRequest_imageCached() = + scope.runTest { + val uri = createUri(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { size -> createBitmap(size.width, size.height) } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val b1 = testSubject.invoke(uri, Size(200, 100)) + val b2 = testSubject.invoke(uri, Size(200, 100), caching = false) + assertThat(b1).isEqualTo(b2) + assertThat(thumbnailLoader.invokeCalls).hasSize(1) + } + + @Test + fun test_nonCachingImageRequest_imageNotCached() = + scope.runTest { + val uri = createUri(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { size -> createBitmap(size.width, size.height) } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + testSubject.invoke(uri, Size(200, 100), caching = false) + testSubject.invoke(uri, Size(200, 100), caching = false) + assertThat(thumbnailLoader.invokeCalls).hasSize(2) + } + + @Test + fun test_twoSimultaneousImageRequests_requestsDeduplicated() = + scope.runTest { + val uri = createUri(0) + val loadingStartedDeferred = CompletableDeferred() + val bitmapDeferred = CompletableDeferred() + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { + loadingStartedDeferred.complete(Unit) + bitmapDeferred.await() + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val b1Deferred = async { testSubject.invoke(uri, Size(200, 100), caching = false) } + loadingStartedDeferred.await() + val b2Deferred = + async(start = CoroutineStart.UNDISPATCHED) { + testSubject.invoke(uri, Size(200, 100), caching = true) + } + bitmapDeferred.complete(createBitmap(200, 200)) + + val b1 = b1Deferred.await() + val b2 = b2Deferred.await() + assertThat(b1).isEqualTo(b2) + assertThat(thumbnailLoader.invokeCalls).hasSize(1) + } + + @Test + fun test_cachingRequestCancelledAndEvoked_imageLoadingCancelled() = + scope.runTest { + val uriOne = createUri(1) + val uriTwo = createUri(2) + val loadingStartedDeferred = CompletableDeferred() + val cancelledRequests = mutableSetOf() + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uriOne] = { + loadingStartedDeferred.complete(Unit) + try { + awaitCancellation() + } catch (e: CancellationException) { + cancelledRequests.add(uriOne) + throw e + } + } + fakeInvoke[uriTwo] = { createBitmap(200, 200) } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + cacheSize = 1, + defaultPreviewSize = 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val jobOne = launch { testSubject.invoke(uriOne, Size(200, 100)) } + loadingStartedDeferred.await() + jobOne.cancel() + scope.runCurrent() + + assertThat(cancelledRequests).isEmpty() + + // second URI should evict the first item from the cache + testSubject.invoke(uriTwo, Size(200, 100)) + + assertThat(thumbnailLoader.invokeCalls).hasSize(2) + assertThat(cancelledRequests).containsExactly(uriOne) + } + + @Test + fun test_nonCachingRequestClientCancels_imageLoadingCancelled() = + scope.runTest { + val uri = createUri(1) + val loadingStartedDeferred = CompletableDeferred() + val cancelledRequests = mutableSetOf() + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { + loadingStartedDeferred.complete(Unit) + try { + awaitCancellation() + } catch (e: CancellationException) { + cancelledRequests.add(uri) + throw e + } + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + cacheSize = 1, + defaultPreviewSize = 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val job = launch { testSubject.invoke(uri, Size(200, 100), caching = false) } + loadingStartedDeferred.await() + job.cancel() + scope.runCurrent() + + assertThat(cancelledRequests).containsExactly(uri) + } + + @Test + fun test_requestHigherResImage_newImageLoaded() = + scope.runTest { + val uri = createUri(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { size -> createBitmap(size.width, size.height) } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val b1 = testSubject.invoke(uri, Size(100, 100)) + val b2 = testSubject.invoke(uri, Size(200, 200)) + assertThat(b1).isNotNull() + assertThat(b1!!.width).isEqualTo(100) + assertThat(b2).isNotNull() + assertThat(b2!!.width).isEqualTo(200) + assertThat(thumbnailLoader.invokeCalls).hasSize(2) + } + + @Test + fun test_imageLoadingThrowsException_returnsNull() = + scope.runTest { + val uri = createUri(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { throw SecurityException("test") } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val bitmap = testSubject.invoke(uri, Size(100, 100)) + assertThat(bitmap).isNull() + } + + @Test + fun test_requestHigherResImage_cancelsLowerResLoading() = + scope.runTest { + val uri = createUri(0) + val cancelledRequestCount = atomic(0) + val imageLoadingStarted = CompletableDeferred() + val bitmapDeferred = CompletableDeferred() + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { + imageLoadingStarted.complete(Unit) + try { + bitmapDeferred.await() + } catch (e: CancellationException) { + cancelledRequestCount.getAndIncrement() + throw e + } + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val lowResSize = 100 + val highResSize = 200 + launch(start = CoroutineStart.UNDISPATCHED) { + testSubject.invoke(uri, Size(lowResSize, lowResSize)) + } + imageLoadingStarted.await() + val result = async { testSubject.invoke(uri, Size(highResSize, highResSize)) } + runCurrent() + assertThat(cancelledRequestCount.value).isEqualTo(1) + + bitmapDeferred.complete(createBitmap(highResSize, highResSize)) + val bitmap = result.await() + assertThat(bitmap).isNotNull() + assertThat(bitmap!!.width).isEqualTo(highResSize) + assertThat(thumbnailLoader.invokeCalls).hasSize(2) + } + + @Test + fun test_requestLowerResImage_cachedHigherResImageReturned() = + scope.runTest { + val uri = createUri(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { size -> createBitmap(size.width, size.height) } + } + val lowResSize = 100 + val highResSize = 200 + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val b1 = testSubject.invoke(uri, Size(highResSize, highResSize)) + val b2 = testSubject.invoke(uri, Size(lowResSize, lowResSize)) + assertThat(b1).isEqualTo(b2) + assertThat(b2!!.width).isEqualTo(highResSize) + assertThat(thumbnailLoader.invokeCalls).hasSize(1) + } + + @Test + fun test_incorrectSizeRequested_defaultSizeIsUsed() = + scope.runTest { + val uri = createUri(0) + val defaultPreviewSize = 100 + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { size -> createBitmap(size.width, size.height) } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + cacheSize = 1, + defaultPreviewSize, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + val b1 = testSubject(uri, Size(0, 0)) + assertThat(b1!!.width).isEqualTo(defaultPreviewSize) + + val largerImageSize = 200 + val b2 = testSubject(uri, Size(largerImageSize, largerImageSize)) + assertThat(b2!!.width).isEqualTo(largerImageSize) + } + + @Test + fun test_prePopulateImages_cachesImagesUpToTheCacheSize() = + scope.runTest { + val previewSize = Size(100, 100) + val uris = List(2) { createUri(it) } + val loadingCount = atomic(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + for (uri in uris) { + fakeInvoke[uri] = { size -> + loadingCount.getAndIncrement() + createBitmap(size.width, size.height) + } + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + testSubject.prePopulate(uris.map { it to previewSize }) + runCurrent() + + assertThat(loadingCount.value).isEqualTo(1) + assertThat(thumbnailLoader.invokeCalls).containsExactly(uris[0]) + + testSubject(uris[0], previewSize) + runCurrent() + + assertThat(loadingCount.value).isEqualTo(1) + } + + @Test + fun test_oldRecordEvictedFromTheCache() = + scope.runTest { + val previewSize = Size(100, 100) + val uriOne = createUri(1) + val uriTwo = createUri(2) + val requestsPerUri = HashMap() + val thumbnailLoader = + FakeThumbnailLoader().apply { + for (uri in arrayOf(uriOne, uriTwo)) { + fakeInvoke[uri] = { size -> + requestsPerUri.getOrPut(uri) { AtomicInteger() }.incrementAndGet() + createBitmap(size.width, size.height) + } + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + testSubject(uriOne, previewSize) + testSubject(uriTwo, previewSize) + testSubject(uriTwo, previewSize) + testSubject(uriOne, previewSize) + + assertThat(requestsPerUri[uriOne]?.get()).isEqualTo(2) + assertThat(requestsPerUri[uriTwo]?.get()).isEqualTo(1) + } + + @Test + fun test_doNotCacheNulls() = + scope.runTest { + val previewSize = Size(100, 100) + val uri = createUri(1) + val loadingCount = atomic(0) + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { + loadingCount.getAndIncrement() + null + } + } + val testSubject = + PreviewImageLoader( + backgroundScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + testSubject(uri, previewSize) + testSubject(uri, previewSize) + + assertThat(loadingCount.value).isEqualTo(2) + } + + @Test(expected = CancellationException::class) + fun invoke_onClosedImageLoaderScope_throwsCancellationException() = + scope.runTest { + val uri = createUri(1) + val thumbnailLoader = FakeThumbnailLoader().apply { fakeInvoke[uri] = { null } } + val imageLoaderScope = CoroutineScope(coroutineContext) + val testSubject = + PreviewImageLoader( + imageLoaderScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + imageLoaderScope.cancel() + testSubject(uri, Size(200, 200)) + } + + @Test(expected = CancellationException::class) + fun invoke_imageLoaderScopeClosedMidflight_throwsCancellationException() = + scope.runTest { + val uri = createUri(1) + val loadingStarted = CompletableDeferred() + val bitmapDeferred = CompletableDeferred() + val thumbnailLoader = + FakeThumbnailLoader().apply { + fakeInvoke[uri] = { + loadingStarted.complete(Unit) + bitmapDeferred.await() + } + } + val imageLoaderScope = CoroutineScope(coroutineContext) + val testSubject = + PreviewImageLoader( + imageLoaderScope, + 1, + 100, + thumbnailLoader, + StandardTestDispatcher(scope.testScheduler) + ) + + launch { + loadingStarted.await() + imageLoaderScope.cancel() + } + testSubject(uri, Size(200, 200)) + } +} + +private fun createUri(id: Int) = Uri.parse("content://org.pkg.app/image-$id.png") + +private fun createBitmap(width: Int, height: Int) = + Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) -- cgit v1.2.3-59-g8ed1b From 068c395fd118c20632a5a77a0e28639863dc070b Mon Sep 17 00:00:00 2001 From: Govinda Wasserman Date: Wed, 24 Jul 2024 21:36:22 -0400 Subject: Center initial item in Sharousel As long as there are enough items in the row to center the initial item, it will be centered, otherwise it will be as close to center as the scroll bounds allow. Test: manual testing BUG: 341925364 Flag: android.service.chooser.chooser_payload_toggling Change-Id: Ifb96071ba70c3ba42f53d584ddfc31d4fc62cedf --- .../ui/composable/ShareouselComposable.kt | 100 ++++++++++++++------- 1 file changed, 67 insertions(+), 33 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index 9ac36a87..e40d98bd 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -45,6 +45,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -71,6 +72,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.Prev import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselPreviewViewModel import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel import kotlin.math.abs +import kotlin.math.min import kotlinx.coroutines.launch @Composable @@ -104,44 +106,48 @@ private fun PreviewCarousel( previews: PreviewsModel, viewModel: ShareouselViewModel, ) { - val centerIdx = previews.startIdx - val carouselState = - rememberLazyListState( - initialFirstVisibleItemIndex = centerIdx, - prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() } - ) var maxAspectRatio by remember { mutableStateOf(0f) } var viewportHeight by remember { mutableStateOf(0) } - - val horizontalPadding = 16.dp + var viewportCenter by remember { mutableStateOf(0) } + var horizontalPadding by remember { mutableStateOf(0.dp) } Box( modifier = Modifier.fillMaxWidth() .height(dimensionResource(R.dimen.chooser_preview_image_height_tall)) .layout { measurable, constraints -> val placeable = measurable.measure(constraints) - val aspectRatio = + val (minItemWidth, maxAR) = if (placeable.height <= 0) { - 0f + 0f to 0f } else { - val maxItemWidth = - maxOf(0, placeable.width - 2 * horizontalPadding.roundToPx()) - (maxItemWidth.toFloat() / placeable.height).coerceIn( - 0f, - MAX_ASPECT_RATIO - ) + val minItemWidth = (MIN_ASPECT_RATIO * placeable.height) + val maxItemWidth = maxOf(0, placeable.width - 32.dp.roundToPx()) + val maxAR = + (maxItemWidth.toFloat() / placeable.height).coerceIn( + 0f, + MAX_ASPECT_RATIO + ) + minItemWidth to maxAR } - maxAspectRatio = aspectRatio + viewportCenter = placeable.width / 2 + maxAspectRatio = maxAR viewportHeight = placeable.height + horizontalPadding = ((placeable.width - minItemWidth) / 2).toDp() layout(placeable.width, placeable.height) { placeable.place(0, 0) } }, ) { - if (maxAspectRatio <= 0) { + if (maxAspectRatio <= 0 && previews.previewModels.isNotEmpty()) { // Do not compose the list until we know the viewport size return@Box } - // TODO: start item needs to be centered, check out ScalingLazyColumn impl or see if - // HorizontalPager works for our use-case + + var firstSelectedIndex by remember { mutableStateOf(null as Int?) } + + val carouselState = + rememberLazyListState( + prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() }, + ) + LazyRow( state = carouselState, horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -149,26 +155,38 @@ private fun PreviewCarousel( modifier = Modifier.fillMaxSize().systemGestureExclusion(), ) { itemsIndexed(previews.previewModels, key = { _, model -> model.uri }) { index, model -> + val visibleItem by remember { + derivedStateOf { + carouselState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index } + } + } + // Index if this is the element in the center of the viewing area, otherwise null val previewIndex by remember { derivedStateOf { - carouselState.layoutInfo.visibleItemsInfo - .firstOrNull { it.index == index } - ?.let { - val viewportCenter = carouselState.layoutInfo.viewportEndOffset / 2 - val halfPreviewWidth = it.size / 2 - val previewCenter = it.offset + halfPreviewWidth - val previewDistanceToViewportCenter = - abs(previewCenter - viewportCenter) - if (previewDistanceToViewportCenter <= halfPreviewWidth) { - index - } else { - null - } + visibleItem?.let { + val halfPreviewWidth = it.size / 2 + val previewCenter = it.offset + halfPreviewWidth + val previewDistanceToViewportCenter = + abs(previewCenter - viewportCenter) + if (previewDistanceToViewportCenter <= halfPreviewWidth) { + index + } else { + null } + } } } + val previewModel = + viewModel.preview(model, viewportHeight, previewIndex, rememberCoroutineScope()) + val selected by + previewModel.isSelected.collectAsStateWithLifecycle(initialValue = false) + + if (selected) { + firstSelectedIndex = min(index, firstSelectedIndex ?: Int.MAX_VALUE) + } + ShareouselCard( viewModel.preview( model, @@ -180,6 +198,22 @@ private fun PreviewCarousel( ) } } + + firstSelectedIndex?.let { index -> + LaunchedEffect(Unit) { + val visibleItem = + carouselState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index } + val center = + with(carouselState.layoutInfo) { + ((viewportEndOffset - viewportStartOffset) / 2) + viewportStartOffset + } + + carouselState.scrollToItem( + index = index, + scrollOffset = visibleItem?.size?.div(2)?.minus(center) ?: 0, + ) + } + } } } -- cgit v1.2.3-59-g8ed1b From 5d580c934c3dec954caa4bb691886a8ba7cd2dc0 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 29 Jul 2024 20:05:33 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I93b7cf02f696dc798599944851e8c098bd638a86 --- java/res/values-ar/strings.xml | 6 +++--- java/res/values-de/strings.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'java') diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml index fa9bd2c2..c50ac67e 100644 --- a/java/res/values-ar/strings.xml +++ b/java/res/values-ar/strings.xml @@ -45,8 +45,8 @@ "استخدام تطبيق آخر" "اختيار إجراء" "ليست هناك تطبيقات يمكنها تنفيذ هذا الإجراء." - "أنت تستخدم هذا التطبيق خارج ملفك الشخصي للعمل" - "أنت تستخدم هذا التطبيق في ملفك الشخصي للعمل" + "أنت تستخدم هذا التطبيق خارج ملف العمل الخاص بك" + "أنت تستخدم هذا التطبيق في ملف العمل الخاص بك" "دائمًا" "مرة واحدة فقط" "لا يتوافق تطبيق \"%1$s\" مع ملف العمل." @@ -94,7 +94,7 @@ "ما مِن تطبيقات شخصية." "ما مِن تطبيقات خاصة" "هل تريد فتح %s في ملفك الشخصي؟" - "هل تريد فتح %s في ملفك الشخصي للعمل؟" + "هل تريد فتح %s في ملف العمل الخاص بك؟" "استخدام المتصفّح الشخصي" "استخدام متصفّح العمل" "استثناء النص" diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml index e8066dcb..3d2250a6 100644 --- a/java/res/values-de/strings.xml +++ b/java/res/values-de/strings.xml @@ -81,7 +81,7 @@ "Private Ansicht" "Geschäftliche Ansicht" "Private Ansicht" - "Von deinem IT-Administrator blockiert" + "Vom IT‑Administrator blockiert" "Diese Art von Inhalt kann nicht über geschäftliche Apps geteilt werden" "Diese Art von Inhalt kann nicht mit geschäftlichen Apps geöffnet werden" "Diese Art von Inhalt kann nicht über private Apps geteilt werden" -- cgit v1.2.3-59-g8ed1b From 7da3a9396f38d92dd01f765d99d1c9d3f65a46d0 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Tue, 30 Jul 2024 10:57:39 -0700 Subject: Do not crash when fail to read from the additional content cursor Bug: 354546194 Test: atest IntentResolver-tests-unit Flag: EXEMPT bugfix Change-Id: I5123fe6c1cabbbae7479d2cea8bd9e4b8ff0b4a8 --- .../domain/interactor/CursorPreviewsInteractor.kt | 73 ++++++++++++------ .../interactor/CursorPreviewsInteractorTest.kt | 89 +++++++++++++++++++--- 2 files changed, 128 insertions(+), 34 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractor.kt index a475263c..7d658209 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractor.kt @@ -20,6 +20,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interacto import android.net.Uri import android.service.chooser.AdditionalContentContract.CursorExtraKeys.POSITION +import android.util.Log import com.android.intentresolver.contentpreview.UriMetadataReader import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.CursorRow import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.LoadDirection @@ -51,6 +52,8 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.mapLatest +private const val TAG = "CursorPreviewsIntr" + /** Queries data from a remote cursor, and caches it locally for presentation in Shareousel. */ class CursorPreviewsInteractor @Inject @@ -273,8 +276,7 @@ constructor( pagedCursor .getPageRows(pageNum) // TODO: what do we do if the load fails? ?.filter { it.uri !in state.merged } - ?.toPage(this, unclaimedRecords) - ?: this + ?.toPage(this, unclaimedRecords) ?: this private suspend fun Sequence.toPage( destination: M, @@ -288,26 +290,32 @@ constructor( private fun createPreviewModel( row: CursorRow, unclaimedRecords: MutableUnclaimedMap, - ): PreviewModel = uriMetadataReader.getMetadata(row.uri).let { metadata -> - val size = - row.previewSize - ?: metadata.previewUri?.let { uriMetadataReader.readPreviewSize(it) } - PreviewModel( - uri = row.uri, - previewUri = metadata.previewUri, - mimeType = metadata.mimeType, - aspectRatio = size.aspectRatioOrDefault(1f), - order = row.position, - ) - }.also { updated -> - if (unclaimedRecords.remove(row.uri) != null) { - // unclaimedRecords contains initially shared (and thus selected) items with unknown - // cursor position. Update selection records when any of those items is encountered - // in the cursor to maintain proper selection order should other items also be - // selected. - selectionInteractor.updateSelection(updated) + ): PreviewModel = + uriMetadataReader + .getMetadata(row.uri) + .let { metadata -> + val size = + row.previewSize + ?: metadata.previewUri?.let { uriMetadataReader.readPreviewSize(it) } + PreviewModel( + uri = row.uri, + previewUri = metadata.previewUri, + mimeType = metadata.mimeType, + aspectRatio = size.aspectRatioOrDefault(1f), + order = row.position, + ) + } + .also { updated -> + if (unclaimedRecords.remove(row.uri) != null) { + // unclaimedRecords contains initially shared (and thus selected) items with + // unknown + // cursor position. Update selection records when any of those items is + // encountered + // in the cursor to maintain proper selection order should other items also be + // selected. + selectionInteractor.updateSelection(updated) + } } - } private fun M.putAllUnclaimedRight(unclaimed: UnclaimedMap): M = putAllUnclaimedWhere(unclaimed) { it >= focusedItemIdx } @@ -343,7 +351,28 @@ private fun M.putAllUnclaimedWhere( .toMap(this) private fun PagedCursor.getPageRows(pageNum: Int): Sequence? = - get(pageNum)?.filterNotNull() + runCatching { get(pageNum) } + .onFailure { Log.e(TAG, "Failed to read additional content cursor page #$pageNum", it) } + .getOrNull() + ?.asSafeSequence() + ?.filterNotNull() + +private fun Sequence.asSafeSequence(): Sequence { + return if (this is SafeSequence) this else SafeSequence(this) +} + +private class SafeSequence(private val sequence: Sequence) : Sequence { + override fun iterator(): Iterator = + sequence.iterator().let { if (it is SafeIterator) it else SafeIterator(it) } +} + +private class SafeIterator(private val iterator: Iterator) : Iterator by iterator { + override fun hasNext(): Boolean { + return runCatching { iterator.hasNext() } + .onFailure { Log.e(TAG, "Failed to read cursor", it) } + .getOrDefault(false) + } +} @Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class PageSize diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt index 48e43190..c4ba8105 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt @@ -18,10 +18,13 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor +import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.provider.MediaStore.MediaColumns.HEIGHT import android.provider.MediaStore.MediaColumns.WIDTH +import android.service.chooser.AdditionalContentContract.Columns.URI +import android.service.chooser.AdditionalContentContract.CursorExtraKeys.POSITION import android.util.Size import androidx.core.os.bundleOf import com.android.intentresolver.contentpreview.FileInfo @@ -39,6 +42,7 @@ import com.android.intentresolver.util.cursor.CursorView import com.android.intentresolver.util.cursor.viewBy import com.android.intentresolver.util.runTest import com.android.systemui.kosmos.Kosmos +import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -93,9 +97,9 @@ class CursorPreviewsInteractorTest { private val cursorSizes: Map, ) { val cursor: CursorView = - MatrixCursor(arrayOf("uri", WIDTH, HEIGHT)) + MatrixCursor(arrayOf(URI, WIDTH, HEIGHT)) .apply { - extras = bundleOf("position" to cursorStartPosition) + extras = bundleOf(POSITION to cursorStartPosition) for (i in cursorRange) { val size = cursorSizes[i] addRow( @@ -279,22 +283,83 @@ class CursorPreviewsInteractorTest { ) { deps -> previewSelectionsRepository.selections.value = PreviewModel( - uri = uri(1), - mimeType = "image/png", - order = 0, - ).let { mapOf(it.uri to it) } + uri = uri(1), + mimeType = "image/png", + order = 0, + ) + .let { mapOf(it.uri to it) } backgroundScope.launch { cursorPreviewsInteractor.launch(deps.cursor, deps.initialPreviews) } runCurrent() - assertThat(previewSelectionsRepository.selections.value.values).containsExactly( - PreviewModel( - uri = uri(1), - mimeType = "image/bitmap", - order = 1, + assertThat(previewSelectionsRepository.selections.value.values) + .containsExactly( + PreviewModel( + uri = uri(1), + mimeType = "image/bitmap", + order = 1, + ) + ) + } + + @Test + fun testReadFailedPages() = + runTestWithDeps( + initialSelection = listOf(4), + cursor = emptyList(), + cursorStartPosition = 0, + pageSize = 2, + maxLoadedPages = 5, + ) { deps -> + val cursor = + MatrixCursor(arrayOf(URI)).apply { + extras = bundleOf(POSITION to 4) + for (i in 0 until 10) { + addRow(arrayOf(uri(i))) + } + } + val failingPositions = setOf(1, 5, 8) + val failingCursor = + object : Cursor by cursor { + override fun move(offset: Int): Boolean = moveToPosition(position + offset) + + override fun moveToPosition(position: Int): Boolean { + if (failingPositions.contains(position)) { + throw RuntimeException( + "A test exception when moving the cursor to position $position" + ) + } + return cursor.moveToPosition(position) + } + + override fun moveToFirst(): Boolean = moveToPosition(0) + + override fun moveToLast(): Boolean = moveToPosition(count - 1) + + override fun moveToNext(): Boolean = move(1) + + override fun moveToPrevious(): Boolean = move(-1) + } + .viewBy { + getString(0)?.let { uriStr -> + CursorRow(Uri.parse(uriStr), readSize(), position) + } + } + backgroundScope.launch { + cursorPreviewsInteractor.launch(failingCursor, deps.initialPreviews) + } + runCurrent() + + assertThat(cursorPreviewsRepository.previewsModel.value).isNotNull() + assertThat(cursorPreviewsRepository.previewsModel.value!!.previewModels) + .comparingElementsUsing( + Correspondence.transforming({ it.uri }, "has a Uri of") + ) + .containsExactlyElementsIn( + (0..7).filterNot { failingPositions.contains(it) }.map { uri(it) } ) - ) + .inOrder() } } -- cgit v1.2.3-59-g8ed1b From 12d34169d65a07a99fb29e2332f7f01d256de202 Mon Sep 17 00:00:00 2001 From: Alan Chen Date: Tue, 30 Jul 2024 14:15:19 -0700 Subject: Add a null check in decorateActionFactoryWithRefinement Since originalFactory.getEditButtonRunnable is nullable, a check needs to be added to avoid an NPE. Bug: 353397828 Flag: EXEMPT bugfix Test: manual Change-Id: I9f028e30e7f6232fcd9c2cd3df423715c81e806b --- java/src/com/android/intentresolver/ChooserActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 84f29e21..bc8788ad 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2116,6 +2116,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override @Nullable public Runnable getEditButtonRunnable() { + if (originalFactory.getEditButtonRunnable() == null) return null; return () -> { if (!mRefinementManager.maybeHandleSelection( RefinementType.EDIT_ACTION, -- cgit v1.2.3-59-g8ed1b From 51dcb3e094bad99d2f3e8d3e692a7dd0ebcf7d30 Mon Sep 17 00:00:00 2001 From: Joshua Trask Date: Wed, 31 Jul 2024 18:15:26 +0000 Subject: Don't send target selection callbacks crossprofile Test: existing + new unit case in ShareResultSenderImplTest Bug: 319671608 Flag: EXEMPT bugfix Change-Id: I9e6f3a531ee6b35cd62a67f33836fa02ce7e70cf --- .../android/intentresolver/ChooserActivity.java | 8 ++- .../android/intentresolver/ui/ShareResultSender.kt | 54 ++++++++++++----- .../intentresolver/ui/ShareResultSenderImplTest.kt | 68 +++++++++++++++++++++- 3 files changed, 109 insertions(+), 21 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 84f29e21..32667ad0 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -1087,7 +1087,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (cti.startAsCaller(this, options, user.getIdentifier())) { // Prevent sending a second chooser result when starting the edit action intent. if (!cti.getTargetIntent().hasExtra(EDIT_SOURCE)) { - maybeSendShareResult(cti); + maybeSendShareResult(cti, user); } maybeLogCrossProfileTargetLaunch(cti, user); } @@ -1639,11 +1639,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return result; } - private void maybeSendShareResult(TargetInfo cti) { + private void maybeSendShareResult(TargetInfo cti, UserHandle launchedAsUser) { if (mShareResultSender != null) { final ComponentName target = cti.getResolvedComponentName(); if (target != null) { - mShareResultSender.onComponentSelected(target, cti.isChooserTargetInfo()); + boolean crossProfile = !UserHandle.of(UserHandle.myUserId()).equals(launchedAsUser); + mShareResultSender.onComponentSelected( + target, cti.isChooserTargetInfo(), crossProfile); } } } diff --git a/java/src/com/android/intentresolver/ui/ShareResultSender.kt b/java/src/com/android/intentresolver/ui/ShareResultSender.kt index 7be2076e..dce477ec 100644 --- a/java/src/com/android/intentresolver/ui/ShareResultSender.kt +++ b/java/src/com/android/intentresolver/ui/ShareResultSender.kt @@ -47,7 +47,7 @@ private const val TAG = "ShareResultSender" /** Reports the result of a share to another process across binder, via an [IntentSender] */ interface ShareResultSender { /** Reports user selection of an activity to launch from the provided choices. */ - fun onComponentSelected(component: ComponentName, directShare: Boolean) + fun onComponentSelected(component: ComponentName, directShare: Boolean, crossProfile: Boolean) /** Reports user invocation of a built-in system action. See [ShareAction]. */ fun onActionSelected(action: ShareAction) @@ -88,11 +88,15 @@ class ShareResultSenderImpl( IntentSenderDispatcher { sender, intent -> sender.dispatchIntent(context, intent) } ) - override fun onComponentSelected(component: ComponentName, directShare: Boolean) { - Log.i(TAG, "onComponentSelected: $component directShare=$directShare") + override fun onComponentSelected( + component: ComponentName, + directShare: Boolean, + crossProfile: Boolean + ) { + Log.i(TAG, "onComponentSelected: $component directShare=$directShare cross=$crossProfile") scope.launch { - val intent = createChosenComponentIntent(component, directShare) - intentDispatcher.dispatchIntent(resultSender, intent) + val intent = createChosenComponentIntent(component, directShare, crossProfile) + intent?.let { intentDispatcher.dispatchIntent(resultSender, it) } } } @@ -112,20 +116,38 @@ class ShareResultSenderImpl( private suspend fun createChosenComponentIntent( component: ComponentName, direct: Boolean, - ): Intent { - // Add extra with component name for backwards compatibility. - val intent: Intent = Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, component) - - // Add ChooserResult value for Android V+ + crossProfile: Boolean, + ): Intent? { if (flags.enableChooserResult() && chooserResultSupported(callerUid)) { - intent.putExtra( - Intent.EXTRA_CHOOSER_RESULT, - ChooserResult(CHOOSER_RESULT_SELECTED_COMPONENT, component, direct) - ) + if (crossProfile) { + Log.i(TAG, "Redacting package from cross-profile ${Intent.EXTRA_CHOOSER_RESULT}") + return Intent() + .putExtra( + Intent.EXTRA_CHOOSER_RESULT, + ChooserResult(CHOOSER_RESULT_UNKNOWN, null, direct) + ) + } else { + // Add extra with component name for backwards compatibility. + val intent: Intent = Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, component) + + // Add ChooserResult value for Android V+ + intent.putExtra( + Intent.EXTRA_CHOOSER_RESULT, + ChooserResult(CHOOSER_RESULT_SELECTED_COMPONENT, component, direct) + ) + return intent + } } else { - Log.i(TAG, "Not including ${Intent.EXTRA_CHOOSER_RESULT}") + if (crossProfile) { + // We can only send cross-profile results in the new ChooserResult format. + Log.i(TAG, "Omitting selection callback for cross-profile target") + return null + } else { + val intent: Intent = Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, component) + Log.i(TAG, "Not including ${Intent.EXTRA_CHOOSER_RESULT}") + return intent + } } - return intent } @ResultType diff --git a/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt b/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt index c254a856..7b43360a 100644 --- a/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt +++ b/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt @@ -68,7 +68,7 @@ class ShareResultSenderImplTest { intentDispatcher = intentDispatcher ) - resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true) + resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, false) runCurrent() val intentReceived = deferred.await() @@ -83,6 +83,43 @@ class ShareResultSenderImplTest { assertThat(chooserResult?.isShortcut).isTrue() } + @OptIn(ExperimentalCoroutinesApi::class) + @EnableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) + @Test + fun onComponentSelected_crossProfile_chooserResultEnabled() = runTest { + val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) + val deferred = CompletableDeferred() + val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } + + flags.setFlag(Flags.FLAG_ENABLE_CHOOSER_RESULT, true) + + val resultSender = + ShareResultSenderImpl( + flags = flags, + scope = this, + backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), + callerUid = Process.myUid(), + resultSender = pi.intentSender, + intentDispatcher = intentDispatcher + ) + + // Invoke as in the previous test, but this time say that the selection was cross-profile. + resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, true) + runCurrent() + + val intentReceived = deferred.await() + val chooserResult = + intentReceived.getParcelableExtra( + Intent.EXTRA_CHOOSER_RESULT, + ChooserResult::class.java + ) + assertThat(chooserResult).isNotNull() + assertThat(chooserResult?.type).isEqualTo(ChooserResult.CHOOSER_RESULT_UNKNOWN) + assertThat(chooserResult?.selectedComponent).isNull() + assertThat(chooserResult?.isShortcut).isTrue() + assertThat(intentReceived.hasExtra(Intent.EXTRA_CHOSEN_COMPONENT)).isFalse() + } + @DisableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) @Test fun onComponentSelected_chooserResultDisabled() = runTest { @@ -102,7 +139,7 @@ class ShareResultSenderImplTest { intentDispatcher = intentDispatcher ) - resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true) + resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, false) runCurrent() val intentReceived = deferred.await() @@ -121,6 +158,33 @@ class ShareResultSenderImplTest { .isFalse() } + @DisableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) + @Test + fun onComponentSelected_crossProfile_chooserResultDisabled() = runTest { + val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) + val deferred = CompletableDeferred() + val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } + + flags.setFlag(Flags.FLAG_ENABLE_CHOOSER_RESULT, true) + + val resultSender = + ShareResultSenderImpl( + flags = flags, + scope = this, + backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), + callerUid = Process.myUid(), + resultSender = pi.intentSender, + intentDispatcher = intentDispatcher + ) + + // Invoke as in the previous test, but this time say that the selection was cross-profile. + resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, true) + runCurrent() + + // In the pre-ChooserResult API, no callback intent is sent for cross-profile selections. + assertWithMessage("deferred result isComplete").that(deferred.isCompleted).isFalse() + } + @EnableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) @Test fun onActionSelected_chooserResultEnabled() = runTest { -- cgit v1.2.3-59-g8ed1b From 38ee8332d767014a9c69a737511bbc5aa705d5ec Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Wed, 31 Jul 2024 15:03:41 -0700 Subject: Update excluded components from payload selection callback Bug: 352496527 Test: atest IntentResolver-tests-unit Test: manual testing using ShareTest app Flag: com.android.intentresolver.shareousel_update_exclude_components_extra Change-Id: I610edf2c11a5beb114185c8724347b0eb0487ef1 --- aconfig/FeatureFlags.aconfig | 7 +++ .../android/intentresolver/ChooserActivity.java | 8 ++- .../interactor/UpdateChooserRequestInteractor.kt | 7 +++ .../payloadtoggle/domain/model/ShareouselUpdate.kt | 2 + .../domain/update/SelectionChangeCallback.kt | 17 ++++-- .../domain/update/SelectionChangeCallbackKosmos.kt | 2 - tests/unit/Android.bp | 1 + .../UpdateChooserRequestInteractorTest.kt | 24 ++++++++ .../update/SelectionChangeCallbackImplTest.kt | 65 +++++++++++++++++----- 9 files changed, 112 insertions(+), 21 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index c449bb43..b58b4daa 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -89,3 +89,10 @@ flag { description: "Use the unified preview image loader for all preview variations; support variable preview sizes." bug: "348665058" } + +flag { + name: "shareousel_update_exclude_components_extra" + namespace: "intentresolver" + description: "Allow Shareousel selection change callback to update Intent#EXTRA_EXCLUDE_COMPONENTS" + bug: "352496527" +} diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 84f29e21..7b43a21d 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static androidx.lifecycle.LifecycleKt.getCoroutineScope; import static com.android.intentresolver.ChooserActionFactory.EDIT_SOURCE; +import static com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra; import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK; @@ -749,10 +750,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements Intent newTargetIntent = newChooserRequest.getTargetIntent(); List oldAltIntents = oldChooserRequest.getAdditionalTargets(); List newAltIntents = newChooserRequest.getAdditionalTargets(); + List oldExcluded = oldChooserRequest.getFilteredComponentNames(); + List newExcluded = newChooserRequest.getFilteredComponentNames(); // TODO: a workaround for the unnecessary target reloading caused by multiple flow updates - // an artifact of the current implementation; revisit. - return !oldTargetIntent.equals(newTargetIntent) || !oldAltIntents.equals(newAltIntents); + return !oldTargetIntent.equals(newTargetIntent) + || !oldAltIntents.equals(newAltIntents) + || (shareouselUpdateExcludeComponentsExtra() + && !oldExcluded.equals(newExcluded)); } private void recreatePagerAdapter() { 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 dd16f0c1..4fe5e8d5 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,6 +17,7 @@ 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 @@ -49,6 +50,12 @@ constructor( 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 + } ) } update.customActions.onValue { actions -> diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt index 821e88a5..77f196e6 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/ShareouselUpdate.kt @@ -16,6 +16,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.model +import android.content.ComponentName import android.content.Intent import android.content.IntentSender import android.service.chooser.ChooserAction @@ -31,4 +32,5 @@ data class ShareouselUpdate( val refinementIntentSender: ValueUpdate = ValueUpdate.Absent, val resultIntentSender: ValueUpdate = ValueUpdate.Absent, val metadataText: ValueUpdate = ValueUpdate.Absent, + val excludeComponents: ValueUpdate> = ValueUpdate.Absent, ) diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt index 479bff77..184cc027 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt @@ -16,6 +16,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.update +import android.content.ComponentName import android.content.ContentInterface import android.content.Intent import android.content.Intent.EXTRA_ALTERNATE_INTENTS @@ -24,6 +25,7 @@ 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_EXCLUDE_COMPONENTS import android.content.Intent.EXTRA_INTENT import android.content.Intent.EXTRA_METADATA_TEXT import android.content.IntentSender @@ -32,11 +34,11 @@ import android.os.Bundle import android.service.chooser.AdditionalContentContract.MethodNames.ON_SELECTION_CHANGED import android.service.chooser.ChooserAction import android.service.chooser.ChooserTarget +import com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate import com.android.intentresolver.inject.AdditionalContent import com.android.intentresolver.inject.ChooserIntent -import com.android.intentresolver.inject.ChooserServiceFlags import com.android.intentresolver.ui.viewmodel.readAlternateIntents import com.android.intentresolver.ui.viewmodel.readChooserActions import com.android.intentresolver.validation.Invalid @@ -70,7 +72,6 @@ constructor( @AdditionalContent private val uri: Uri, @ChooserIntent private val chooserIntent: Intent, private val contentResolver: ContentInterface, - private val flags: ChooserServiceFlags, ) : SelectionChangeCallback { private val mutex = Mutex() @@ -90,7 +91,7 @@ constructor( ) } ?.let { bundle -> - return when (val result = readCallbackResponse(bundle, flags)) { + return when (val result = readCallbackResponse(bundle)) { is Valid -> { result.warnings.forEach { it.log(TAG) } result.value @@ -105,7 +106,6 @@ constructor( private fun readCallbackResponse( bundle: Bundle, - flags: ChooserServiceFlags ): ValidationResult { return validateFrom(bundle::get) { // An error is treated as an empty collection or null as the presence of a value indicates @@ -139,6 +139,14 @@ private fun readCallbackResponse( bundle.readValueUpdate(EXTRA_METADATA_TEXT) { key -> optional(value(key)) } + val excludedComponents: ValueUpdate> = + if (shareouselUpdateExcludeComponentsExtra()) { + bundle.readValueUpdate(EXTRA_EXCLUDE_COMPONENTS) { key -> + optional(array(key)) ?: emptyList() + } + } else { + ValueUpdate.Absent + } ShareouselUpdate( customActions, @@ -148,6 +156,7 @@ private fun readCallbackResponse( refinementIntentSender, resultIntentSender, metadataText, + excludedComponents, ) } } diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackKosmos.kt b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackKosmos.kt index 548b1f37..b26b562e 100644 --- a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackKosmos.kt +++ b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackKosmos.kt @@ -19,7 +19,6 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.update import com.android.intentresolver.contentInterface import com.android.intentresolver.inject.additionalContentUri import com.android.intentresolver.inject.chooserIntent -import com.android.intentresolver.inject.chooserServiceFlags import com.android.systemui.kosmos.Kosmos val Kosmos.selectionChangeCallbackImpl by @@ -28,7 +27,6 @@ val Kosmos.selectionChangeCallbackImpl by additionalContentUri, chooserIntent, contentInterface, - chooserServiceFlags, ) } var Kosmos.selectionChangeCallback: SelectionChangeCallback by diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 1ae8d883..aa1be2e0 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -33,6 +33,7 @@ android_test { "android.test.mock", "framework", "framework-res", + "flag-junit", ], resource_dirs: ["res"], diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt index 570c346c..32d040fe 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt @@ -18,7 +18,11 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor +import android.content.ComponentName import android.content.Intent +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import com.android.intentresolver.Flags.FLAG_SHAREOUSEL_UPDATE_EXCLUDE_COMPONENTS_EXTRA import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.pendingSelectionCallbackRepository import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate @@ -29,9 +33,12 @@ import com.android.intentresolver.util.runKosmosTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch +import org.junit.Rule import org.junit.Test class UpdateChooserRequestInteractorTest { + @get:Rule val setFlagsRule = SetFlagsRule() + @Test fun updateTargetIntentWithSelection() = runKosmosTest { val selectionCallbackResult = ShareouselUpdate(metadataText = ValueUpdate.Value("update")) @@ -45,4 +52,21 @@ class UpdateChooserRequestInteractorTest { assertThat(pendingSelectionCallbackRepository.pendingTargetIntent.value).isNull() assertThat(chooserRequestRepository.chooserRequest.value.metadataText).isEqualTo("update") } + + @Test + @EnableFlags(FLAG_SHAREOUSEL_UPDATE_EXCLUDE_COMPONENTS_EXTRA) + fun testSelectionResultWithExcludedComponents_chooserRequestIsUpdated() = runKosmosTest { + val excludedComponent = ComponentName("org.pkg.app", "Class") + val selectionCallbackResult = + ShareouselUpdate(excludeComponents = ValueUpdate.Value(listOf(excludedComponent))) + selectionChangeCallback = SelectionChangeCallback { selectionCallbackResult } + + backgroundScope.launch { processTargetIntentUpdatesInteractor.activate() } + + updateTargetIntentInteractor.updateTargetIntent(Intent()) + runCurrent() + + assertThat(chooserRequestRepository.chooserRequest.value.filteredComponentNames) + .containsExactly(excludedComponent) + } } diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt index fd21ea3f..c1a1833a 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallbackImplTest.kt @@ -29,32 +29,34 @@ 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_EXCLUDE_COMPONENTS import android.content.Intent.EXTRA_INTENT import android.content.Intent.EXTRA_METADATA_TEXT import android.content.Intent.EXTRA_STREAM import android.graphics.drawable.Icon import android.net.Uri import android.os.Bundle +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.service.chooser.AdditionalContentContract.MethodNames.ON_SELECTION_CHANGED import android.service.chooser.ChooserAction import android.service.chooser.ChooserTarget -import android.service.chooser.Flags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.android.intentresolver.Flags.FLAG_SHAREOUSEL_UPDATE_EXCLUDE_COMPONENTS_EXTRA import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate.Absent -import com.android.intentresolver.inject.FakeChooserServiceFlags import com.google.common.truth.Correspondence import com.google.common.truth.Correspondence.BinaryPredicate import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import java.lang.IllegalArgumentException import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.capture import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -62,16 +64,16 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class SelectionChangeCallbackImplTest { + @get:Rule val setFlagsRule = SetFlagsRule() + private val uri = Uri.parse("content://org.pkg/content-provider") private val chooserIntent = Intent(ACTION_CHOOSER) private val contentResolver = mock() private val context = InstrumentationRegistry.getInstrumentation().context - private val flags = - FakeChooserServiceFlags().apply { setFlag(Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING, false) } @Test fun testPayloadChangeCallbackContact() = runTest { - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val u1 = createUri(1) val u2 = createUri(2) @@ -166,7 +168,7 @@ class SelectionChangeCallbackImplTest { Bundle().apply { putParcelableArray(EXTRA_CHOOSER_CUSTOM_ACTIONS, arrayOf(a1, a2)) } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND_MULTIPLE) val result = testSubject.onSelectionChanged(targetIntent) @@ -183,6 +185,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -204,7 +207,7 @@ class SelectionChangeCallbackImplTest { Bundle().apply { putParcelable(EXTRA_CHOOSER_MODIFY_SHARE_ACTION, modifyShare) } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -223,6 +226,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -239,7 +243,7 @@ class SelectionChangeCallbackImplTest { Bundle().apply { putParcelableArray(EXTRA_ALTERNATE_INTENTS, alternateIntents) } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -264,6 +268,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -289,7 +294,7 @@ class SelectionChangeCallbackImplTest { Bundle().apply { putParcelableArray(EXTRA_CHOOSER_TARGETS, arrayOf(t1, t2)) } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -317,6 +322,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -331,7 +337,7 @@ class SelectionChangeCallbackImplTest { } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -344,6 +350,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender.getOrThrow()).isNotNull() assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -358,7 +365,7 @@ class SelectionChangeCallbackImplTest { } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -371,6 +378,7 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender.getOrThrow()).isNotNull() assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents).isEqualTo(Absent) } @Test @@ -379,7 +387,7 @@ class SelectionChangeCallbackImplTest { whenever(contentResolver.call(any(), any(), any(), any())) .thenReturn(Bundle().apply { putCharSequence(EXTRA_METADATA_TEXT, metadataText) }) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) @@ -392,6 +400,35 @@ class SelectionChangeCallbackImplTest { assertThat(result.refinementIntentSender).isEqualTo(Absent) assertThat(result.resultIntentSender).isEqualTo(Absent) assertThat(result.metadataText.getOrThrow()).isEqualTo(metadataText) + assertThat(result.excludeComponents).isEqualTo(Absent) + } + + @Test + @EnableFlags(FLAG_SHAREOUSEL_UPDATE_EXCLUDE_COMPONENTS_EXTRA) + fun testPayloadChangeCallbackUpdatesExcludedComponents_valueUpdated() = runTest { + val excludedComponent = ComponentName("org.pkg.app", "org.pkg.app.TheClass") + whenever(contentResolver.call(any(), any(), any(), any())) + .thenReturn( + Bundle().apply { + putParcelableArray(EXTRA_EXCLUDE_COMPONENTS, arrayOf(excludedComponent)) + } + ) + + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) + + val targetIntent = Intent(ACTION_SEND) + val result = testSubject.onSelectionChanged(targetIntent) + + assertWithMessage("Callback result should not be null").that(result).isNotNull() + requireNotNull(result) + assertThat(result.customActions).isEqualTo(Absent) + assertThat(result.modifyShareAction).isEqualTo(Absent) + assertThat(result.alternateIntents).isEqualTo(Absent) + assertThat(result.callerTargets).isEqualTo(Absent) + assertThat(result.refinementIntentSender).isEqualTo(Absent) + assertThat(result.resultIntentSender).isEqualTo(Absent) + assertThat(result.metadataText).isEqualTo(Absent) + assertThat(result.excludeComponents.getOrThrow()).containsExactly(excludedComponent) } @Test @@ -409,7 +446,7 @@ class SelectionChangeCallbackImplTest { } ) - val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver, flags) + val testSubject = SelectionChangeCallbackImpl(uri, chooserIntent, contentResolver) val targetIntent = Intent(ACTION_SEND) val result = testSubject.onSelectionChanged(targetIntent) -- cgit v1.2.3-59-g8ed1b From 1a429e9038a47d30470df7e278fd1beabe2bc387 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Fri, 2 Aug 2024 12:10:27 -0700 Subject: Add disabled state to chooser targets The new functinality is not used yet as this is a preparation step. Allows chooser targets to be disabled: unclickable with grey-scale icons. Bug: 349468879 Test: presubmits Flag: EXEMPT refactoring Change-Id: I202a496954c08da27b7cc7c0f9df8d04d9916350 --- .../android/intentresolver/ChooserListAdapter.java | 17 +++++++- .../intentresolver/ResolverListAdapter.java | 30 +------------- .../profiles/ChooserMultiProfilePagerAdapter.java | 9 +++++ .../util/graphics/SuspendedMatrixColorFilter.kt | 46 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 java/src/com/android/intentresolver/util/graphics/SuspendedMatrixColorFilter.kt (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index 07a615a5..548cd6ac 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.util.graphics.SuspendedMatrixColorFilter.getSuspendedColorMatrix; import android.app.ActivityManager; import android.app.prediction.AppTarget; @@ -153,6 +154,7 @@ public class ChooserListAdapter extends ResolverListAdapter { }; private boolean mAnimateItems = true; + private boolean mTargetsEnabled = true; public ChooserListAdapter( Context context, @@ -306,6 +308,16 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Set the enabled state for all targets. + */ + public void setTargetsEnabled(boolean isEnabled) { + if (mTargetsEnabled != isEnabled) { + mTargetsEnabled = isEnabled; + notifyDataSetChanged(); + } + } + public void setAnimateItems(boolean animateItems) { mAnimateItems = animateItems; } @@ -353,7 +365,7 @@ public class ChooserListAdapter extends ResolverListAdapter { @VisibleForTesting @Override public void onBindView(View view, TargetInfo info, int position) { - view.setEnabled(!isDestroyed()); + view.setEnabled(!isDestroyed() && mTargetsEnabled); final ViewHolder holder = (ViewHolder) view.getTag(); resetViewHolder(holder); @@ -413,6 +425,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } holder.bindIcon(info); + if (info.hasDisplayIcon() && !mTargetsEnabled) { + holder.icon.setColorFilter(getSuspendedColorMatrix()); + } if (mAnimateItems && info.hasDisplayIcon()) { mAnimationTracker.animateIcon(holder.icon, info); } diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index 5fd37d43..7ca1c724 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -16,14 +16,14 @@ package com.android.intentresolver; +import static com.android.intentresolver.util.graphics.SuspendedMatrixColorFilter.getSuspendedColorMatrix; + import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.RemoteException; @@ -63,9 +63,6 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ResolverListAdapter extends BaseAdapter { private static final String TAG = "ResolverListAdapter"; - @Nullable // TODO: other model for lazy computation? Or just precompute? - private static ColorMatrixColorFilter sSuspendedMatrixColorFilter; - protected final Context mContext; protected final LayoutInflater mInflater; protected final ResolverListCommunicator mResolverListCommunicator; @@ -797,29 +794,6 @@ public class ResolverListAdapter extends BaseAdapter { return mDestroyed.get(); } - private static ColorMatrixColorFilter getSuspendedColorMatrix() { - if (sSuspendedMatrixColorFilter == null) { - - int grayValue = 127; - float scale = 0.5f; // half bright - - ColorMatrix tempBrightnessMatrix = new ColorMatrix(); - float[] mat = tempBrightnessMatrix.getArray(); - mat[0] = scale; - mat[6] = scale; - mat[12] = scale; - mat[4] = grayValue; - mat[9] = grayValue; - mat[14] = grayValue; - - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0.0f); - matrix.preConcat(tempBrightnessMatrix); - sSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix); - } - return sSuspendedMatrixColorFilter; - } - protected final Drawable loadIconPlaceholder() { return mContext.getDrawable(R.drawable.resolver_icon_placeholder); } diff --git a/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java index 8aee0da1..9176cd35 100644 --- a/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java @@ -112,6 +112,15 @@ public class ChooserMultiProfilePagerAdapter extends MultiProfilePagerAdapter< } } + /** + * Set enabled status for all targets in all profiles. + */ + public void setTargetsEnabled(boolean isEnabled) { + for (int i = 0, size = getItemCount(); i < size; i++) { + getPageAdapterForIndex(i).getListAdapter().setTargetsEnabled(isEnabled); + } + } + private static ViewGroup makeProfileView(Context context) { LayoutInflater inflater = LayoutInflater.from(context); ViewGroup rootView = diff --git a/java/src/com/android/intentresolver/util/graphics/SuspendedMatrixColorFilter.kt b/java/src/com/android/intentresolver/util/graphics/SuspendedMatrixColorFilter.kt new file mode 100644 index 00000000..3e2d8e2a --- /dev/null +++ b/java/src/com/android/intentresolver/util/graphics/SuspendedMatrixColorFilter.kt @@ -0,0 +1,46 @@ +/* + * 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 + * + * 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. + */ + +@file:JvmName("SuspendedMatrixColorFilter") + +package com.android.intentresolver.util.graphics + +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter + +val suspendedColorMatrix by lazy { + val grayValue = 127f + val scale = 0.5f // half bright + + val tempBrightnessMatrix = + ColorMatrix().apply { + array.let { m -> + m[0] = scale + m[6] = scale + m[12] = scale + m[4] = grayValue + m[9] = grayValue + m[14] = grayValue + } + } + + val matrix = + ColorMatrix().apply { + setSaturation(0.0f) + preConcat(tempBrightnessMatrix) + } + ColorMatrixColorFilter(matrix) +} -- cgit v1.2.3-59-g8ed1b From 014824053450ef297505d1058987f95a4c5d3231 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Thu, 8 Aug 2024 20:57:51 -0700 Subject: Unify profile records and profile tabs creation. Create one ProfileRecord per profile tab. Utilize legacy profile tab logic to build an ordered map of ProfileRecords and generate profile tabs from these records. This change enforces 1:1 relation between profile records and tabs and highlights the exsiting dependency between them. Bug: 343300158 Test: manual testing with varius profile combinations available on the device Flag: EXEMPT refactoring Change-Id: I47bbbbdcc7832d6b881a272b0e489622c31de231 --- .../android/intentresolver/ChooserActivity.java | 49 ++++++++++++---------- .../intentresolver/ChooserWrapperActivity.java | 2 +- 2 files changed, 27 insertions(+), 24 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 8c6c7b7f..b0344565 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -153,8 +153,10 @@ import kotlinx.coroutines.CoroutineDispatcher; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -305,7 +307,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate = new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout); - private final Map mProfileRecords = new HashMap<>(); + private final Map mProfileRecords = new LinkedHashMap<>(); private boolean mExcludeSharedText = false; /** @@ -518,6 +520,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mProfilePagerResources, mRequest, mProfiles, + mProfileRecords.values(), mProfileAvailability, mRequest.getInitialIntents(), mMaxTargetsPerRow); @@ -787,6 +790,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mProfilePagerResources, mRequest, mProfiles, + mProfileRecords.values(), mProfileAvailability, mRequest.getInitialIntents(), mMaxTargetsPerRow); @@ -1351,26 +1355,23 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private void createProfileRecords( AppPredictorFactory factory, IntentFilter targetIntentFilter) { - UserHandle mainUserHandle = mProfiles.getPersonalHandle(); - ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory); - if (record.shortcutLoader == null) { - Tracer.INSTANCE.endLaunchToShortcutTrace(); - } - - UserHandle workUserHandle = mProfiles.getWorkHandle(); - if (workUserHandle != null) { - createProfileRecord(workUserHandle, targetIntentFilter, factory); - } - UserHandle privateUserHandle = mProfiles.getPrivateHandle(); - if (privateUserHandle != null && mProfileAvailability.isAvailable( - requireNonNull(mProfiles.getPrivateProfile()))) { - createProfileRecord(privateUserHandle, targetIntentFilter, factory); + Profile launchedAsProfile = mProfiles.getLaunchedAsProfile(); + for (Profile profile : mProfiles.getProfiles()) { + if (profile.getType() == Profile.Type.PRIVATE + && !mProfileAvailability.isAvailable(profile)) { + continue; + } + ProfileRecord record = createProfileRecord(profile, targetIntentFilter, factory); + if (profile.equals(launchedAsProfile) && record.shortcutLoader == null) { + Tracer.INSTANCE.endLaunchToShortcutTrace(); + } } } private ProfileRecord createProfileRecord( - UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory) { + Profile profile, IntentFilter targetIntentFilter, AppPredictorFactory factory) { + UserHandle userHandle = profile.getPrimary().getHandle(); AppPredictor appPredictor = factory.create(userHandle); ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic() ? null @@ -1380,7 +1381,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements userHandle, targetIntentFilter, shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult)); - ProfileRecord record = new ProfileRecord(appPredictor, shortcutLoader); + ProfileRecord record = new ProfileRecord(profile, appPredictor, shortcutLoader); mProfileRecords.put(userHandle.getIdentifier(), record); return record; } @@ -1415,6 +1416,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ProfilePagerResources profilePagerResources, ChooserRequest request, ProfileHelper profileHelper, + Collection profileRecords, ProfileAvailability profileAvailability, List initialIntents, int maxTargetsPerRow) { @@ -1426,11 +1428,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements List payloadIntents = request.getPayloadIntents(); List> tabs = new ArrayList<>(); - for (Profile profile : profileHelper.getProfiles()) { - if (profile.getType() == Profile.Type.PRIVATE - && !profileAvailability.isAvailable(profile)) { - continue; - } + for (ProfileRecord record : profileRecords) { + Profile profile = record.profile; ChooserGridAdapter adapter = createChooserGridAdapter( context, payloadIntents, @@ -2044,7 +2043,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements initialIntents, rList, filterLastUsed, - createListController(userHandle), + resolverListController, userHandle, targetIntent, referrerFillInIntent, @@ -2680,6 +2679,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private static class ProfileRecord { + public final Profile profile; + /** The {@link AppPredictor} for this profile, if any. */ @Nullable public final AppPredictor appPredictor; @@ -2691,8 +2692,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements public long loadingStartTime; private ProfileRecord( + Profile profile, @Nullable AppPredictor appPredictor, @Nullable ShortcutLoader shortcutLoader) { + this.profile = profile; this.appPredictor = appPredictor; this.shortcutLoader = shortcutLoader; } diff --git a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java index 930c84f0..6ff7af3f 100644 --- a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -65,7 +65,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW initialIntents, rList, filterLastUsed, - createListController(userHandle), + resolverListController, userHandle, targetIntent, referrerFillInIntent, -- cgit v1.2.3-59-g8ed1b From 45aee5e60a4f300bed30db58d3aa66c4a1917866 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Thu, 8 Aug 2024 21:27:31 -0700 Subject: Per-profile caller-provided direct target collections Instead of (partially) relying on the active profile value when deciding whether the caller-provided direct targets should be added to the list, define a list of caller-provided targets for each profile and use those lists unconditionally. Bug: 343300158 Test: manual tests: launche the ShareTest app from different profiles and confirm that the caller-provided targes are added only to the right profile (the profile the Chooser was launched from). Flag: EXEMPT refactoring Change-Id: I9c4ba35d48a090b21f96ddf4723f7b28654acb48 --- .../android/intentresolver/ChooserActivity.java | 46 ++++++++++++++-------- 1 file changed, 30 insertions(+), 16 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index b0344565..845d1e1d 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -1362,7 +1362,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements && !mProfileAvailability.isAvailable(profile)) { continue; } - ProfileRecord record = createProfileRecord(profile, targetIntentFilter, factory); + ProfileRecord record = createProfileRecord( + profile, + targetIntentFilter, + launchedAsProfile.equals(profile) + ? mRequest.getCallerChooserTargets() + : Collections.emptyList(), + factory); if (profile.equals(launchedAsProfile) && record.shortcutLoader == null) { Tracer.INSTANCE.endLaunchToShortcutTrace(); } @@ -1370,7 +1376,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private ProfileRecord createProfileRecord( - Profile profile, IntentFilter targetIntentFilter, AppPredictorFactory factory) { + Profile profile, + IntentFilter targetIntentFilter, + List callerTargets, + AppPredictorFactory factory) { UserHandle userHandle = profile.getPrimary().getHandle(); AppPredictor appPredictor = factory.create(userHandle); ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic() @@ -1381,7 +1390,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements userHandle, targetIntentFilter, shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult)); - ProfileRecord record = new ProfileRecord(profile, appPredictor, shortcutLoader); + ProfileRecord record = new ProfileRecord( + profile, appPredictor, shortcutLoader, callerTargets); mProfileRecords.put(userHandle.getIdentifier(), record); return record; } @@ -1655,17 +1665,18 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } } - private void addCallerChooserTargets() { - if (!mRequest.getCallerChooserTargets().isEmpty()) { - // Send the caller's chooser targets only to the default profile. - if (mChooserMultiProfilePagerAdapter.getActiveProfile() == findSelectedProfile()) { - mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( - /* origTarget */ null, - new ArrayList<>(mRequest.getCallerChooserTargets()), - TARGET_TYPE_DEFAULT, - /* directShareShortcutInfoCache */ Collections.emptyMap(), - /* directShareAppTargetCache */ Collections.emptyMap()); - } + private void addCallerChooserTargets(ChooserListAdapter adapter) { + ProfileRecord record = getProfileRecord(adapter.getUserHandle()); + List callerTargets = record == null + ? Collections.emptyList() + : record.callerTargets; + if (!callerTargets.isEmpty()) { + adapter.addServiceResults( + /* origTarget */ null, + new ArrayList<>(mRequest.getCallerChooserTargets()), + TARGET_TYPE_DEFAULT, + /* directShareShortcutInfoCache */ Collections.emptyMap(), + /* directShareAppTargetCache */ Collections.emptyMap()); } } @@ -2408,7 +2419,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (duration >= 0) { Log.d(TAG, "app target loading time " + duration + " ms"); } - addCallerChooserTargets(); + addCallerChooserTargets(chooserListAdapter); getEventLog().logSharesheetAppLoadComplete(); maybeQueryAdditionalPostProcessingTargets( listProfileUserHandle, @@ -2689,15 +2700,18 @@ public class ChooserActivity extends Hilt_ChooserActivity implements */ @Nullable public final ShortcutLoader shortcutLoader; + public final List callerTargets; public long loadingStartTime; private ProfileRecord( Profile profile, @Nullable AppPredictor appPredictor, - @Nullable ShortcutLoader shortcutLoader) { + @Nullable ShortcutLoader shortcutLoader, + List callerTargets) { this.profile = profile; this.appPredictor = appPredictor; this.shortcutLoader = shortcutLoader; + this.callerTargets = callerTargets; } public void destroy() { -- cgit v1.2.3-59-g8ed1b From e70191e44f659a10e913ffb20335c959bf43a39c Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Wed, 7 Aug 2024 11:22:19 -0700 Subject: Use nested scope for ShortcutLoader Use a nested coroutine scope for each ShortcutLoader instance and close it when the instance is stopped being used. Fix: 358135601 Test: atest IntentResolver-tests-unit Test: manual testing checking the logs Flag: com.android.intentresolver.fix_shortcut_loader_job_leak Change-Id: I84bbca75612f153193b03e24bfc0cb8842a8d3e2 --- aconfig/FeatureFlags.aconfig | 10 ++++ .../android/intentresolver/ChooserActivity.java | 3 ++ .../intentresolver/shortcuts/ShortcutLoader.kt | 56 +++++++++++++++------- .../intentresolver/shortcuts/ShortcutLoaderTest.kt | 30 ++++++++++++ 4 files changed, 82 insertions(+), 17 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 87a584c9..07ef198f 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -76,6 +76,16 @@ flag { } } +flag { + name: "fix_shortcut_loader_job_leak" + namespace: "intentresolver" + description: "User a nested coroutine scope for shortcut loader instances" + bug: "358135601" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "preview_image_loader" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 8c6c7b7f..d3c21f3e 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2701,6 +2701,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (appPredictor != null) { appPredictor.destroy(); } + if (shortcutLoader != null) { + shortcutLoader.destroy(); + } } } } diff --git a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt index 08230d90..68412256 100644 --- a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt +++ b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt @@ -35,6 +35,7 @@ import androidx.annotation.MainThread import androidx.annotation.OpenForTesting import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.intentresolver.Flags.fixShortcutLoaderJobLeak import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.measurements.Tracer import com.android.intentresolver.measurements.runTracing @@ -43,7 +44,9 @@ import java.util.function.Consumer import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.combine @@ -65,7 +68,7 @@ open class ShortcutLoader @VisibleForTesting constructor( private val context: Context, - private val scope: CoroutineScope, + parentScope: CoroutineScope, private val appPredictor: AppPredictorProxy?, private val userHandle: UserHandle, private val isPersonalProfile: Boolean, @@ -73,6 +76,8 @@ constructor( private val dispatcher: CoroutineDispatcher, private val callback: Consumer ) { + private val scope = + if (fixShortcutLoaderJobLeak()) parentScope.createChildScope() else parentScope private val shortcutToChooserTargetConverter = ShortcutToChooserTargetConverter() private val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager private val appPredictorCallback = @@ -88,6 +93,9 @@ constructor( private val isDestroyed get() = !scope.isActive + private val id + get() = System.identityHashCode(this).toString(Character.MAX_RADIX) + @MainThread constructor( context: Context, @@ -132,7 +140,7 @@ constructor( } .invokeOnCompletion { runCatching { appPredictor?.unregisterPredictionUpdates(appPredictorCallback) } - Log.d(TAG, "destroyed, user: $userHandle") + Log.d(TAG, "[$id] destroyed, user: $userHandle") } reset() } @@ -140,7 +148,7 @@ constructor( /** Clear application targets (see [updateAppTargets] and initiate shortcuts loading. */ @OpenForTesting open fun reset() { - Log.d(TAG, "reset shortcut loader for user $userHandle") + Log.d(TAG, "[$id] reset shortcut loader for user $userHandle") appTargetSource.tryEmit(null) shortcutSource.tryEmit(null) scope.launch(dispatcher) { loadShortcuts() } @@ -155,14 +163,21 @@ constructor( appTargetSource.tryEmit(appTargets) } + @OpenForTesting + open fun destroy() { + if (fixShortcutLoaderJobLeak()) { + scope.cancel() + } + } + @WorkerThread private fun loadShortcuts() { // no need to query direct share for work profile when its locked or disabled if (!shouldQueryDirectShareTargets()) { - Log.d(TAG, "skip shortcuts loading for user $userHandle") + Log.d(TAG, "[$id] skip shortcuts loading for user $userHandle") return } - Log.d(TAG, "querying direct share targets for user $userHandle") + Log.d(TAG, "[$id] querying direct share targets for user $userHandle") queryDirectShareTargets(false) } @@ -170,7 +185,7 @@ constructor( private fun queryDirectShareTargets(skipAppPredictionService: Boolean) { if (!skipAppPredictionService && appPredictor != null) { try { - Log.d(TAG, "query AppPredictor for user $userHandle") + Log.d(TAG, "[$id] query AppPredictor for user $userHandle") Tracer.beginAppPredictorQueryTrace(userHandle) appPredictor.requestPredictionUpdate() return @@ -180,12 +195,12 @@ constructor( if (isDestroyed) { return } - Log.e(TAG, "Failed to query AppPredictor for user $userHandle", e) + Log.e(TAG, "[$id] failed to query AppPredictor for user $userHandle", e) } } // Default to just querying ShortcutManager if AppPredictor not present. if (targetIntentFilter == null) { - Log.d(TAG, "skip querying ShortcutManager for $userHandle") + Log.d(TAG, "[$id] skip querying ShortcutManager for $userHandle") sendShareShortcutInfoList( emptyList(), isFromAppPredictor = false, @@ -193,12 +208,12 @@ constructor( ) return } - Log.d(TAG, "query ShortcutManager for user $userHandle") + Log.d(TAG, "[$id] query ShortcutManager for user $userHandle") val shortcuts = runTracing("shortcut-mngr-${userHandle.identifier}") { queryShortcutManager(targetIntentFilter) } - Log.d(TAG, "receive shortcuts from ShortcutManager for user $userHandle") + Log.d(TAG, "[$id] receive shortcuts from ShortcutManager for user $userHandle") sendShareShortcutInfoList(shortcuts, false, null) } @@ -210,14 +225,13 @@ constructor( val pm = context.createContextAsUser(userHandle, 0 /* flags */).packageManager return sm?.getShareTargets(targetIntentFilter)?.filter { pm.isPackageEnabled(it.targetComponent.packageName) - } - ?: emptyList() + } ?: emptyList() } @WorkerThread private fun onAppPredictorCallback(appPredictorTargets: List) { endAppPredictorQueryTrace(userHandle) - Log.d(TAG, "receive app targets from AppPredictor") + Log.d(TAG, "[$id] receive app targets from AppPredictor") if (appPredictorTargets.isEmpty() && shouldQueryDirectShareTargets()) { // APS may be disabled, so try querying targets ourselves. queryDirectShareTargets(true) @@ -330,6 +344,11 @@ constructor( val directShareShortcutInfoCache: Map ) + private fun endAppPredictorQueryTrace(userHandle: UserHandle) { + val duration = Tracer.endAppPredictorQueryTrace(userHandle) + Log.d(TAG, "[$id] AppPredictor query duration for user $userHandle: $duration ms") + } + /** Shortcuts grouped by app. */ class ShortcutResultInfo( val appTarget: DisplayResolveInfo, @@ -378,9 +397,12 @@ constructor( .getOrDefault(false) } - private fun endAppPredictorQueryTrace(userHandle: UserHandle) { - val duration = Tracer.endAppPredictorQueryTrace(userHandle) - Log.d(TAG, "AppPredictor query duration for user $userHandle: $duration ms") - } + /** + * Creates a new coroutine scope and makes its job a child of the given, `this`, coroutine + * scope's job. This ensures that the new scope will be canceled when the parent scope is + * canceled (but not vice versa). + */ + private fun CoroutineScope.createChildScope() = + CoroutineScope(coroutineContext + Job(parent = coroutineContext[Job])) } } diff --git a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt index fbdc062b..9c84cdcf 100644 --- a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt @@ -26,7 +26,10 @@ import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ShortcutManager import android.os.UserHandle import android.os.UserManager +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import androidx.test.filters.SmallTest +import com.android.intentresolver.Flags.FLAG_FIX_SHORTCUT_LOADER_JOB_LEAK import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.createAppTarget import com.android.intentresolver.createShareShortcutInfo @@ -42,6 +45,7 @@ import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor @@ -56,6 +60,8 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest class ShortcutLoaderTest { + @get:Rule val flagRule = SetFlagsRule() + private val appInfo = ApplicationInfo().apply { enabled = true @@ -465,6 +471,30 @@ class ShortcutLoaderTest { testAlwaysCallSystemForMainProfile(isQuietModeEnabled = true) } + @Test + @EnableFlags(FLAG_FIX_SHORTCUT_LOADER_JOB_LEAK) + fun test_ShortcutLoaderDestroyed_appPredictorCallbackUnregisteredAndWatchdogCancelled() { + scope.runTest { + val testSubject = + ShortcutLoader( + context, + backgroundScope, + appPredictor, + UserHandle.of(0), + true, + intentFilter, + dispatcher, + callback + ) + + testSubject.updateAppTargets(appTargets) + testSubject.destroy() + + verify(appPredictor, times(1)).registerPredictionUpdates(any(), any()) + verify(appPredictor, times(1)).unregisterPredictionUpdates(any()) + } + } + private fun testDisabledWorkProfileDoNotCallSystem( isUserRunning: Boolean = true, isUserUnlocked: Boolean = true, -- cgit v1.2.3-59-g8ed1b From 6872117b988dbf42ab81f078c5c27f8182517d0a Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Mon, 12 Aug 2024 10:53:47 -0700 Subject: Fix direct share row flashing upon Shareousel selection change. Upon payload selection change, copy direct share targets into the new adapters but keep the items disabled until the new set of shortcuts is loaded. To have all targets updated at once, publish the caller-provided targets together with the remaining direct shares. Fix: 343300158 Test: manual testing: using the ShareTest app, launch the Shareousel with a caller-provided targets and make the payload selection. Flag: com.android.intentresolver.fix_shortcuts_flashing Change-Id: Iff5ea6c892708daa9cca968a56a03aaaf80196f6 --- aconfig/FeatureFlags.aconfig | 10 +++++++ .../android/intentresolver/ChooserActivity.java | 24 ++++++++++++++- .../android/intentresolver/ChooserListAdapter.java | 35 ++++++++++++++++++++-- 3 files changed, 66 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 07ef198f..d6116c9a 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -86,6 +86,16 @@ flag { } } +flag { + name: "fix_shortcuts_flashing" + namespace: "intentresolver" + description: "Do not flash shortcuts on payload selection change" + bug: "343300158" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "preview_image_loader" namespace: "intentresolver" diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index cc8e3a11..51d8785f 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -24,6 +24,7 @@ import static androidx.lifecycle.LifecycleKt.getCoroutineScope; import static com.android.intentresolver.ChooserActionFactory.EDIT_SOURCE; import static com.android.intentresolver.Flags.shareouselUpdateExcludeComponentsExtra; +import static com.android.intentresolver.Flags.fixShortcutsFlashing; import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK; @@ -785,6 +786,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } // Update the pager adapter but do not attach it to the view till the targets are reloaded, // see onChooserAppTargetsLoaded method. + ChooserMultiProfilePagerAdapter oldPagerAdapter = + mChooserMultiProfilePagerAdapter; mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter( /* context = */ this, mProfilePagerResources, @@ -824,6 +827,19 @@ public class ChooserActivity extends Hilt_ChooserActivity implements postRebuildList( mChooserMultiProfilePagerAdapter.rebuildTabs( mProfiles.getWorkProfilePresent() || mProfiles.getPrivateProfilePresent())); + if (fixShortcutsFlashing() && oldPagerAdapter != null) { + for (int i = 0, count = mChooserMultiProfilePagerAdapter.getCount(); i < count; i++) { + ChooserListAdapter listAdapter = + mChooserMultiProfilePagerAdapter.getPageAdapterForIndex(i) + .getListAdapter(); + ChooserListAdapter oldListAdapter = + oldPagerAdapter.getListAdapterForUserHandle(listAdapter.getUserHandle()); + if (oldListAdapter != null) { + listAdapter.copyDirectTargetsFrom(oldListAdapter); + listAdapter.setDirectTargetsEnabled(false); + } + } + } setTabsViewEnabled(false); } @@ -2419,7 +2435,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (duration >= 0) { Log.d(TAG, "app target loading time " + duration + " ms"); } - addCallerChooserTargets(chooserListAdapter); + if (!fixShortcutsFlashing()) { + addCallerChooserTargets(chooserListAdapter); + } getEventLog().logSharesheetAppLoadComplete(); maybeQueryAdditionalPostProcessingTargets( listProfileUserHandle, @@ -2449,6 +2467,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ChooserListAdapter adapter = mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle); if (adapter != null) { + if (fixShortcutsFlashing()) { + adapter.setDirectTargetsEnabled(true); + addCallerChooserTargets(adapter); + } for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) { adapter.addServiceResults( resultInfo.getAppTarget(), diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index 548cd6ac..26e2feeb 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -155,6 +155,7 @@ public class ChooserListAdapter extends ResolverListAdapter { private boolean mAnimateItems = true; private boolean mTargetsEnabled = true; + private boolean mDirectTargetsEnabled = true; public ChooserListAdapter( Context context, @@ -318,6 +319,18 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Set the enabled state for direct targets. + */ + public void setDirectTargetsEnabled(boolean isEnabled) { + if (mDirectTargetsEnabled != isEnabled) { + mDirectTargetsEnabled = isEnabled; + if (!mServiceTargets.isEmpty() && !isDirectTargetRowEmptyState()) { + notifyDataSetChanged(); + } + } + } + public void setAnimateItems(boolean animateItems) { mAnimateItems = animateItems; } @@ -365,7 +378,8 @@ public class ChooserListAdapter extends ResolverListAdapter { @VisibleForTesting @Override public void onBindView(View view, TargetInfo info, int position) { - view.setEnabled(!isDestroyed() && mTargetsEnabled); + final boolean isEnabled = !isDestroyed() && mTargetsEnabled; + view.setEnabled(isEnabled); final ViewHolder holder = (ViewHolder) view.getTag(); resetViewHolder(holder); @@ -390,6 +404,7 @@ public class ChooserListAdapter extends ResolverListAdapter { } if (info.isSelectableTargetInfo()) { + view.setEnabled(isEnabled && mDirectTargetsEnabled); // direct share targets should append the application name for a better readout DisplayResolveInfo rInfo = info.getDisplayResolveInfo(); CharSequence appName = @@ -747,7 +762,7 @@ public class ChooserListAdapter extends ResolverListAdapter { Map directShareToShortcutInfos, Map directShareToAppTargets) { // Avoid inserting any potentially late results. - if ((mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo()) { + if (isDirectTargetRowEmptyState()) { return; } boolean isShortcutResult = targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER @@ -769,6 +784,22 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Copy direct targets from another ChooserListAdapter instance + */ + public void copyDirectTargetsFrom(ChooserListAdapter adapter) { + if (adapter.isDirectTargetRowEmptyState()) { + return; + } + + mServiceTargets.clear(); + mServiceTargets.addAll(adapter.mServiceTargets); + } + + private boolean isDirectTargetRowEmptyState() { + return (mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo(); + } + /** * Use the scoring system along with artificial boosts to create up to 4 distinct buckets: *
    -- cgit v1.2.3-59-g8ed1b From 2f6086166956949bc5ab732795f671b1800cb613 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Tue, 6 Aug 2024 13:46:56 -0700 Subject: Add timeout for AppPredictor response. Timeout AppPredictor callbacks and fallback to ShortcutManager. Bug: 295956687 Bug: 343300158 Test: atest IntentResolver-tests-unit Flag: com.android.intentresolver.fix_shortcuts_flashing Change-Id: Ia6e5643451a840e10213f242c6c79364b0193e78 --- .../intentresolver/shortcuts/ShortcutLoader.kt | 28 +++++ .../intentresolver/shortcuts/ShortcutLoaderTest.kt | 139 +++++++++++++++++++++ 2 files changed, 167 insertions(+) (limited to 'java') diff --git a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt index 68412256..1fedee30 100644 --- a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt +++ b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt @@ -36,18 +36,22 @@ import androidx.annotation.OpenForTesting import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import com.android.intentresolver.Flags.fixShortcutLoaderJobLeak +import com.android.intentresolver.Flags.fixShortcutsFlashing import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.measurements.Tracer import com.android.intentresolver.measurements.runTracing import java.util.concurrent.Executor import java.util.function.Consumer +import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.asExecutor import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter @@ -80,6 +84,7 @@ constructor( if (fixShortcutLoaderJobLeak()) parentScope.createChildScope() else parentScope private val shortcutToChooserTargetConverter = ShortcutToChooserTargetConverter() private val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager + private val appPredictorWatchdog = atomic(null) private val appPredictorCallback = ScopedAppTargetListCallback(scope) { onAppPredictorCallback(it) }.toAppPredictorCallback() @@ -186,8 +191,29 @@ constructor( if (!skipAppPredictionService && appPredictor != null) { try { Log.d(TAG, "[$id] query AppPredictor for user $userHandle") + + val watchdogJob = + if (fixShortcutsFlashing()) { + scope + .launch(start = CoroutineStart.LAZY) { + delay(APP_PREDICTOR_RESPONSE_TIMEOUT_MS) + Log.w(TAG, "AppPredictor response timeout for user: $userHandle") + appPredictorCallback.onTargetsAvailable(emptyList()) + } + .also { job -> + appPredictorWatchdog.getAndSet(job)?.cancel() + job.invokeOnCompletion { + appPredictorWatchdog.compareAndSet(job, null) + } + } + } else { + null + } + Tracer.beginAppPredictorQueryTrace(userHandle) appPredictor.requestPredictionUpdate() + + watchdogJob?.start() return } catch (e: Throwable) { endAppPredictorQueryTrace(userHandle) @@ -230,6 +256,7 @@ constructor( @WorkerThread private fun onAppPredictorCallback(appPredictorTargets: List) { + appPredictorWatchdog.value?.cancel() endAppPredictorQueryTrace(userHandle) Log.d(TAG, "[$id] receive app targets from AppPredictor") if (appPredictorTargets.isEmpty() && shouldQueryDirectShareTargets()) { @@ -378,6 +405,7 @@ constructor( } companion object { + @VisibleForTesting const val APP_PREDICTOR_RESPONSE_TIMEOUT_MS = 2_000L private const val TAG = "ShortcutLoader" private fun PackageManager.isPackageEnabled(packageName: String): Boolean { diff --git a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt index 9c84cdcf..d11cb460 100644 --- a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt @@ -26,9 +26,11 @@ import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ShortcutManager import android.os.UserHandle import android.os.UserManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.filters.SmallTest +import com.android.intentresolver.Flags.FLAG_FIX_SHORTCUTS_FLASHING import com.android.intentresolver.Flags.FLAG_FIX_SHORTCUT_LOADER_JOB_LEAK import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.createAppTarget @@ -322,6 +324,143 @@ class ShortcutLoaderTest { } } + @Test + @DisableFlags(FLAG_FIX_SHORTCUTS_FLASHING) + fun test_appPredictorNotResponding_noCallbackFromShortcutLoader() { + scope.runTest { + val shortcutManagerResult = + listOf( + ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), + // mismatching shortcut + createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) + ) + val shortcutManager = + mock { + on { getShareTargets(intentFilter) } doReturn shortcutManagerResult + } + whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) + val testSubject = + ShortcutLoader( + context, + backgroundScope, + appPredictor, + UserHandle.of(0), + true, + intentFilter, + dispatcher, + callback + ) + + testSubject.updateAppTargets(appTargets) + + verify(appPredictor, times(1)).requestPredictionUpdate() + + scheduler.advanceTimeBy(ShortcutLoader.APP_PREDICTOR_RESPONSE_TIMEOUT_MS * 2) + verify(callback, never()).accept(any()) + } + } + + @Test + @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING) + fun test_appPredictorNotResponding_timeoutAndFallbackToShortcutManager() { + scope.runTest { + val testSubject = + ShortcutLoader( + context, + backgroundScope, + appPredictor, + UserHandle.of(0), + true, + intentFilter, + dispatcher, + callback + ) + + testSubject.updateAppTargets(appTargets) + + val matchingAppTarget = createAppTarget(matchingShortcutInfo) + val shortcuts = + listOf( + matchingAppTarget, + // an AppTarget that does not belong to any resolved application; should be + // ignored + createAppTarget( + createShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) + ) + ) + val appPredictorCallbackCaptor = argumentCaptor() + verify(appPredictor, atLeastOnce()) + .registerPredictionUpdates(any(), appPredictorCallbackCaptor.capture()) + appPredictorCallbackCaptor.firstValue.onTargetsAvailable(shortcuts) + + scheduler.advanceTimeBy(ShortcutLoader.APP_PREDICTOR_RESPONSE_TIMEOUT_MS * 2) + verify(callback, times(1)).accept(any()) + } + } + + @Test + @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING) + fun test_appPredictorResponding_appPredictorTimeoutJobIsCancelled() { + scope.runTest { + val shortcutManagerResult = + listOf( + ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), + // mismatching shortcut + createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) + ) + val shortcutManager = + mock { + on { getShareTargets(intentFilter) } doReturn shortcutManagerResult + } + whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) + val testSubject = + ShortcutLoader( + context, + backgroundScope, + appPredictor, + UserHandle.of(0), + true, + intentFilter, + dispatcher, + callback + ) + + testSubject.updateAppTargets(appTargets) + + verify(appPredictor, times(1)).requestPredictionUpdate() + + scheduler.advanceTimeBy(ShortcutLoader.APP_PREDICTOR_RESPONSE_TIMEOUT_MS / 2) + verify(callback, never()).accept(any()) + + val resultCaptor = argumentCaptor() + scheduler.advanceTimeBy(ShortcutLoader.APP_PREDICTOR_RESPONSE_TIMEOUT_MS) + verify(callback, times(1)).accept(resultCaptor.capture()) + val result = resultCaptor.firstValue + assertWithMessage("An ShortcutManager result is expected") + .that(result.isFromAppPredictor) + .isFalse() + assertWithMessage("Wrong input app targets in the result") + .that(appTargets) + .asList() + .containsExactlyElementsIn(result.appTargets) + .inOrder() + assertWithMessage("Wrong shortcut count").that(result.shortcutsByApp).hasLength(1) + assertWithMessage("Wrong app target") + .that(appTarget) + .isEqualTo(result.shortcutsByApp[0].appTarget) + for (shortcut in result.shortcutsByApp[0].shortcuts) { + assertWithMessage( + "AppTargets are not expected the cache of a ShortcutManager result" + ) + .that(result.directShareAppTargetCache) + .isEmpty() + assertWithMessage("Wrong ShortcutInfo in the cache") + .that(matchingShortcutInfo) + .isEqualTo(result.directShareShortcutInfoCache[shortcut]) + } + } + } + @Test fun test_ShortcutLoader_shortcutsRequestedIndependentlyFromAppTargets() = scope.runTest { -- cgit v1.2.3-59-g8ed1b From 754d59681277b2afdd52b79746a593488f132e4b Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Thu, 1 Aug 2024 14:56:31 -0700 Subject: Allow toggling of final shareousel item When no items selected the UI will: * change the chooser headline; * make the custom action row invisible; * make all targets disabled and all target icons greyscale. Fix: 349468879 Test: atest IntentResolver-tests-unit Test: manual functionality testing Flag: com.android.intentresolver.unselect_final_item Change-Id: I53b9c908943b1f1003cb0131a6dec8abc26ec782 --- aconfig/FeatureFlags.aconfig | 7 +++ java/res/values/strings.xml | 3 ++ .../android/intentresolver/ChooserActivity.java | 9 +++- .../com/android/intentresolver/ChooserHelper.kt | 40 ++++++++++++--- .../android/intentresolver/ChooserListAdapter.java | 6 +-- .../intentresolver/ResolverListAdapter.java | 18 ++++++- .../contentpreview/HeadlineGenerator.kt | 2 + .../contentpreview/HeadlineGeneratorImpl.kt | 3 ++ .../data/repository/PreviewSelectionsRepository.kt | 4 +- .../domain/interactor/SelectionInteractor.kt | 9 +++- .../ui/composable/ShareouselComposable.kt | 58 ++++++++++++++-------- .../ui/viewmodel/ShareouselViewModel.kt | 16 ++++-- .../domain/interactor/SelectionInteractorTest.kt | 35 +++++++++++++ .../ui/viewmodel/ShareouselViewModelTest.kt | 2 + 14 files changed, 169 insertions(+), 43 deletions(-) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index d6116c9a..6f7e75d5 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -109,3 +109,10 @@ flag { description: "Allow Shareousel selection change callback to update Intent#EXTRA_EXCLUDE_COMPONENTS" bug: "352496527" } + +flag { + name: "unselect_final_item" + namespace: "intentresolver" + description: "Allow toggling of final Shareousel item" + bug: "349468879" +} diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index c026ee59..4f77d248 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -162,6 +162,9 @@ } + + Select items to share + {count, plural, diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 51d8785f..8871ce3f 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.shareouselUpdateExcludeComponentsExtra; import static com.android.intentresolver.Flags.fixShortcutsFlashing; +import static com.android.intentresolver.Flags.unselectFinalItem; import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL; import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK; @@ -351,6 +352,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (mChooserServiceFeatureFlags.chooserPayloadToggling()) { mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged); mChooserHelper.setOnPendingSelection(this::onPendingSelection); + if (unselectFinalItem()) { + mChooserHelper.setOnHasSelections(this::onHasSelections); + } } } private int mInitialProfile = -1; @@ -705,7 +709,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void onChooserRequestChanged(ChooserRequest chooserRequest) { - // intentional reference comparison if (mRequest == chooserRequest) { return; } @@ -724,6 +727,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements setTabsViewEnabled(false); } + private void onHasSelections(boolean hasSelections) { + mChooserMultiProfilePagerAdapter.setTargetsEnabled(hasSelections); + } + private void onAppTargetsLoaded(ResolverListAdapter listAdapter) { Log.d(TAG, "onAppTargetsLoaded(" + "listAdapter.userHandle=" + listAdapter.getUserHandle() + ")"); diff --git a/java/src/com/android/intentresolver/ChooserHelper.kt b/java/src/com/android/intentresolver/ChooserHelper.kt index 312911a6..7d382bfc 100644 --- a/java/src/com/android/intentresolver/ChooserHelper.kt +++ b/java/src/com/android/intentresolver/ChooserHelper.kt @@ -27,9 +27,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.intentresolver.Flags.unselectFinalItem import com.android.intentresolver.annotation.JavaInterop +import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ActivityResultRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PendingSelectionCallbackRepository +import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository import com.android.intentresolver.data.model.ChooserRequest import com.android.intentresolver.platform.GlobalSettings import com.android.intentresolver.ui.viewmodel.ChooserViewModel @@ -39,6 +42,8 @@ import com.android.intentresolver.validation.log import dagger.hilt.android.scopes.ActivityScoped import java.util.function.Consumer import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -46,6 +51,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch private const val TAG: String = "ChooserHelper" @@ -86,6 +92,7 @@ constructor( hostActivity: Activity, private val activityResultRepo: ActivityResultRepository, private val pendingSelectionCallbackRepo: PendingSelectionCallbackRepository, + private val selectionsRepo: PreviewSelectionsRepository, private val globalSettings: GlobalSettings, ) : DefaultLifecycleObserver { // This is guaranteed by Hilt, since only a ComponentActivity is injectable. @@ -98,6 +105,7 @@ constructor( var onChooserRequestChanged: Consumer = Consumer {} /** Invoked when there are a new change to payload selection */ var onPendingSelection: Runnable = Runnable {} + var onHasSelections: Consumer = Consumer {} init { activity.lifecycle.addObserver(this) @@ -144,22 +152,40 @@ constructor( } activity.lifecycleScope.launch { - val hasPendingCallbackFlow = + val hasPendingIntentFlow = pendingSelectionCallbackRepo.pendingTargetIntent .map { it != null } .distinctUntilChanged() - .onEach { hasPendingCallback -> - if (hasPendingCallback) { + .onEach { hasPendingIntent -> + if (hasPendingIntent) { onPendingSelection.run() } } activity.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.request - .combine(hasPendingCallbackFlow) { request, hasPendingCallback -> - request to hasPendingCallback + val hasSelectionFlow = + if ( + unselectFinalItem() && + viewModel.previewDataProvider.previewType == + CONTENT_PREVIEW_PAYLOAD_SELECTION + ) { + selectionsRepo.selections + .map { it.isNotEmpty() } + .distinctUntilChanged() + .stateIn(scope = this) + .also { flow -> launch { flow.collect { onHasSelections.accept(it) } } } + } else { + MutableStateFlow(true).asStateFlow() } + val requestControlFlow = + hasSelectionFlow + .combine(hasPendingIntentFlow) { hasSelections, hasPendingIntent -> + hasSelections && !hasPendingIntent + } + .distinctUntilChanged() + viewModel.request + .combine(requestControlFlow) { request, isReady -> request to isReady } // only take ChooserRequest if there are no pending callbacks - .filter { !it.second } + .filter { it.second } .map { it.first } .distinctUntilChanged(areEquivalent = { old, new -> old === new }) .collect { onChooserRequestChanged.accept(it) } diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index 26e2feeb..016eb714 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -18,7 +18,6 @@ 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.util.graphics.SuspendedMatrixColorFilter.getSuspendedColorMatrix; import android.app.ActivityManager; import android.app.prediction.AppTarget; @@ -439,10 +438,7 @@ public class ChooserListAdapter extends ResolverListAdapter { } } - holder.bindIcon(info); - if (info.hasDisplayIcon() && !mTargetsEnabled) { - holder.icon.setColorFilter(getSuspendedColorMatrix()); - } + holder.bindIcon(info, mTargetsEnabled); if (mAnimateItems && info.hasDisplayIcon()) { mAnimationTracker.animateIcon(holder.icon, info); } diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index 7ca1c724..fc5514b6 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -16,6 +16,7 @@ package com.android.intentresolver; +import static com.android.intentresolver.Flags.unselectFinalItem; import static com.android.intentresolver.util.graphics.SuspendedMatrixColorFilter.getSuspendedColorMatrix; import android.content.Context; @@ -973,13 +974,26 @@ public class ResolverListAdapter extends BaseAdapter { /** * Bind view holder to a TargetInfo. */ - public void bindIcon(TargetInfo info) { + public final void bindIcon(TargetInfo info) { + bindIcon(info, true); + } + + /** + * Bind view holder to a TargetInfo. + */ + public void bindIcon(TargetInfo info, boolean isEnabled) { Drawable displayIcon = info.getDisplayIconHolder().getDisplayIcon(); icon.setImageDrawable(displayIcon); - if (info.isSuspended()) { + if (info.isSuspended() || !isEnabled) { icon.setColorFilter(getSuspendedColorMatrix()); } else { icon.setColorFilter(null); + if (unselectFinalItem() && displayIcon != null) { + // For some reason, ImageView.setColorFilter() not always propagate the call + // to the drawable and the icon remains grayscale when rebound; reset the filter + // explicitly. + displayIcon.setColorFilter(null); + } } } } diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt index 21308341..059ee083 100644 --- a/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt +++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt @@ -36,4 +36,6 @@ interface HeadlineGenerator { fun getVideosHeadline(count: Int): String fun getFilesHeadline(count: Int): String + + fun getNotItemsSelectedHeadline(): String } diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt index e92d9bc6..822d3097 100644 --- a/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt +++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt @@ -93,6 +93,9 @@ constructor( return getPluralString(R.string.sharing_files, count) } + override fun getNotItemsSelectedHeadline(): String = + context.getString(R.string.select_items_to_share) + private fun getPluralString(@StringRes templateResource: Int, count: Int): String { return PluralsMessageFormatter.format( context.resources, diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt index 81c56d1e..0688ce02 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt @@ -18,12 +18,12 @@ package com.android.intentresolver.contentpreview.payloadtoggle.data.repository import android.net.Uri import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel -import dagger.hilt.android.scopes.ViewModelScoped +import dagger.hilt.android.scopes.ActivityRetainedScoped import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow /** Stores set of selected previews. */ -@ViewModelScoped +@ActivityRetainedScoped class PreviewSelectionsRepository @Inject constructor() { val selections = MutableStateFlow(emptyMap()) } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractor.kt index 97d9fa66..2d02e4fd 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractor.kt @@ -17,6 +17,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor import android.net.Uri +import com.android.intentresolver.Flags.unselectFinalItem import com.android.intentresolver.contentpreview.MimeTypeClassifier import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier @@ -60,8 +61,12 @@ constructor( } fun unselect(model: PreviewModel) { - if (selectionsRepo.selections.value.size > 1) { - updateChooserRequest(selectionsRepo.selections.updateAndGet { it - model.uri }.values) + if (selectionsRepo.selections.value.size > 1 || unselectFinalItem()) { + selectionsRepo.selections + .updateAndGet { it - model.uri } + .values + .takeIf { it.isNotEmpty() } + ?.let { updateChooserRequest(it) } } } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index 93ac90db..f8cf243d 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.intentresolver.Flags.unselectFinalItem import com.android.intentresolver.R import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault @@ -73,6 +74,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.Shar import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel import kotlin.math.abs import kotlin.math.min +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @Composable @@ -286,30 +288,46 @@ private fun ActionCarousel(viewModel: ShareouselViewModel) { val actions by viewModel.actions.collectAsStateWithLifecycle(initialValue = emptyList()) if (actions.isNotEmpty()) { Spacer(Modifier.height(16.dp)) - LazyRow( - horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.height(32.dp), - ) { - itemsIndexed(actions) { idx, actionViewModel -> - if (idx == 0) { - Spacer(Modifier.width(dimensionResource(R.dimen.chooser_edge_margin_normal))) - } - ShareouselAction( - label = actionViewModel.label, - onClick = { actionViewModel.onClicked() }, - ) { - actionViewModel.icon?.let { - Image( - icon = it, - modifier = Modifier.size(16.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current) + val visibilityFlow = + if (unselectFinalItem()) { + viewModel.hasSelectedItems + } else { + MutableStateFlow(true) + } + val visibility by visibilityFlow.collectAsStateWithLifecycle(true) + val height = 32.dp + if (visibility) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.height(height), + ) { + itemsIndexed(actions) { idx, actionViewModel -> + if (idx == 0) { + Spacer( + Modifier.width(dimensionResource(R.dimen.chooser_edge_margin_normal)) + ) + } + ShareouselAction( + label = actionViewModel.label, + onClick = { actionViewModel.onClicked() }, + ) { + actionViewModel.icon?.let { + Image( + icon = it, + modifier = Modifier.size(16.dp), + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + } + } + if (idx == actions.size - 1) { + Spacer( + Modifier.width(dimensionResource(R.dimen.chooser_edge_margin_normal)) ) } - } - if (idx == actions.size - 1) { - Spacer(Modifier.width(dimensionResource(R.dimen.chooser_edge_margin_normal))) } } + } else { + Spacer(modifier = Modifier.height(height)) } } } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt index 6f8be1ff..9762794e 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt @@ -17,6 +17,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel import android.util.Size import com.android.intentresolver.Flags +import com.android.intentresolver.Flags.unselectFinalItem import com.android.intentresolver.contentpreview.CachingImagePreviewImageLoader import com.android.intentresolver.contentpreview.HeadlineGenerator import com.android.intentresolver.contentpreview.ImageLoader @@ -58,6 +59,8 @@ data class ShareouselViewModel( val previews: Flow, /** List of action chips presented underneath Shareousel. */ val actions: Flow>, + /** Indicates whether there are any selected items */ + val hasSelectedItems: Flow, /** Creates a [ShareouselPreviewViewModel] for a [PreviewModel] present in [previews]. */ val preview: ( @@ -104,10 +107,14 @@ object ShareouselViewModelModule { selectionInteractor.aggregateContentType.zip(selectionInteractor.amountSelected) { contentType, numItems -> - when (contentType) { - ContentType.Other -> headlineGenerator.getFilesHeadline(numItems) - ContentType.Image -> headlineGenerator.getImagesHeadline(numItems) - ContentType.Video -> headlineGenerator.getVideosHeadline(numItems) + if (unselectFinalItem() && numItems == 0) { + headlineGenerator.getNotItemsSelectedHeadline() + } else { + when (contentType) { + ContentType.Other -> headlineGenerator.getFilesHeadline(numItems) + ContentType.Image -> headlineGenerator.getImagesHeadline(numItems) + ContentType.Video -> headlineGenerator.getVideosHeadline(numItems) + } } }, metadataText = chooserRequestInteractor.metadataText, @@ -128,6 +135,7 @@ object ShareouselViewModelModule { } } }, + hasSelectedItems = selectionInteractor.selections.map { it.isNotEmpty() }, preview = { key, previewHeight, index, previewScope -> keySet.value?.maybeLoad(index) val previewInteractor = interactor.preview(key) diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt index 87db243d..c8242333 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt @@ -18,16 +18,24 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interacto import android.content.Intent import android.net.Uri +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import com.android.intentresolver.Flags import com.android.intentresolver.contentpreview.mimetypeClassifier import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.previewSelectionsRepository import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel import com.android.intentresolver.util.runKosmosTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first +import org.junit.Rule import org.junit.Test class SelectionInteractorTest { + @get:Rule val flagsRule = SetFlagsRule() + @Test + @DisableFlags(Flags.FLAG_UNSELECT_FINAL_ITEM) fun singleSelection_removalPrevented() = runKosmosTest { val initialPreview = PreviewModel( @@ -53,6 +61,33 @@ class SelectionInteractorTest { assertThat(underTest.selections.first()).containsExactly(initialPreview.uri) } + @Test + @EnableFlags(Flags.FLAG_UNSELECT_FINAL_ITEM) + fun singleSelection_itemRemovedNoPendingIntentUpdates() = runKosmosTest { + val initialPreview = + PreviewModel( + uri = Uri.fromParts("scheme", "ssp", "fragment"), + mimeType = null, + order = 0 + ) + previewSelectionsRepository.selections.value = mapOf(initialPreview.uri to initialPreview) + + val underTest = + SelectionInteractor( + previewSelectionsRepository, + { Intent() }, + updateTargetIntentInteractor, + mimetypeClassifier, + ) + + assertThat(underTest.selections.first()).containsExactly(initialPreview.uri) + + underTest.unselect(initialPreview) + + assertThat(underTest.selections.first()).isEmpty() + assertThat(previewSelectionsRepository.selections.value).isEmpty() + } + @Test fun multipleSelections_removalAllowed() = runKosmosTest { val first = diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt index 1047d145..fc7ac751 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt @@ -347,6 +347,8 @@ class ShareouselViewModelTest { override fun getVideosHeadline(count: Int): String = "VIDEOS: $count" override fun getFilesHeadline(count: Int): String = "FILES: $count" + + override fun getNotItemsSelectedHeadline() = "Select items to share" } // instantiate the view model, and then runCurrent() so that it is fully hydrated before // starting the test -- cgit v1.2.3-59-g8ed1b From f2dcc1e410b605fbecd87b5f8850dede45ec7398 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 19 Aug 2024 19:33:53 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ie0b046b273f07376c8fa8282df26e68a055ad883 --- java/res/values-af/strings.xml | 2 ++ java/res/values-am/strings.xml | 2 ++ java/res/values-ar/strings.xml | 2 ++ java/res/values-as/strings.xml | 2 ++ java/res/values-az/strings.xml | 2 ++ java/res/values-b+sr+Latn/strings.xml | 2 ++ java/res/values-be/strings.xml | 2 ++ java/res/values-bg/strings.xml | 2 ++ java/res/values-bn/strings.xml | 2 ++ java/res/values-bs/strings.xml | 2 ++ java/res/values-ca/strings.xml | 2 ++ java/res/values-cs/strings.xml | 2 ++ java/res/values-da/strings.xml | 2 ++ java/res/values-de/strings.xml | 2 ++ java/res/values-el/strings.xml | 2 ++ java/res/values-en-rAU/strings.xml | 2 ++ java/res/values-en-rCA/strings.xml | 1 + java/res/values-en-rGB/strings.xml | 2 ++ java/res/values-en-rIN/strings.xml | 2 ++ java/res/values-en-rXC/strings.xml | 1 + java/res/values-es-rUS/strings.xml | 2 ++ java/res/values-es/strings.xml | 2 ++ java/res/values-et/strings.xml | 2 ++ java/res/values-eu/strings.xml | 2 ++ java/res/values-fa/strings.xml | 2 ++ java/res/values-fi/strings.xml | 2 ++ java/res/values-fr-rCA/strings.xml | 2 ++ java/res/values-fr/strings.xml | 2 ++ java/res/values-gl/strings.xml | 2 ++ java/res/values-gu/strings.xml | 2 ++ java/res/values-hi/strings.xml | 2 ++ java/res/values-hr/strings.xml | 2 ++ java/res/values-hu/strings.xml | 2 ++ java/res/values-hy/strings.xml | 2 ++ java/res/values-in/strings.xml | 2 ++ java/res/values-is/strings.xml | 2 ++ java/res/values-it/strings.xml | 2 ++ java/res/values-iw/strings.xml | 2 ++ java/res/values-ja/strings.xml | 2 ++ java/res/values-ka/strings.xml | 2 ++ java/res/values-kk/strings.xml | 2 ++ java/res/values-km/strings.xml | 2 ++ java/res/values-kn/strings.xml | 2 ++ java/res/values-ko/strings.xml | 2 ++ java/res/values-ky/strings.xml | 2 ++ java/res/values-lo/strings.xml | 2 ++ java/res/values-lt/strings.xml | 2 ++ java/res/values-lv/strings.xml | 2 ++ java/res/values-mk/strings.xml | 2 ++ java/res/values-ml/strings.xml | 2 ++ java/res/values-mn/strings.xml | 2 ++ java/res/values-mr/strings.xml | 2 ++ java/res/values-ms/strings.xml | 2 ++ java/res/values-my/strings.xml | 2 ++ java/res/values-nb/strings.xml | 2 ++ java/res/values-ne/strings.xml | 2 ++ java/res/values-nl/strings.xml | 2 ++ java/res/values-or/strings.xml | 2 ++ java/res/values-pa/strings.xml | 2 ++ java/res/values-pl/strings.xml | 2 ++ java/res/values-pt-rBR/strings.xml | 2 ++ java/res/values-pt-rPT/strings.xml | 2 ++ java/res/values-pt/strings.xml | 2 ++ java/res/values-ro/strings.xml | 2 ++ java/res/values-ru/strings.xml | 2 ++ java/res/values-si/strings.xml | 2 ++ java/res/values-sk/strings.xml | 2 ++ java/res/values-sl/strings.xml | 2 ++ java/res/values-sq/strings.xml | 2 ++ java/res/values-sr/strings.xml | 2 ++ java/res/values-sv/strings.xml | 2 ++ java/res/values-sw/strings.xml | 2 ++ java/res/values-ta/strings.xml | 2 ++ java/res/values-te/strings.xml | 2 ++ java/res/values-th/strings.xml | 2 ++ java/res/values-tl/strings.xml | 2 ++ java/res/values-tr/strings.xml | 2 ++ java/res/values-uk/strings.xml | 2 ++ java/res/values-ur/strings.xml | 2 ++ java/res/values-uz/strings.xml | 2 ++ java/res/values-vi/strings.xml | 2 ++ java/res/values-zh-rCN/strings.xml | 2 ++ java/res/values-zh-rHK/strings.xml | 2 ++ java/res/values-zh-rTW/strings.xml | 2 ++ java/res/values-zu/strings.xml | 2 ++ 85 files changed, 168 insertions(+) (limited to 'java') diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml index bfe3e7dc..43bc9604 100644 --- a/java/res/values-af/strings.xml +++ b/java/res/values-af/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deel tans prent}other{Deel tans # prente}}" "{count,plural, =1{Deel tans video}other{Deel tans # video’s}}" "{count,plural, =1{Deel tans # lêer}other{Deel tans # lêers}}" + + "{count,plural, =1{Deel tans prent met teks}other{Deel tans # prente met teks}}" "{count,plural, =1{Deel tans prent met skakel}other{Deel tans # prente met skakel}}" "{count,plural, =1{Deel tans video met teks}other{Deel tans # video’s met teks}}" diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml index 6daccad9..7d359304 100644 --- a/java/res/values-am/strings.xml +++ b/java/res/values-am/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ምስልን በማጋራት ላይ}one{# ምስልን በማጋራት ላይ}other{# ምስሎችን በማጋራት ላይ}}" "{count,plural, =1{ቪድዮ በማጋራት ላይ}one{# ቪድዮ በማጋራት ላይ}other{# ቪድዮዎችን በማጋራት ላይ}}" "{count,plural, =1{# ፋይልን በማጋራት ላይ}one{# ፋይልን በማጋራት ላይ}other{# ፋይሎችን በማጋራት ላይ}}" + + "{count,plural, =1{ምስልን ከጽሑፍ ጋር በማጋራት ላይ}one{# ምስልን ከጽሑፍ ጋር በማጋራት ላይ}other{# ምስሎችን ከጽሑፍ ጋር በማጋራት ላይ}}" "{count,plural, =1{ምስልን ከአገናኝ ጋር በማጋራት ላይ}one{# ምስልን ከአገናኝ ጋር በማጋራት ላይ}other{# ምስሎችን ከአገናኝ ጋር በማጋራት ላይ}}" "{count,plural, =1{ቪድዮ ከጽሑፍ ጋር በማጋራት ላይ}one{# ቪድዮ ከጽሑፍ ጋር በማጋራት ላይ}other{# ቪድዮዎችን ከጽሑፍ ጋር በማጋራት ላይ}}" diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml index c50ac67e..25ed0ba5 100644 --- a/java/res/values-ar/strings.xml +++ b/java/res/values-ar/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{جارٍ مشاركة صورة واحدة}zero{جارٍ مشاركة # صورة}two{جارٍ مشاركة صورتَين}few{جارٍ مشاركة # صور}many{جارٍ مشاركة # صورة}other{جارٍ مشاركة # صورة}}" "{count,plural, =1{جارٍ مشاركة فيديو واحد}zero{جارٍ مشاركة # فيديو}two{جارٍ مشاركة فيديوهَين}few{جارٍ مشاركة # فيديوهات}many{جارٍ مشاركة # فيديو}other{جارٍ مشاركة # فيديو}}" "{count,plural, =1{مشاركة ملف واحد}zero{مشاركة # ملف}two{مشاركة ملفَّين}few{مشاركة # ملفات}many{مشاركة # ملفًّا}other{مشاركة # ملف}}" + + "{count,plural, =1{مشاركة صورة واحدة ونص}zero{مشاركة # صورة ونص}two{مشاركة صورتَين ونص}few{مشاركة # صور ونص}many{مشاركة # صورة ونص}other{مشاركة # صورة ونص}}" "{count,plural, =1{مشاركة صورة واحدة ورابط}zero{مشاركة # صورة ورابط}two{مشاركة # صورتَين ورابط}few{مشاركة # صور ورابط}many{مشاركة # صورة ورابط}other{مشاركة # صورة ورابط}}" "{count,plural, =1{مشاركة فيديو واحد ونص}zero{مشاركة # فيديو ونص}two{مشاركة فيديوهَين ونص}few{مشاركة # فيديوهات ونص}many{مشاركة # فيديو ونص}other{مشاركة # فيديو ونص}}" diff --git a/java/res/values-as/strings.xml b/java/res/values-as/strings.xml index d2b3cb69..978af0c0 100644 --- a/java/res/values-as/strings.xml +++ b/java/res/values-as/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{# খন প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{# খন প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}one{# টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}other{# টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}one{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}other{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}}" + + "{count,plural, =1{পাঠৰ সৈতে প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{পাঠৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{পাঠৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{লিংকৰ সৈতে প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{লিংকৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{লিংকৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{পাঠৰ সৈতে ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}one{পাঠৰ সৈতে # টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}other{পাঠৰ সৈতে # টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}}" diff --git a/java/res/values-az/strings.xml b/java/res/values-az/strings.xml index e8915892..1ca43ae1 100644 --- a/java/res/values-az/strings.xml +++ b/java/res/values-az/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Şəkil paylaşılır}other{# şəkil paylaşılır}}" "{count,plural, =1{Video paylaşılır}other{# video paylaşılır}}" "{count,plural, =1{# fayl paylaşılır}other{# fayl paylaşılır}}" + + "{count,plural, =1{Mətn olan şəkil paylaşılır}other{Mətn olan # şəkil paylaşılır}}" "{count,plural, =1{Link olan şəkil paylaşılır}other{Link olan # şəkil paylaşılır}}" "{count,plural, =1{Mətn olan video paylaşılır}other{Mətn olan # video paylaşılır}}" diff --git a/java/res/values-b+sr+Latn/strings.xml b/java/res/values-b+sr+Latn/strings.xml index 228576f6..5bb53b2a 100644 --- a/java/res/values-b+sr+Latn/strings.xml +++ b/java/res/values-b+sr+Latn/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deljenje slike}one{Deljenje # slike}few{Deljenje # slike}other{Deljenje # slika}}" "{count,plural, =1{Deli se video}one{Deli se # video}few{Dele se # video snimka}other{Deli se # videa}}" "{count,plural, =1{Deli se # fajl}one{Deli se # fajl}few{Dele se # fajla}other{Deli se # fajlova}}" + + "{count,plural, =1{Deli se slika sa tekstom}one{Deli se # slika sa tekstom}few{Dele se # slike sa tekstom}other{Deli se # slika sa tekstom}}" "{count,plural, =1{Deli se slika sa linkom}one{Deli se # slika sa linkom}few{Dele se # slike sa linkom}other{Deli se # slika sa linkom}}" "{count,plural, =1{Deli se video sa tekstom}one{Deli se # video sa tekstom}few{Dele se # video snimka sa tekstom}other{Deli se # videa sa tekstom}}" diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml index 22079a0d..5f943f1e 100644 --- a/java/res/values-be/strings.xml +++ b/java/res/values-be/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Абагульванне відарыса}one{Абагульванне # відарыса}few{Абагульванне # відарысаў}many{Абагульванне # відарысаў}other{Абагульванне # відарыса}}" "{count,plural, =1{Абагульванне відэа}one{Абагульванне # відэа}few{Абагульванне # відэа}many{Абагульванне # відэа}other{Абагульванне # відэа}}" "{count,plural, =1{Абагульваецца # файл}one{Абагульваецца # файл}few{Абагульваюцца # файлы}many{Абагульваюцца # файлаў}other{Абагульваюцца # файла}}" + + "{count,plural, =1{Абагульванне відарыса з тэкстам}one{Абагульванне # відарыса з тэкстам}few{Абагульванне # відарысаў з тэкстам}many{Абагульванне # відарысаў з тэкстам}other{Абагульванне # відарыса з тэкстам}}" "{count,plural, =1{Абагульванне відарыса са спасылкай}one{Абагульванне # відарыса са спасылкай}few{Абагульванне # відарысаў са спасылкай}many{Абагульванне # відарысаў са спасылкай}other{Абагульванне # відарыса са спасылкай}}" "{count,plural, =1{Абагульванне відэа з тэкстам}one{Абагульванне # відэа з тэкстам}few{Абагульванне # відэа з тэкстам}many{Абагульванне # відэа з тэкстам}other{Абагульванне # відэа з тэкстам}}" diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml index 0b5fcad5..2c371364 100644 --- a/java/res/values-bg/strings.xml +++ b/java/res/values-bg/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Изображението се споделя}other{# изображения се споделят}}" "{count,plural, =1{Видеоклипът се споделя}other{# видеоклипа се споделят}}" "{count,plural, =1{# файл се споделя}other{# файла се споделят}}" + + "{count,plural, =1{Споделяне на изображението чрез SMS съобщение}other{Споделяне на # изображения чрез SMS съобщение}}" "{count,plural, =1{Споделяне на изображението чрез връзка}other{Споделяне на # изображения чрез връзка}}" "{count,plural, =1{Споделяне на видеоклипа чрез SMS съобщение}other{Споделяне на # видеоклипа чрез SMS съобщение}}" diff --git a/java/res/values-bn/strings.xml b/java/res/values-bn/strings.xml index b0d433c1..e3290c22 100644 --- a/java/res/values-bn/strings.xml +++ b/java/res/values-bn/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ছবি শেয়ার করা হচ্ছে}one{#টি ছবি শেয়ার করা হচ্ছে}other{#টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{ভিডিও শেয়ার করা হচ্ছে}one{#টি ভিডিও শেয়ার করা হচ্ছে}other{#টি ভিডিও শেয়ার করা হচ্ছে}}" "{count,plural, =1{#টি ফাইল শেয়ার করা হচ্ছে}one{#টি ফাইল শেয়ার করা হচ্ছে}other{#টি ফাইল শেয়ার করা হচ্ছে}}" + + "{count,plural, =1{টেক্সট সহ ছবি শেয়ার করা হচ্ছে}one{টেক্সট সহ #টি ছবি শেয়ার করা হচ্ছে}other{টেক্সট সহ #টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{লিঙ্ক সহ ছবি শেয়ার করা হচ্ছে}one{লিঙ্ক সহ #টি ছবি শেয়ার করা হচ্ছে}other{লিঙ্ক সহ #টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{টেক্সট সহ ভিডিও শেয়ার করা হচ্ছে}one{টেক্সট সহ #টি ভিডিও শেয়ার করা হচ্ছে}other{টেক্সট সহ #টি ভিডিও শেয়ার করা হচ্ছে}}" diff --git a/java/res/values-bs/strings.xml b/java/res/values-bs/strings.xml index 97d3e7cf..c844051b 100644 --- a/java/res/values-bs/strings.xml +++ b/java/res/values-bs/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeljenje videozapisa}one{Dijeljenje # videozapisa}few{Dijeljenje # videozapisa}other{Dijeljenje # videozapisa}}" "{count,plural, =1{Dijeljenje # fajla}one{Dijeljenje # fajla}few{Dijeljenje # fajla}other{Dijeljenje # fajlova}}" + + "{count,plural, =1{Dijeljenje slike putem poruke}one{Dijeljenje # slike putem poruke}few{Dijeljenje # slike putem poruke}other{Dijeljenje # slika putem poruke}}" "{count,plural, =1{Dijeljenje slike putem linka}one{Dijeljenje # slike putem linka}few{Dijeljenje # slike putem linka}other{Dijeljenje # slika putem linka}}" "{count,plural, =1{Dijeljenje videozapisa putem poruke}one{Dijeljenje # videozapisa putem poruke}few{Dijeljenje # videozapisa putem poruke}other{Dijeljenje # videozapisa putem poruke}}" diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml index 4cc905ba..1529787b 100644 --- a/java/res/values-ca/strings.xml +++ b/java/res/values-ca/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Comparteix una imatge}many{Comparteix # d\'imatges}other{Comparteix # imatges}}" "{count,plural, =1{S\'està compartint un vídeo}many{S\'estan compartint # de vídeos}other{S\'estan compartint # vídeos}}" "{count,plural, =1{S\'està compartint # fitxer}many{S\'estan compartint # de fitxers}other{S\'estan compartint # fitxers}}" + + "{count,plural, =1{S\'està compartint la imatge amb text}many{S\'estan compartint # d\'imatges amb text}other{S\'estan compartint # imatges amb text}}" "{count,plural, =1{S\'està compartint la imatge amb un enllaç}many{S\'estan compartint # d\'imatges amb un enllaç}other{S\'estan compartint # imatges amb un enllaç}}" "{count,plural, =1{S\'està compartint el vídeo amb un enllaç}many{S\'estan compartint # de vídeos amb un enllaç}other{S\'estan compartint # vídeos amb un enllaç}}" diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml index cca5091d..6c9e2e61 100644 --- a/java/res/values-cs/strings.xml +++ b/java/res/values-cs/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Sdílení obrázku}few{Sdílení # obrázků}many{Sdílení # obrázku}other{Sdílení # obrázků}}" "{count,plural, =1{Sdílení videa}few{Sdílení # videí}many{Sdílení # videa}other{Sdílení # videí}}" "{count,plural, =1{Sdílení # souboru}few{Sdílení # souborů}many{Sdílení # souboru}other{Sdílení # souborů}}" + + "{count,plural, =1{Sdílení obrázku s textem}few{Sdílení # obrázků s textem}many{Sdílení # obrázku s textem}other{Sdílení # obrázků s textem}}" "{count,plural, =1{Sdílení obrázku s odkazem}few{Sdílení # obrázků s odkazem}many{Sdílení # obrázku s odkazem}other{Sdílení # obrázků s odkazem}}" "{count,plural, =1{Sdílení videa s textem}few{Sdílení # videí s textem}many{Sdílení # videa s textem}other{Sdílení # videí s textem}}" diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml index f0d27442..e9faae8a 100644 --- a/java/res/values-da/strings.xml +++ b/java/res/values-da/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deler billede}one{Deler # billede}other{Deler # billeder}}" "{count,plural, =1{Deler video}one{Deler # video}other{Deler # videoer}}" "{count,plural, =1{Deler # fil}one{Deler # fil}other{Deler # filer}}" + + "{count,plural, =1{Deler billede med tekst}one{Deler # billede med tekst}other{Deler # billeder med tekst}}" "{count,plural, =1{Deler billede med et link}one{Deler # billede med et link}other{Deler # billeder med et link}}" "{count,plural, =1{Deler video med tekst}one{Deler # video med tekst}other{Deler # videoer med tekst}}" diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml index 3d2250a6..2a96c519 100644 --- a/java/res/values-de/strings.xml +++ b/java/res/values-de/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Bild wird geteilt}other{# Bilder werden geteilt}}" "{count,plural, =1{Video wird geteilt}other{# Videos werden geteilt}}" "{count,plural, =1{# Datei wird freigegeben}other{# Dateien werden freigegeben}}" + + "{count,plural, =1{Bild wird mit Text geteilt}other{# Bilder werden mit Text geteilt}}" "{count,plural, =1{Bild wird per Link geteilt}other{# Bilder werden per Link geteilt}}" "{count,plural, =1{Video wird per SMS geteilt}other{# Videos werden per SMS geteilt}}" diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml index ed09f127..ef23cf91 100644 --- a/java/res/values-el/strings.xml +++ b/java/res/values-el/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Κοινοποίηση εικόνας}other{Κοινοποίηση # εικόνων}}" "{count,plural, =1{Κοινοποίηση βίντεο}other{Κοινοποίηση # βίντεο}}" "{count,plural, =1{Κοινή χρήση # αρχείου}other{Κοινή χρήση # αρχείων}}" + + "{count,plural, =1{Κοινοποίηση εικόνας με κείμενο}other{Κοινοποίηση # εικόνων με κείμενο}}" "{count,plural, =1{Κοινοποίηση εικόνας με σύνδεσμο}other{Κοινοποίηση # εικόνων με σύνδεσμο}}" "{count,plural, =1{Κοινοποίηση βίντεο με κείμενο}other{Κοινοποίηση # βίντεο με κείμενο}}" diff --git a/java/res/values-en-rAU/strings.xml b/java/res/values-en-rAU/strings.xml index 88e86718..699b3bf0 100644 --- a/java/res/values-en-rAU/strings.xml +++ b/java/res/values-en-rAU/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" + + "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rCA/strings.xml b/java/res/values-en-rCA/strings.xml index 978da764..1c44b945 100644 --- a/java/res/values-en-rCA/strings.xml +++ b/java/res/values-en-rCA/strings.xml @@ -60,6 +60,7 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" + "Select items to share" "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml index 88e86718..699b3bf0 100644 --- a/java/res/values-en-rGB/strings.xml +++ b/java/res/values-en-rGB/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" + + "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rIN/strings.xml b/java/res/values-en-rIN/strings.xml index 88e86718..699b3bf0 100644 --- a/java/res/values-en-rIN/strings.xml +++ b/java/res/values-en-rIN/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" + + "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rXC/strings.xml b/java/res/values-en-rXC/strings.xml index 7447d83b..4fc18b62 100644 --- a/java/res/values-en-rXC/strings.xml +++ b/java/res/values-en-rXC/strings.xml @@ -60,6 +60,7 @@ "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎Sharing image‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎Sharing # images‎‏‎‎‏‎}}" "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎Sharing video‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎Sharing # videos‎‏‎‎‏‎}}" "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎Sharing # file‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎Sharing # files‎‏‎‎‏‎}}" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎Select items to share‎‏‎‎‏‎" "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎Sharing image with text‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎Sharing # images with text‎‏‎‎‏‎}}" "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎Sharing image with link‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎Sharing # images with link‎‏‎‎‏‎}}" "{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎Sharing video with text‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎Sharing # videos with text‎‏‎‎‏‎}}" diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml index 7c96aa65..9b9ea1a1 100644 --- a/java/res/values-es-rUS/strings.xml +++ b/java/res/values-es-rUS/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Compartir la imagen}many{Compartir # de imágenes}other{Compartir # imágenes}}" "{count,plural, =1{Compartiendo video}many{Compartiendo # de videos}other{Compartiendo # videos}}" "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # de archivos}other{Compartiendo # archivos}}" + + "{count,plural, =1{Compartir imagen con texto}many{Compartir # de imágenes con texto}other{Compartir # imágenes con texto}}" "{count,plural, =1{Compartir imagen con vínculo}many{Compartir # de imágenes con vínculo}other{Compartir # imágenes con vínculo}}" "{count,plural, =1{Compartir video con texto}many{Compartir # de videos con texto}other{Compartir # videos con texto}}" diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml index ca392a1f..7a029873 100644 --- a/java/res/values-es/strings.xml +++ b/java/res/values-es/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Compartiendo imagen}many{Compartiendo # imágenes}other{Compartiendo # imágenes}}" "{count,plural, =1{Compartiendo vídeo}many{Compartiendo # vídeos}other{Compartiendo # vídeos}}" "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # archivos}other{Compartiendo # archivos}}" + + "{count,plural, =1{Compartiendo imagen con texto}many{Compartiendo # imágenes con texto}other{Compartiendo # imágenes con texto}}" "{count,plural, =1{Compartiendo imagen con enlace}many{Compartiendo # imágenes con enlace}other{Compartiendo # imágenes con enlace}}" "{count,plural, =1{Compartiendo vídeo con texto}many{Compartiendo # vídeos con texto}other{Compartiendo # vídeos con texto}}" diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml index ab849b2c..86023bc5 100644 --- a/java/res/values-et/strings.xml +++ b/java/res/values-et/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Pildi jagamine}other{# pildi jagamine}}" "{count,plural, =1{Video jagamine}other{# video jagamine}}" "{count,plural, =1{# faili jagamine}other{# faili jagamine}}" + + "{count,plural, =1{Teksti sisaldava pildi jagamine}other{# teksti sisaldava pildi jagamine}}" "{count,plural, =1{Linki sisaldava pildi jagamine}other{# linki sisaldava pildi jagamine}}" "{count,plural, =1{Teksti sisaldava video jagamine}other{# teksti sisaldava video jagamine}}" diff --git a/java/res/values-eu/strings.xml b/java/res/values-eu/strings.xml index a3269d72..034abd34 100644 --- a/java/res/values-eu/strings.xml +++ b/java/res/values-eu/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Irudia partekatuko da}other{# irudi partekatuko dira}}" "{count,plural, =1{Bideoa partekatzen}other{# bideo partekatzen}}" "{count,plural, =1{# fitxategi partekatuko da}other{# fitxategi partekatuko dira}}" + + "{count,plural, =1{Irudi testudun bat partekatuko da}other{# irudi testudun partekatuko dira}}" "{count,plural, =1{Irudi estekadun bat partekatuko da}other{# irudi estekadun partekatuko dira}}" "{count,plural, =1{Bideo testudun bat partekatuko da}other{# bideo testudun partekatuko dira}}" diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index 0119fe69..85e2524d 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{هم‌رسانی تصویر}one{هم‌رسانی ‍# تصویر}other{هم‌رسانی ‍# تصویر}}" "{count,plural, =1{درحال هم‌رسانی ویدیو}one{درحال هم‌رسانی # ویدیو}other{درحال هم‌رسانی # ویدیو}}" "{count,plural, =1{هم‌رسانی # فایل}one{هم‌رسانی # فایل}other{هم‌رسانی # فایل}}" + + "{count,plural, =1{درحال هم‌رسانی تصویر با نوشتار}one{درحال هم‌رسانی # تصویر با نوشتار}other{درحال هم‌رسانی # تصویر با نوشتار}}" "{count,plural, =1{درحال هم‌رسانی تصویر با پیوند}one{درحال هم‌رسانی # تصویر با پیوند}other{درحال هم‌رسانی # تصویر با پیوند}}" "{count,plural, =1{درحال هم‌رسانی ویدیو با نوشتار}one{درحال هم‌رسانی # ویدیو با نوشتار}other{درحال هم‌رسانی # ویدیو با نوشتار}}" diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml index ee740f13..754fa720 100644 --- a/java/res/values-fi/strings.xml +++ b/java/res/values-fi/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Jaetaan kuvaa}other{Jaetaan # kuvaa}}" "{count,plural, =1{Jaetaan videota}other{Jaetaan # videota}}" "{count,plural, =1{Jaetaan # tiedosto}other{Jaetaan # tiedostoa}}" + + "{count,plural, =1{Kuvaa ja tekstiä jaetaan}other{# kuvaa ja tekstiä jaetaan}}" "{count,plural, =1{Kuvaa ja linkkiä jaetaan}other{# kuvaa ja linkkiä jaetaan}}" "{count,plural, =1{Videota ja tekstiä jaetaan}other{# videota ja tekstiä jaetaan}}" diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml index 9d1a8bb9..76f50b21 100644 --- a/java/res/values-fr-rCA/strings.xml +++ b/java/res/values-fr-rCA/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Partage d\'une image}one{Partage de # image}many{Partage de # d\'images}other{Partage de # images}}" "{count,plural, =1{Partage de la vidéo…}one{Partage de # vidéo…}many{Partage de # de vidéos…}other{Partage de # vidéos…}}" "{count,plural, =1{Partage de # fichier en cours…}one{Partage de # fichier en cours…}many{Partage de # de fichiers en cours…}other{Partage de # fichiers en cours…}}" + + "{count,plural, =1{Partage d\'une image avec du texte}one{Partage de # image avec du texte}many{Partage de # d\'images avec du texte}other{Partage de # images avec du texte}}" "{count,plural, =1{Partage d\'une image avec un lien}one{Partage de # image avec un lien}many{Partage de # d\'images avec un lien}other{Partage de # images avec un lien}}" "{count,plural, =1{Partage d\'une vidéo avec du texte}one{Partage de # vidéo avec du texte}many{Partage de # de vidéos avec du texte}other{Partage de # vidéos avec du texte}}" diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml index 6f55cbf9..1317bc71 100644 --- a/java/res/values-fr/strings.xml +++ b/java/res/values-fr/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Partager l\'image}one{Partager # image}many{Partager # d\'images}other{Partager # images}}" "{count,plural, =1{Partage de la vidéo…}one{Partage de # vidéo…}many{Partage de # de vidéos…}other{Partage de # vidéos…}}" "{count,plural, =1{Partage de # fichier}one{Partage de # fichier}many{Partage de # fichiers}other{Partage de # fichiers}}" + + "{count,plural, =1{Partager 1 image avec du texte}one{Partager # image avec du texte}many{Partager # images avec du texte}other{Partager # images avec du texte}}" "{count,plural, =1{Partager 1 image avec un lien}one{Partager # image avec un lien}many{Partager # images avec un lien}other{Partager # images avec un lien}}" "{count,plural, =1{Partager 1 vidéo avec du texte}one{Partager # vidéo avec du texte}many{Partager # vidéos avec du texte}other{Partager # vidéos avec du texte}}" diff --git a/java/res/values-gl/strings.xml b/java/res/values-gl/strings.xml index fe59eaa6..4eec6aa0 100644 --- a/java/res/values-gl/strings.xml +++ b/java/res/values-gl/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Compartindo imaxe}other{Compartindo # imaxes}}" "{count,plural, =1{Compartindo vídeo}other{Compartindo # vídeos}}" "{count,plural, =1{Compartindo # ficheiro}other{Compartindo # ficheiros}}" + + "{count,plural, =1{Compartindo imaxe con texto}other{Compartindo # imaxes con texto}}" "{count,plural, =1{Compartindo imaxe con ligazón}other{Compartindo # imaxes con ligazón}}" "{count,plural, =1{Compartindo vídeo con texto}other{Compartindo # vídeos con texto}}" diff --git a/java/res/values-gu/strings.xml b/java/res/values-gu/strings.xml index 70d84bc8..a857e943 100644 --- a/java/res/values-gu/strings.xml +++ b/java/res/values-gu/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{છબી શેર કરી રહ્યાં છીએ}one{# છબી શેર કરી રહ્યાં છીએ}other{# છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{વીડિયો શેર કરીએ છીએ}one{# વીડિયો શેર કરીએ છીએ}other{# વીડિયો શેર કરીએ છીએ}}" "{count,plural, =1{# ફાઇલ શેર કરી રહ્યાં છીએ}one{# ફાઇલ શેર કરી રહ્યાં છીએ}other{# ફાઇલ શેર કરી રહ્યાં છીએ}}" + + "{count,plural, =1{ટેક્સ્ટ સાથે છબી શેર કરી રહ્યાં છીએ}one{ટેક્સ્ટ સાથે # છબી શેર કરી રહ્યાં છીએ}other{ટેક્સ્ટ સાથે # છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{લિંક સાથે છબી શેર કરી રહ્યાં છીએ}one{લિંક સાથે # છબી શેર કરી રહ્યાં છીએ}other{લિંક સાથે # છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{ટેક્સ્ટ સાથે વીડિયો શેર કરી રહ્યાં છીએ}one{ટેક્સ્ટ સાથે # વીડિયો શેર કરી રહ્યાં છીએ}other{ટેક્સ્ટ સાથે # વીડિયો શેર કરી રહ્યાં છીએ}}" diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml index fcf484b9..f4f1846a 100644 --- a/java/res/values-hi/strings.xml +++ b/java/res/values-hi/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{इमेज शेयर की जा रही है}one{# इमेज शेयर की जा रही है}other{# इमेज शेयर की जा रही हैं}}" "{count,plural, =1{वीडियो शेयर किया जा रहा है}one{# वीडियो शेयर किया जा रहा है}other{# वीडियो शेयर किए जा रहे हैं}}" "{count,plural, =1{# फ़ाइल शेयर की जा रही है}one{# फ़ाइल शेयर की जा रही है}other{# फ़ाइलें शेयर की जा रही हैं}}" + + "{count,plural, =1{टेक्स्ट के साथ इमेज शेयर की जा रही है}one{टेक्स्ट के साथ # इमेज शेयर की जा रही है}other{टेक्स्ट के साथ # इमेज शेयर की जा रही हैं}}" "{count,plural, =1{लिंक के साथ इमेज शेयर की जा रही है}one{लिंक के साथ # इमेज शेयर की जा रही है}other{लिंक के साथ # इमेज शेयर की जा रही हैं}}" "{count,plural, =1{टेक्स्ट के साथ वीडियो शेयर किया जा रहा है}one{टेक्स्ट के साथ # वीडियो शेयर किया जा रहा है}other{टेक्स्ट के साथ # वीडियो शेयर किए जा रहे हैं}}" diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml index ca62036d..02cc3391 100644 --- a/java/res/values-hr/strings.xml +++ b/java/res/values-hr/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeli se videozapis}one{Dijeli se # videozapis}few{Dijele se # videozapisa}other{Dijeli se # videozapisa}}" "{count,plural, =1{Dijeli se # datoteka}one{Dijeli se # datoteka}few{Dijele se # datoteke}other{Dijeli se # datoteka}}" + + "{count,plural, =1{Dijeli se slika s tekstom}one{Dijeli se # slika s tekstom}few{Dijele se # slike s tekstom}other{Dijeli se # slika s tekstom}}" "{count,plural, =1{Dijeli se slika s vezom}one{Dijeli se # slika s vezom}few{Dijele se # slike s vezom}other{Dijeli se # slika s vezom}}" "{count,plural, =1{Dijeli se videozapis s tekstom}one{Dijeli se # videozapis s tekstom}few{Dijele se # videozapisa s tekstom}other{Dijeli se # videozapisa s tekstom}}" diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml index a0bce668..6874dbc7 100644 --- a/java/res/values-hu/strings.xml +++ b/java/res/values-hu/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Kép megosztása}other{# kép megosztása}}" "{count,plural, =1{Videó megosztása}other{# videó megosztása}}" "{count,plural, =1{# fájl megosztása}other{# fájl megosztása}}" + + "{count,plural, =1{Kép megosztása szöveggel}other{# kép megosztása szöveggel}}" "{count,plural, =1{Kép megosztása linkkel}other{# kép megosztása linkkel}}" "{count,plural, =1{Videó megosztása szöveggel}other{# videó megosztása szöveggel}}" diff --git a/java/res/values-hy/strings.xml b/java/res/values-hy/strings.xml index 2ee335da..da5ae914 100644 --- a/java/res/values-hy/strings.xml +++ b/java/res/values-hy/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Պատկերի ուղարկում}one{# պատկերի ուղարկում}other{# պատկերի ուղարկում}}" "{count,plural, =1{Տեսանյութի ուղարկում}one{# տեսանյութի ուղարկում}other{# տեսանյութի ուղարկում}}" "{count,plural, =1{Ուղարկվում է # ֆայլ}one{Ուղարկվում է # ֆայլ}other{Ուղարկվում է # ֆայլ}}" + + "{count,plural, =1{Պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}one{# պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}other{# պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}}" "{count,plural, =1{Պատկերի ուղարկում հղման միջոցով}one{# պատկերի ուղարկում հղման միջոցով}other{# պատկերի ուղարկում հղման միջոցով}}" "{count,plural, =1{Տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}one{# տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}other{# տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}}" diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml index 1efaf920..7bacf1b7 100644 --- a/java/res/values-in/strings.xml +++ b/java/res/values-in/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Berbagi gambar}other{Berbagi # gambar}}" "{count,plural, =1{Membagikan video}other{Membagikan # video}}" "{count,plural, =1{Membagikan # file}other{Membagikan # file}}" + + "{count,plural, =1{Membagikan gambar dengan teks}other{Membagikan # gambar dengan teks}}" "{count,plural, =1{Membagikan gambar dengan link}other{Membagikan # gambar dengan link}}" "{count,plural, =1{Membagikan video dengan teks}other{Membagikan # video dengan teks}}" diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml index 9bc4f5cb..cb889e35 100644 --- a/java/res/values-is/strings.xml +++ b/java/res/values-is/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deilir mynd}one{Deilir # mynd}other{Deilir # myndum}}" "{count,plural, =1{Deilir myndskeiði}one{Deilir # myndskeiði}other{Deilir # myndskeiðum}}" "{count,plural, =1{Deilir # skrá}one{Deilir # skrá}other{Deilir # skrám}}" + + "{count,plural, =1{Deilir mynd með texta}one{Deilir # mynd með texta}other{Deilir # myndum með texta}}" "{count,plural, =1{Deilir mynd með tengli}one{Deilir # mynd með tengli}other{Deilir # myndum með tengli}}" "{count,plural, =1{Deilir myndskeiði með texta}one{Deilir # myndskeiði með texta}other{Deilir # myndskeiðum með texta}}" diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml index 702dc48e..9858a8cd 100644 --- a/java/res/values-it/strings.xml +++ b/java/res/values-it/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Condivisione dell\'immagine}many{Condivisione di # immagini}other{Condivisione di # immagini}}" "{count,plural, =1{Condivisione del video…}many{Condivisione di # video…}other{Condivisione di # video…}}" "{count,plural, =1{Condivisione di # file in corso…}many{Condivisione di # file in corso…}other{Condivisione di # file in corso…}}" + + "{count,plural, =1{Condivisione immagine con testo in corso…}many{Condivisione # immagini con testo in corso…}other{Condivisione # immagini con testo in corso…}}" "{count,plural, =1{Condivisione immagine con link}many{Condivisione # immagini con link}other{Condivisione # immagini con link}}" "{count,plural, =1{Condivisione video con messaggio in corso…}many{Condivisione # video con messaggio in corso…}other{Condivisione # video con messaggio in corso…}}" diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml index 7c13ebd3..ec489888 100644 --- a/java/res/values-iw/strings.xml +++ b/java/res/values-iw/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{שיתוף של תמונה}one{שיתוף של # תמונות}two{שיתוף של # תמונות}other{שיתוף של # תמונות}}" "{count,plural, =1{מתבצע שיתוף של סרטון}one{מתבצע שיתוף של # סרטונים}two{מתבצע שיתוף של # סרטונים}other{מתבצע שיתוף של # סרטונים}}" "{count,plural, =1{מתבצע שיתוף של קובץ אחד}one{מתבצע שיתוף של # קבצים}two{מתבצע שיתוף של # קבצים}other{מתבצע שיתוף של # קבצים}}" + + "{count,plural, =1{שיתוף תמונה עם טקסט}one{שיתוף # תמונות עם טקסט}two{שיתוף # תמונות עם טקסט}other{שיתוף # תמונות עם טקסט}}" "{count,plural, =1{שיתוף תמונה עם קישור}one{שיתוף # תמונות עם קישור}two{שיתוף # תמונות עם קישור}other{שיתוף # תמונות עם קישור}}" "{count,plural, =1{שיתוף סרטון עם טקסט}one{שיתוף # סרטונים עם טקסט}two{שיתוף # סרטונים עם טקסט}other{שיתוף # סרטונים עם טקסט}}" diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml index 0c97d64a..1d90a71b 100644 --- a/java/res/values-ja/strings.xml +++ b/java/res/values-ja/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{1 枚の画像を共有します}other{# 枚の画像を共有します}}" "{count,plural, =1{動画を共有中}other{# 個の動画を共有中}}" "{count,plural, =1{# 個のファイルを共有中}other{# 個のファイルを共有中}}" + + "{count,plural, =1{テキスト付き画像を共有しています}other{テキスト付き画像を # 件共有しています}}" "{count,plural, =1{リンク付き画像を共有しています}other{リンク付き画像を # 件共有しています}}" "{count,plural, =1{テキスト付き動画を共有中}other{テキスト付き動画を # 件共有中}}" diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml index 46d1f1e7..ad8e6dfb 100644 --- a/java/res/values-ka/strings.xml +++ b/java/res/values-ka/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ზიარდება სურათი}other{ზიარდება # სურათი}}" "{count,plural, =1{ზიარდება ვიდეო}other{ზიარდება # ვიდეო}}" "{count,plural, =1{ზიარდება # ფაილი}other{ზიარდება # ფაილი}}" + + "{count,plural, =1{სურათი ზიარდება ტექსტით}other{# სურათი ზიარდება ტექსტით}}" "{count,plural, =1{სურათი ზიარდება ბმულით}other{# სურათი ზიარდება ბმულით}}" "{count,plural, =1{ვიდეო ზიარდება ტექსტით}other{# ვიდეო ზიარდება ტექსტით}}" diff --git a/java/res/values-kk/strings.xml b/java/res/values-kk/strings.xml index ee3135fa..ce71c71d 100644 --- a/java/res/values-kk/strings.xml +++ b/java/res/values-kk/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Сурет бөлісіп жатырсыз}other{# сурет бөлісіп жатырсыз}}" "{count,plural, =1{Бейне бөлісіліп жатыр}other{# бейне бөлісіліп жатыр}}" "{count,plural, =1{# файлды бөлісіп жатыр}other{# файлды бөлісіп жатыр}}" + + "{count,plural, =1{Мәтіні бар сурет жіберу}other{Мәтіні бар # сурет жіберу}}" "{count,plural, =1{Сілтемесі бар сурет жіберу}other{Сілтемесі бар # сурет жіберу}}" "{count,plural, =1{Мәтіні бар бейне жіберу}other{Мәтіні бар # бейне жіберу}}" diff --git a/java/res/values-km/strings.xml b/java/res/values-km/strings.xml index eb2ef8a0..0e75c153 100644 --- a/java/res/values-km/strings.xml +++ b/java/res/values-km/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{កំពុងចែក​រំលែករូបភាព}other{កំពុងចែក​រំលែករូបភាព #}}" "{count,plural, =1{កំពុងចែករំលែកវីដេអូ}other{កំពុងចែករំលែកវីដេអូ #}}" "{count,plural, =1{កំពុង​ចែករំលែកឯកសារ #}other{កំពុង​ចែករំលែកឯកសារ #}}" + + "{count,plural, =1{ចែករំលែករូបភាពជាមួយអក្សរ}other{ចែករំលែករូបភាព # ជាមួយអក្សរ}}" "{count,plural, =1{ចែករំលែករូបភាពជាមួយតំណ}other{ចែករំលែករូបភាព # ជាមួយតំណ}}" "{count,plural, =1{ចែករំលែកវីដេអូជាមួយអក្សរ}other{ចែករំលែក # វីដេអូជាមួយអក្សរ}}" diff --git a/java/res/values-kn/strings.xml b/java/res/values-kn/strings.xml index 17f3b295..84c830d2 100644 --- a/java/res/values-kn/strings.xml +++ b/java/res/values-kn/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{# ಫೈಲ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" + + "{count,plural, =1{ಪಠ್ಯದೊಂದಿಗೆ ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಪಠ್ಯದೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಪಠ್ಯದೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ಲಿಂಕ್‌ನೊಂದಿಗೆ ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಲಿಂಕ್‌ನೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಲಿಂಕ್‌ನೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ಪಠ್ಯದೊಂದಿಗೆ ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಪಠ್ಯದೊಂದಿಗೆ # ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಪಠ್ಯದೊಂದಿಗೆ # ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml index b75b9bdd..b9c27045 100644 --- a/java/res/values-ko/strings.xml +++ b/java/res/values-ko/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{이미지 공유}other{이미지 #개 공유}}" "{count,plural, =1{동영상 1개 공유 중}other{동영상 #개 공유 중}}" "{count,plural, =1{파일 #개 공유 중}other{파일 #개 공유 중}}" + + "{count,plural, =1{텍스트로 이미지 공유 중}other{텍스트로 이미지 #개 공유 중}}" "{count,plural, =1{링크로 이미지 공유 중}other{링크로 이미지 #개 공유 중}}" "{count,plural, =1{텍스트로 동영상 공유 중}other{텍스트로 동영상 #개 공유 중}}" diff --git a/java/res/values-ky/strings.xml b/java/res/values-ky/strings.xml index 6f84e1bf..55d91a02 100644 --- a/java/res/values-ky/strings.xml +++ b/java/res/values-ky/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Сүрөт бөлүшүү}other{# сүрөт бөлүшүлүүдө}}" "{count,plural, =1{Видео бөлүшүлүүдө}other{# видео бөлүшүлүүдө}}" "{count,plural, =1{# файл бөлүшүлүүдө}other{# файл бөлүшүлүүдө}}" + + "{count,plural, =1{Сүрөттү текст менен жөнөтүү}other{# cүрөттү текст менен жөнөтүү}}" "{count,plural, =1{Сүрөттү шилтеме менен жөнөтүү}other{# сүрөттү шилтеме менен жөнөтүү}}" "{count,plural, =1{Видеону текст менен жөнөтүү}other{# видеону текст менен жөнөтүү}}" diff --git a/java/res/values-lo/strings.xml b/java/res/values-lo/strings.xml index 2a65f486..cb6050d1 100644 --- a/java/res/values-lo/strings.xml +++ b/java/res/values-lo/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບ}other{ກຳລັງແບ່ງປັນ # ຮູບ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນວິດີໂອ}other{ກຳລັງແບ່ງປັນ # ວິດີໂອ}}" "{count,plural, =1{ກຳລັງຈະແບ່ງປັນ # ໄຟລ໌}other{ກຳລັງຈະແບ່ງປັນ # ໄຟລ໌}}" + + "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບພ້ອມຂໍ້ຄວາມ}other{ກຳລັງແບ່ງປັນ # ຮູບພ້ອມຂໍ້ຄວາມ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບພ້ອມລິ້ງ}other{ກຳລັງແບ່ງປັນ # ຮູບພ້ອມລິ້ງ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນວິດີໂອພ້ອມຂໍ້ຄວາມ}other{ກຳລັງແບ່ງປັນ # ວິດີໂອພ້ອມຂໍ້ຄວາມ}}" diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml index bb495311..706ed856 100644 --- a/java/res/values-lt/strings.xml +++ b/java/res/values-lt/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Bendrinamas vaizdas}one{Bendrinamas # vaizdas}few{Bendrinami # vaizdai}many{Bendrinama # vaizdo}other{Bendrinama # vaizdų}}" "{count,plural, =1{Bendrinamas vaizdo įrašas}one{Bendrinamas # vaizdo įrašas}few{Bendrinami # vaizdo įrašai}many{Bendrinama # vaizdo įrašo}other{Bendrinama # vaizdo įrašų}}" "{count,plural, =1{Bendrinamas # failas}one{Bendrinamas # failas}few{Bendrinami # failai}many{Bendrinama # failo}other{Bendrinama # failų}}" + + "{count,plural, =1{Bendrinamas vaizdas su tekstu}one{Bendrinamas # vaizdas su tekstu}few{Bendrinami # vaizdai su tekstu}many{Bendrinama # vaizdo su tekstu}other{Bendrinama # vaizdų su tekstu}}" "{count,plural, =1{Bendrinamas vaizdas su nuoroda}one{Bendrinamas # vaizdas su nuoroda}few{Bendrinami # vaizdai su nuoroda}many{Bendrinama # vaizdo su nuoroda}other{Bendrinama # vaizdų su nuoroda}}" "{count,plural, =1{Bendrinamas vaizdo įrašas su tekstu}one{Bendrinamas # vaizdo įrašas su tekstu}few{Bendrinami # vaizdo įrašai su tekstu}many{Bendrinama # vaizdo įrašo su tekstu}other{Bendrinama # vaizdo įrašų su tekstu}}" diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml index 7dd6cac9..a53bc08b 100644 --- a/java/res/values-lv/strings.xml +++ b/java/res/values-lv/strings.xml @@ -60,6 +60,8 @@ "{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}}" "{count,plural, =1{Tiek kopīgots video}zero{Tiek kopīgoti # video}one{Tiek kopīgots # video}other{Tiek kopīgoti # video}}" "{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}}" + + "{count,plural, =1{Tiek kopīgots attēls ar tekstu}zero{Tiek kopīgoti # attēli ar tekstu}one{Tiek kopīgots # attēls ar tekstu}other{Tiek kopīgoti # attēli ar tekstu}}" "{count,plural, =1{Tiek kopīgots attēls ar saiti}zero{Tiek kopīgoti # attēli ar saitēm}one{Tiek kopīgots # attēls ar saitēm}other{Tiek kopīgoti # attēli ar saitēm}}" "{count,plural, =1{Tiek kopīgots videoklips ar tekstu}zero{Tiek kopīgoti # videoklipi ar tekstu}one{Tiek kopīgots # videoklips ar tekstu}other{Tiek kopīgoti # videoklipi ar tekstu}}" diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml index 45fb82e3..1d01459a 100644 --- a/java/res/values-mk/strings.xml +++ b/java/res/values-mk/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Споделување слика}one{Споделување # слика}other{Споделување # слики}}" "{count,plural, =1{Се споделува видео}one{Се споделува # видео}other{Се споделуваат # видеа}}" "{count,plural, =1{Се споделува # датотека}one{Се споделуваат # датотека}other{Се споделуваат # датотеки}}" + + "{count,plural, =1{Се споделува слика со SMS}one{Се споделуваат # слика со SMS}other{Се споделуваат # слики со SMS}}" "{count,plural, =1{Се споделува слика со линк}one{Се споделуваат # слика со линк}other{Се споделуваат # слики со линк}}" "{count,plural, =1{Се споделува видео со SMS}one{Се споделуваат # видео со SMS}other{Се споделуваат # видеа со SMS}}" diff --git a/java/res/values-ml/strings.xml b/java/res/values-ml/strings.xml index ce466e8f..3faa7981 100644 --- a/java/res/values-ml/strings.xml +++ b/java/res/values-ml/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ചിത്രം പങ്കിടുന്നു}other{# ചിത്രങ്ങൾ പങ്കിടുന്നു}}" "{count,plural, =1{വീഡിയോ പങ്കിടുന്നു}other{# വീഡിയോകൾ പങ്കിടുന്നു}}" "{count,plural, =1{# ഫയൽ പങ്കിടുന്നു}other{# ഫയലുകൾ പങ്കിടുന്നു}}" + + "{count,plural, =1{ടെക്സ്റ്റിനൊപ്പം ചിത്രം പങ്കിടുന്നു}other{ടെക്സ്റ്റിനൊപ്പം # ചിത്രം പങ്കിടുന്നു}}" "{count,plural, =1{ലിങ്കിനൊപ്പം ചിത്രം പങ്കിടുന്നു}other{ലിങ്കിനൊപ്പം # ചിത്രങ്ങൾ പങ്കിടുന്നു}}" "{count,plural, =1{ടെക്സ്റ്റിനൊപ്പം വീഡിയോ പങ്കിടുന്നു}other{ടെക്സ്റ്റിനൊപ്പം # വീഡിയോകൾ പങ്കിടുന്നു}}" diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml index 30686c51..4287f8c9 100644 --- a/java/res/values-mn/strings.xml +++ b/java/res/values-mn/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Зураг хуваалцаж байна}other{# зураг хуваалцаж байна}}" "{count,plural, =1{Видео хуваалцаж байна}other{# видео хуваалцаж байна}}" "{count,plural, =1{# файл хуваалцаж байна}other{# файл хуваалцаж байна}}" + + "{count,plural, =1{Тексттэй зураг хуваалцаж байна}other{Тексттэй # зураг хуваалцаж байна}}" "{count,plural, =1{Холбоостой зураг хуваалцаж байна}other{Холбоостой # зураг хуваалцаж байна}}" "{count,plural, =1{Тексттэй видео хуваалцаж байна}other{Тексттэй # видео хуваалцаж байна}}" diff --git a/java/res/values-mr/strings.xml b/java/res/values-mr/strings.xml index 9ad4a4c8..f964e1f5 100644 --- a/java/res/values-mr/strings.xml +++ b/java/res/values-mr/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{इमेज शेअर करत आहे}other{# इमेज शेअर करत आहे}}" "{count,plural, =1{व्हिडिओ शेअर करत आहे}other{# व्हिडिओ शेअर करत आहे}}" "{count,plural, =1{# फाइल शेअर करत आहे}other{# फाइल शेअर करत आहे}}" + + "{count,plural, =1{मजकुरासह इमेज शेअर करत आहे}other{मजकुरासह # इमेज शेअर करत आहे}}" "{count,plural, =1{लिंकसह इमेज शेअर करत आहे}other{लिंकसह # इमेज शेअर करत आहे}}" "{count,plural, =1{मजकुरासह व्हिडिओ शेअर करत आहे}other{मजकुरासह # व्हिडिओ शेअर करत आहे}}" diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml index 92e7a26f..c851c9e2 100644 --- a/java/res/values-ms/strings.xml +++ b/java/res/values-ms/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Berkongsi imej}other{Berkongsi # imej}}" "{count,plural, =1{Berkongsi video}other{Berkongsi # video}}" "{count,plural, =1{Berkongsi # fail}other{Berkongsi # fail}}" + + "{count,plural, =1{Berkongsi imej dengan teks}other{Berkongsi # imej dengan teks}}" "{count,plural, =1{Berkongsi imej dengan pautan}other{Berkongsi # imej dengan pautan}}" "{count,plural, =1{Berkongsi video dengan teks}other{Berkongsi # video dengan teks}}" diff --git a/java/res/values-my/strings.xml b/java/res/values-my/strings.xml index 1f78c7f1..b6de8795 100644 --- a/java/res/values-my/strings.xml +++ b/java/res/values-my/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ပုံ မျှဝေနေသည်}other{ပုံ # ပုံ မျှဝေနေသည်}}" "{count,plural, =1{ဗီဒီယို မျှဝေနေသည်}other{ဗီဒီယို # ခု မျှဝေနေသည်}}" "{count,plural, =1{# ဖိုင် မျှဝေနေသည်}other{# ဖိုင် မျှဝေနေသည်}}" + + "{count,plural, =1{စာသားပါသောပုံကို မျှဝေနေသည်}other{စာသားပါသောပုံ # ပုံကို မျှဝေနေသည်}}" "{count,plural, =1{လင့်ခ်ပါသောပုံကို မျှဝေနေသည်}other{လင့်ခ်ပါသောပုံ # ပုံကို မျှဝေနေသည်}}" "{count,plural, =1{စာသားပါသောဗီဒီယိုကို မျှဝေနေသည်}other{စာသားပါသောဗီဒီယို # ခုကို မျှဝေနေသည်}}" diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml index f9b91f7a..66eb54a9 100644 --- a/java/res/values-nb/strings.xml +++ b/java/res/values-nb/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deler bildet}other{Deler # bilder}}" "{count,plural, =1{Deler videoen}other{Deler # videoer}}" "{count,plural, =1{Deler # fil}other{Deler # filer}}" + + "{count,plural, =1{Deler bildet med tekst}other{Deler # bilder med tekst}}" "{count,plural, =1{Deler bildet med link}other{Deler # bilder med link}}" "{count,plural, =1{Deler videoen med tekst}other{Deler # videoer med tekst}}" diff --git a/java/res/values-ne/strings.xml b/java/res/values-ne/strings.xml index 61c7fe17..22596863 100644 --- a/java/res/values-ne/strings.xml +++ b/java/res/values-ne/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{फोटो सेयर गरिँदै छ}other{# वटा फोटो सेयर गरिँदै छ}}" "{count,plural, =1{भिडियो सेयर गरिँदै छ}other{# वटा भिडियो सेयर गरिँदै छ}}" "{count,plural, =1{# वटा फाइल सेयर गरिँदै छ}other{# वटा फाइल सेयर गरिँदै छ}}" + + "{count,plural, =1{टेक्स्ट भएको फोटो सेयर गरिँदै छ}other{टेक्स्ट भएका # वटा फोटो सेयर गरिँदै छन्}}" "{count,plural, =1{लिंक भएको फोटो सेयर गरिँदै छ}other{लिंक भएका # वटा फोटो सेयर गरिँदै छन्}}" "{count,plural, =1{टेक्स्ट भएको भिडियो सेयर गरिँदै छ}other{टेक्स्ट भएका # वटा भिडियो सेयर गरिँदै छन्}}" diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml index a259a205..d1aee9cb 100644 --- a/java/res/values-nl/strings.xml +++ b/java/res/values-nl/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Afbeelding delen}other{# afbeeldingen delen}}" "{count,plural, =1{Video delen}other{# video\'s delen}}" "{count,plural, =1{# bestand delen}other{# bestanden delen}}" + + "{count,plural, =1{Afbeelding met tekst wordt gedeeld}other{# afbeeldingen met tekst worden gedeeld}}" "{count,plural, =1{Afbeelding delen via link}other{# afbeeldingen delen via link}}" "{count,plural, =1{Video delen via tekstbericht}other{# video\'s delen via tekstbericht}}" diff --git a/java/res/values-or/strings.xml b/java/res/values-or/strings.xml index 7586ae91..0c1b03ff 100644 --- a/java/res/values-or/strings.xml +++ b/java/res/values-or/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ଇମେଜ ସେୟାର କରାଯାଉଛି}other{#ଟିି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଭିଡିଓ ସେୟାର କରାଯାଉଛି}other{#ଟି ଭିଡିଓ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{#ଟି ଫାଇଲ ସେୟାର କରାଯାଉଛି}other{#ଟି ଫାଇଲ ସେୟାର କରାଯାଉଛି}}" + + "{count,plural, =1{ଟେକ୍ସଟ ସହ ଇମେଜ ସେୟାର କରାଯାଉଛି}other{ଟେକ୍ସଟ ସହ #ଟି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଲିଙ୍କ ସହ ଇମେଜ ସେୟାର କରାଯାଉଛି}other{ଲିଙ୍କ ସହ #ଟି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଟେକ୍ସଟ ସହ ଭିଡିଓ ସେୟାର କରାଯାଉଛି}other{ଟେକ୍ସଟ ସହ #ଟି ଭିଡିଓ ସେୟାର କରାଯାଉଛି}}" diff --git a/java/res/values-pa/strings.xml b/java/res/values-pa/strings.xml index 04565373..b01de893 100644 --- a/java/res/values-pa/strings.xml +++ b/java/res/values-pa/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ਚਿੱਤਰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{# ਚਿੱਤਰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{# ਚਿੱਤਰ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" "{count,plural, =1{ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{# ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{# ਵੀਡੀਓ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" "{count,plural, =1{# ਫ਼ਾਈਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ}one{# ਫ਼ਾਈਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ}other{# ਫ਼ਾਈਲਾਂ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਜਾ ਰਹੀਆਂ ਹਨ}}" + + "{count,plural, =1{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਚਿੱਤਰਾਂ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}}" "{count,plural, =1{ਲਿੰਕ ਨਾਲ ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿੰਕ ਨਾਲ # ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿੰਕ ਨਾਲ # ਚਿੱਤਰਾਂ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}}" "{count,plural, =1{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਵੀਡੀਓ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml index e67510e3..78e6457c 100644 --- a/java/res/values-pl/strings.xml +++ b/java/res/values-pl/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Udostępniam obraz}few{Udostępniam # obrazy}many{Udostępniam # obrazów}other{Udostępniam # obrazu}}" "{count,plural, =1{Udostępnianie filmu}few{Udostępnianie # filmów}many{Udostępnianie # filmów}other{Udostępnianie # filmu}}" "{count,plural, =1{Udostępnianie # pliku}few{Udostępnianie # plików}many{Udostępnianie # plików}other{Udostępnianie # pliku}}" + + "{count,plural, =1{Udostępnianie obrazu przez SMS}few{Udostępnianie # obrazów przez SMS}many{Udostępnianie # obrazów przez SMS}other{Udostępnianie # obrazu przez SMS}}" "{count,plural, =1{Udostępnianie obrazu przez link}few{Udostępnianie # obrazów przez link}many{Udostępnianie # obrazów przez link}other{Udostępnianie # obrazu przez link}}" "{count,plural, =1{Udostępnianie filmu przez SMS}few{Udostępnianie # filmów przez SMS}many{Udostępnianie # filmów przez SMS}other{Udostępnianie # filmu przez SMS}}" diff --git a/java/res/values-pt-rBR/strings.xml b/java/res/values-pt-rBR/strings.xml index b5778cf6..7c33d050 100644 --- a/java/res/values-pt-rBR/strings.xml +++ b/java/res/values-pt-rBR/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" + + "{count,plural, =1{Compartilhando imagem com texto}one{Compartilhando # imagem com texto}many{Compartilhando # de imagens com texto}other{Compartilhando # imagens com texto}}" "{count,plural, =1{Compartilhando imagem com link}one{Compartilhando # imagem com link}many{Compartilhando # de imagens com link}other{Compartilhando # imagens com link}}" "{count,plural, =1{Compartilhando vídeo com texto}one{Compartilhando # vídeo com texto}many{Compartilhando # de vídeos com texto}other{Compartilhando # vídeos com texto}}" diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml index 0abb79be..60abf642 100644 --- a/java/res/values-pt-rPT/strings.xml +++ b/java/res/values-pt-rPT/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Partilhar imagem}many{Partilhar # imagens}other{Partilhar # imagens}}" "{count,plural, =1{A partilhar vídeo}many{A partilhar # vídeos}other{A partilhar # vídeos}}" "{count,plural, =1{A partilhar # ficheiro}many{A partilhar # ficheiros}other{A partilhar # ficheiros}}" + + "{count,plural, =1{A partilhar imagem com texto}many{A partilhar # imagens com texto}other{A partilhar # imagens com texto}}" "{count,plural, =1{A partilhar imagem com link}many{A partilhar # imagens com link}other{A partilhar # imagens com link}}" "{count,plural, =1{A partilhar vídeo com texto}many{A partilhar # vídeos com texto}other{A partilhar # vídeos com texto}}" diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml index b5778cf6..7c33d050 100644 --- a/java/res/values-pt/strings.xml +++ b/java/res/values-pt/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" + + "{count,plural, =1{Compartilhando imagem com texto}one{Compartilhando # imagem com texto}many{Compartilhando # de imagens com texto}other{Compartilhando # imagens com texto}}" "{count,plural, =1{Compartilhando imagem com link}one{Compartilhando # imagem com link}many{Compartilhando # de imagens com link}other{Compartilhando # imagens com link}}" "{count,plural, =1{Compartilhando vídeo com texto}one{Compartilhando # vídeo com texto}many{Compartilhando # de vídeos com texto}other{Compartilhando # vídeos com texto}}" diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml index 02d5df12..e68e9b45 100644 --- a/java/res/values-ro/strings.xml +++ b/java/res/values-ro/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Se trimite imaginea}few{Se trimit # imagini}other{Se trimit # de imagini}}" "{count,plural, =1{Se trimite videoclipul}few{Se trimit # videoclipuri}other{Se trimit # de videoclipuri}}" "{count,plural, =1{Se trimite un fișier}few{Se trimit # fișiere}other{Se trimit # de fișiere}}" + + "{count,plural, =1{Se trimite imaginea cu text}few{Se trimit # imagini cu text}other{Se trimit # de imagini cu text}}" "{count,plural, =1{Se trimite imaginea cu linkul}few{Se trimit # imagini cu linkul}other{Se trimit # de imagini cu linkul}}" "{count,plural, =1{Se trimite videoclipul cu text}few{Se trimit # videoclipuri cu text}other{Se trimit # de videoclipuri cu text}}" diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml index fa8a06a3..bf2f31c9 100644 --- a/java/res/values-ru/strings.xml +++ b/java/res/values-ru/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Отправка изображения}one{Отправка # изображения}few{Отправка # изображений}many{Отправка # изображений}other{Отправка # изображения}}" "{count,plural, =1{Отправка видео}one{Отправка # видео}few{Отправка # видео}many{Отправка # видео}other{Отправка # видео}}" "{count,plural, =1{Предоставляется доступ к # файлу}one{Предоставляется доступ к # файлу}few{Предоставляется доступ к # файлам}many{Предоставляется доступ к # файлам}other{Предоставляется доступ к # файла}}" + + "{count,plural, =1{Отправка изображения с текстом}one{Отправка # изображения с текстом}few{Отправка # изображений с текстом}many{Отправка # изображений с текстом}other{Отправка # изображения с текстом}}" "{count,plural, =1{Отправка изображения со ссылкой}one{Отправка # изображения со ссылкой}few{Отправка # изображений со ссылкой}many{Отправка # изображений со ссылкой}other{Отправка # изображения со ссылкой}}" "{count,plural, =1{Отправка видео с текстом}one{Отправка # видео с текстом}few{Отправка # видео с текстом}many{Отправка # видео с текстом}other{Отправка # видео с текстом}}" diff --git a/java/res/values-si/strings.xml b/java/res/values-si/strings.xml index 6f5be5f5..cf890aca 100644 --- a/java/res/values-si/strings.xml +++ b/java/res/values-si/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{රූපය බෙදා ගැනීම}one{රූප #ක් බෙදා ගැනීම}other{රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{වීඩියෝව බෙදා ගැනීම}one{වීඩියෝ #ක් බෙදා ගැනීම}other{වීඩියෝ #ක් බෙදා ගැනීම}}" "{count,plural, =1{# ගොනුවක් බෙදා ගැනීම}one{ගොනු #ක් බෙදා ගැනීම}other{ගොනු #ක් බෙදා ගැනීම}}" + + "{count,plural, =1{පෙළ සමග රූපය බෙදා ගැනීම}one{පෙළ සමග රූප #ක් බෙදා ගැනීම}other{පෙළ සමග රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{සබැඳිය සමග රූපය බෙදා ගැනීම}one{සබැඳිය සමග රූප #ක් බෙදා ගැනීම}other{සබැඳිය සමග රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{පෙළ සමග වීඩියෝව බෙදා ගැනීම}one{පෙළ සමග වීඩියෝ #ක් බෙදා ගැනීම}other{පෙළ සමග වීඩියෝ #ක් බෙදා ගැනීම}}" diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml index 926d9d50..c5fb9984 100644 --- a/java/res/values-sk/strings.xml +++ b/java/res/values-sk/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Zdieľanie obrázku}few{Zdieľanie # obrázkov}many{Sharing # images}other{Zdieľanie # obrázkov}}" "{count,plural, =1{Zdieľa sa video}few{Zdieľajú sa # videá}many{Sharing # videos}other{Zdieľa sa # videí}}" "{count,plural, =1{Zdieľa sa # súbor}few{Zdieľajú sa # súbory}many{Sharing # files}other{Zdieľa sa # súborov}}" + + "{count,plural, =1{Zdieľa sa obrázok s textom}few{Zdieľajú sa # obrázky s textom}many{Sharing # images with text}other{Zdieľa sa # obrázkov s textom}}" "{count,plural, =1{Zdieľa sa obrázok s odkazom}few{Zdieľajú sa # obrázky s odkazom}many{Sharing # images with link}other{Zdieľa sa # obrázkov s odkazom}}" "{count,plural, =1{Zdieľa sa video s textom}few{Zdieľajú sa # videá s textom}many{Sharing # videos with text}other{Zdieľa sa # videí s textom}}" diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml index afa61945..b7685e05 100644 --- a/java/res/values-sl/strings.xml +++ b/java/res/values-sl/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Deljenje slike}one{Deljenje # slike}two{Deljenje # slik}few{Deljenje # slik}other{Deljenje # slik}}" "{count,plural, =1{Deljenje videoposnetka}one{Deljenje # videoposnetka}two{Deljenje # videoposnetkov}few{Deljenje # videoposnetkov}other{Deljenje # videoposnetkov}}" "{count,plural, =1{Deljenje # datoteke}one{Deljenje # datoteke}two{Deljenje # datotek}few{Deljenje # datotek}other{Deljenje # datotek}}" + + "{count,plural, =1{Deljenje slike z besedilom}one{Deljenje # slike z besedilom}two{Deljenje # slik z besedilom}few{Deljenje # slik z besedilom}other{Deljenje # slik z besedilom}}" "{count,plural, =1{Deljenje slike s povezavo}one{Deljenje # slike s povezavo}two{Deljenje # slik s povezavo}few{Deljenje # slik s povezavo}other{Deljenje # slik s povezavo}}" "{count,plural, =1{Deljenje videoposnetka z besedilom}one{Deljenje # videoposnetka z besedilom}two{Deljenje # videoposnetkov z besedilom}few{Deljenje # videoposnetkov z besedilom}other{Deljenje # videoposnetkov z besedilom}}" diff --git a/java/res/values-sq/strings.xml b/java/res/values-sq/strings.xml index faf27da5..a661aecc 100644 --- a/java/res/values-sq/strings.xml +++ b/java/res/values-sq/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Po ndahet imazh}other{Po ndahen # imazhe}}" "{count,plural, =1{Po ndahet videoja}other{Po ndahen # video}}" "{count,plural, =1{Po ndahet # skedar}other{Po ndahen # skedarë}}" + + "{count,plural, =1{Po ndahet një imazh me tekst}other{Po ndahen # imazhe me tekst}}" "{count,plural, =1{Po ndahet një imazh me lidhje}other{Po ndahen # imazhe me lidhje}}" "{count,plural, =1{Po ndahet një video me tekst}other{Po ndahen # video me tekst}}" diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml index 1a9834d9..70310f44 100644 --- a/java/res/values-sr/strings.xml +++ b/java/res/values-sr/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Дељење слике}one{Дељење # слике}few{Дељење # слике}other{Дељење # слика}}" "{count,plural, =1{Дели се видео}one{Дели се # видео}few{Деле се # видео снимка}other{Дели се # видеа}}" "{count,plural, =1{Дели се # фајл}one{Дели се # фајл}few{Деле се # фајла}other{Дели се # фајлова}}" + + "{count,plural, =1{Дели се слика са текстом}one{Дели се # слика са текстом}few{Деле се # слике са текстом}other{Дели се # слика са текстом}}" "{count,plural, =1{Дели се слика са линком}one{Дели се # слика са линком}few{Деле се # слике са линком}other{Дели се # слика са линком}}" "{count,plural, =1{Дели се видео са текстом}one{Дели се # видео са текстом}few{Деле се # видео снимка са текстом}other{Дели се # видеа са текстом}}" diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml index c20b2a43..02807b03 100644 --- a/java/res/values-sv/strings.xml +++ b/java/res/values-sv/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Delar bild}other{Delar # bilder}}" "{count,plural, =1{Delar video}other{Delar # videor}}" "{count,plural, =1{Delar # fil}other{Delar # filer}}" + + "{count,plural, =1{Delar bild med text}other{Delar # bilder med text}}" "{count,plural, =1{Delar bild med länk}other{Delar # bilder med länk}}" "{count,plural, =1{Delar video med text}other{Delar # videor med text}}" diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml index 3f99f9e7..b7814f7b 100644 --- a/java/res/values-sw/strings.xml +++ b/java/res/values-sw/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Inashiriki picha}other{Inashiriki picha #}}" "{count,plural, =1{Inashiriki video}other{Inashiriki video #}}" "{count,plural, =1{Inashiriki faili #}other{Inashiriki faili #}}" + + "{count,plural, =1{Inashiriki picha na maandishi}other{Inashiriki picha # na maandishi}}" "{count,plural, =1{Inashiriki picha na kiungo}other{Inashiriki picha # na kiungo}}" "{count,plural, =1{Inashiriki video na maandishi}other{Inashiriki video # na maandishi}}" diff --git a/java/res/values-ta/strings.xml b/java/res/values-ta/strings.xml index f2fbb6e3..301efaf0 100644 --- a/java/res/values-ta/strings.xml +++ b/java/res/values-ta/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{படத்தைப் பகிர்கிறது}other{# படங்களைப் பகிர்கிறது}}" "{count,plural, =1{வீடியோவைப் பகிர்கிறது}other{# வீடியோக்களை பகிர்கிறது}}" "{count,plural, =1{# ஃபைலைப் பகிர்கிறது}other{# ஃபைல்களைப் பகிர்கிறது}}" + + "{count,plural, =1{வார்த்தைகளுடன் படத்தைப் பகிர்கிறது}other{வார்த்தைகளுடன் # படங்களைப் பகிர்கிறது}}" "{count,plural, =1{இணைப்பைக் கொண்ட படத்தைப் பகிர்கிறது}other{இணைப்பைக் கொண்ட # படங்களைப் பகிர்கிறது}}" "{count,plural, =1{வார்த்தைகளைக் கொண்ட வீடியோவைப் பகிர்கிறது}other{வார்த்தைகளைக் கொண்ட # வீடியோக்களைப் பகிர்கிறது}}" diff --git a/java/res/values-te/strings.xml b/java/res/values-te/strings.xml index 840279f3..d0490613 100644 --- a/java/res/values-te/strings.xml +++ b/java/res/values-te/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{ఈ ఇమేజ్‌ను షేర్ చేస్తున్నారు}other{ఈ # ఇమేజ్‌లను షేర్ చేస్తున్నారు}}" "{count,plural, =1{వీడియోను షేర్ చేయడం}other{# వీడియోలను షేర్ చేయడం}}" "{count,plural, =1{# ఫైల్‌ను షేర్ చేస్తోంది}other{# ఫైళ్లను షేర్ చేస్తోంది}}" + + "{count,plural, =1{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా ఇమేజ్‌ను షేర్ చేయడం}other{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా # ఇమేజ్‌లను షేర్ చేయడం}}" "{count,plural, =1{లింక్ చేయడం ద్వారా ఇమేజ్‌ను షేర్ చేయడం}other{లింక్ చేయడం ద్వారా # ఇమేజ్‌లను షేర్ చేయడం}}" "{count,plural, =1{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా వీడియోను షేర్ చేయడం}other{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా # వీడియోలను షేర్ చేయడం}}" diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml index 29a97978..7616b31c 100644 --- a/java/res/values-th/strings.xml +++ b/java/res/values-th/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{กำลังแชร์รูปภาพ}other{กำลังแชร์รูปภาพ # รายการ}}" "{count,plural, =1{กำลังแชร์วิดีโอ}other{กำลังแชร์วิดีโอ # รายการ}}" "{count,plural, =1{กำลังจะแชร์ # ไฟล์}other{กำลังจะแชร์ # ไฟล์}}" + + "{count,plural, =1{กำลังแชร์รูปภาพพร้อมข้อความ}other{กำลังแชร์รูปภาพ # รายการพร้อมข้อความ}}" "{count,plural, =1{กำลังแชร์รูปภาพพร้อมลิงก์}other{กำลังแชร์รูปภาพ # รายการพร้อมลิงก์}}" "{count,plural, =1{กำลังแชร์วิดีโอพร้อมข้อความ}other{กำลังแชร์วิดีโอ # รายการพร้อมข้อความ}}" diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml index b085b46b..708be5e8 100644 --- a/java/res/values-tl/strings.xml +++ b/java/res/values-tl/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Shine-share ang larawan}one{Shine-share ang # larawan}other{Shine-share ang # na larawan}}" "{count,plural, =1{Ibinabahagi ang video}one{Ibinabahagi ang # video}other{Ibinabahagi ang # na video}}" "{count,plural, =1{Nagshe-share ng # file}one{Nagshe-share ng # file}other{Nagshe-share ng # na file}}" + + "{count,plural, =1{Nagbabahagi ng larawang may text}one{Nagbabahagi ng # larawang may text}other{Nagbabahagi ng # na larawang may text}}" "{count,plural, =1{Nagbabahagi ng larawang may link}one{Nagbabahagi ng # larawang may link}other{Nagbabahagi ng # na larawang may link}}" "{count,plural, =1{Nagbabahagi ng video na may text}one{Nagbabahagi ng # video na may text}other{Nagbabahagi ng # na video na may text}}" diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml index 22024818..a633d62e 100644 --- a/java/res/values-tr/strings.xml +++ b/java/res/values-tr/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Resim paylaşılıyor}other{# resim paylaşılıyor}}" "{count,plural, =1{Video paylaşılıyor}other{# video paylaşılıyor}}" "{count,plural, =1{# dosya paylaşılıyor}other{# dosya paylaşılıyor}}" + + "{count,plural, =1{Metin ekli resim paylaşılıyor}other{Metin ekli # resim paylaşılıyor}}" "{count,plural, =1{Bağlantı ekli resim paylaşılıyor}other{Bağlantı ekli # resim paylaşılıyor}}" "{count,plural, =1{Metin ekli video paylaşılıyor}other{Metin ekli # video paylaşılıyor}}" diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml index b5f91741..a694ee77 100644 --- a/java/res/values-uk/strings.xml +++ b/java/res/values-uk/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Надсилається зображення}one{Надсилається # зображення}few{Надсилаються # зображення}many{Надсилаються # зображень}other{Надсилається # зображення}}" "{count,plural, =1{Надсилається відео}one{Надсилається # відео}few{Надсилаються # відео}many{Надсилаються # відео}other{Надсилається # відео}}" "{count,plural, =1{Надсилається # файл}one{Надсилається # файл}few{Надсилаються # файли}many{Надсилаються # файлів}other{Надсилається # файлу}}" + + "{count,plural, =1{Надсилання зображення з текстом}one{Надсилання # зображення з текстом}few{Надсилання # зображень із текстом}many{Надсилання # зображень із текстом}other{Надсилання # зображення з текстом}}" "{count,plural, =1{Надсилання зображення з посиланням}one{Надсилання # зображення з посиланням}few{Надсилання # зображень із посиланням}many{Надсилання # зображень із посиланням}other{Надсилання # зображення з посиланням}}" "{count,plural, =1{Надсилання відео з текстом}one{Надсилання # відео з текстом}few{Надсилання # відео з текстом}many{Надсилання # відео з текстом}other{Надсилання # відео з текстом}}" diff --git a/java/res/values-ur/strings.xml b/java/res/values-ur/strings.xml index f6eb8612..dfdbc4c3 100644 --- a/java/res/values-ur/strings.xml +++ b/java/res/values-ur/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{تصویر کا اشتراک کیا جا رہا ہے}other{# تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{ویڈیو کا اشتراک کیا جا رہا ہے}other{# ویڈیوز کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{# فائل کا اشتراک کیا جا رہا ہے}other{# فائلز کا اشتراک کیا جا رہا ہے}}" + + "{count,plural, =1{ٹیکسٹ کے ساتھ تصویر کا اشتراک کیا جا رہا ہے}other{ٹیکسٹ کے ساتھ # تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{لنک کے ساتھ تصویر کا اشتراک کیا جا رہا ہے}other{لنک کے ساتھ # تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{ٹیکسٹ کے ساتھ ویڈیو کا اشتراک کیا جا رہا ہے}other{ٹیکسٹ کے ساتھ # ویڈیوز کا اشتراک کیا جا رہا ہے}}" diff --git a/java/res/values-uz/strings.xml b/java/res/values-uz/strings.xml index 96439147..21aea564 100644 --- a/java/res/values-uz/strings.xml +++ b/java/res/values-uz/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Rasm ulashilmoqda}other{# ta rasm ulashilmoqda}}" "{count,plural, =1{Video ulashilmoqda}other{# ta video ulashilmoqda}}" "{count,plural, =1{# ta fayl ulashilmoqda}other{# ta fayl ulashilmoqda}}" + + "{count,plural, =1{Matnli havolani yuborish}other{# ta matnli havolani yuborish}}" "{count,plural, =1{Havolali rasmni yuborish}other{# ta havolali rasmni yuborish}}" "{count,plural, =1{Matnli videoni yuborish}other{# ta matnli videoni yuborish}}" diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml index 0645d052..170be22f 100644 --- a/java/res/values-vi/strings.xml +++ b/java/res/values-vi/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Chia sẻ hình ảnh}other{Chia sẻ # hình ảnh}}" "{count,plural, =1{Đang chia sẻ video}other{Đang chia sẻ # video}}" "{count,plural, =1{Đang chia sẻ # tệp}other{Đang chia sẻ # tệp}}" + + "{count,plural, =1{Đang chia sẻ hình ảnh có văn bản}other{Đang chia sẻ # hình ảnh có văn bản}}" "{count,plural, =1{Đang chia sẻ hình ảnh có đường liên kết}other{Đang chia sẻ # hình ảnh có đường liên kết}}" "{count,plural, =1{Đang chia sẻ video có văn bản}other{Đang chia sẻ # video có văn bản}}" diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml index 9fea3097..aed7679f 100644 --- a/java/res/values-zh-rCN/strings.xml +++ b/java/res/values-zh-rCN/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{分享图片}other{分享 # 张图片}}" "{count,plural, =1{正在分享视频}other{正在分享 # 个视频}}" "{count,plural, =1{正在分享 # 个文件}other{正在分享 # 个文件}}" + + "{count,plural, =1{正在分享带有文本的图片}other{正在分享带有文本的 # 个图片}}" "{count,plural, =1{正在分享带有链接的图片}other{正在分享带有链接的 # 个图片}}" "{count,plural, =1{正在分享带有文本的视频}other{正在分享带有文本的 # 个视频}}" diff --git a/java/res/values-zh-rHK/strings.xml b/java/res/values-zh-rHK/strings.xml index 65f73d0a..c4360bc0 100644 --- a/java/res/values-zh-rHK/strings.xml +++ b/java/res/values-zh-rHK/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{分享圖片}other{分享 # 張圖片}}" "{count,plural, =1{正在分享影片}other{正在分享 # 部影片}}" "{count,plural, =1{正在分享 # 個檔案}other{正在分享 # 個檔案}}" + + "{count,plural, =1{正在分享圖片 (含有文字)}other{正在分享 # 張圖片 (含有文字)}}" "{count,plural, =1{正在分享圖片 (含有連結)}other{正在分享 # 張圖片 (含有連結)}}" "{count,plural, =1{正在分享影片 (含有文字)}other{正在分享 # 部影片 (含有文字)}}" diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml index bade791a..5fdeff86 100644 --- a/java/res/values-zh-rTW/strings.xml +++ b/java/res/values-zh-rTW/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{分享圖片}other{分享 # 張圖片}}" "{count,plural, =1{正在分享影片}other{正在分享 # 部影片}}" "{count,plural, =1{正在分享 # 個檔案}other{正在分享 # 個檔案}}" + + "{count,plural, =1{分享含有文字的圖片}other{分享 # 張含有文字的圖片}}" "{count,plural, =1{分享含有連結的圖片}other{分享 # 張含有連結的圖片}}" "{count,plural, =1{分享含有文字的影片}other{分享 # 部含有文字的影片}}" diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml index 38e62f88..3697df56 100644 --- a/java/res/values-zu/strings.xml +++ b/java/res/values-zu/strings.xml @@ -60,6 +60,8 @@ "{count,plural, =1{Yabelana ngomfanekiso}one{Yabelana ngemifanekiso engu-#}other{Yabelana ngemifanekiso engu-#}}" "{count,plural, =1{Yabelana ngevidiyo}one{Yabelana ngamavidiyo angu-#}other{Yabelana ngamavidiyo angu-#}}" "{count,plural, =1{Yabelana ngefayela eli-#}one{Yabelana ngamafayela angu-#}other{Yabelana ngamafayela angu-#}}" + + "{count,plural, =1{Yabelana ngomfanekiso ngombhalo}one{Yabelana ngemifanekiso engu-# ngombhalo}other{Yabelana ngemifanekiso engu-# ngombhalo}}" "{count,plural, =1{Yabelana ngomfanekiso ngelinki}one{Yabelana ngemifanekiso engu-# ngelinki}other{Yabelana ngemifanekiso engu-# ngelinki}}" "{count,plural, =1{Yabelana ngevidiyo ngombhalo}one{Yabelana ngamavidiyo angu-# ngombhalo}other{Yabelana ngamavidiyo angu-# ngombhalo}}" -- cgit v1.2.3-59-g8ed1b From 57bad9d1cf27d0272f18285f4b31ca0e31a6411b Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Mon, 19 Aug 2024 19:36:08 -0700 Subject: Use a viewmodel property instead of reaching to the repository A small adjustment to ag/28621884 to use the ShareouselViewModel.hasSelectedItems property instead of reaching to the selection repository. Fix: 349468879 Test: atest IntentResolver-tests-unit Test: manual functionality testing Flag: com.android.intentresolver.unselect_final_item Change-Id: Ifded6890acd116a6dacb76ca2adccafccc1fb5f3 --- java/src/com/android/intentresolver/ChooserHelper.kt | 11 ++++------- .../payloadtoggle/ui/viewmodel/ShareouselViewModel.kt | 4 +++- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserHelper.kt b/java/src/com/android/intentresolver/ChooserHelper.kt index 7d382bfc..c26dd77c 100644 --- a/java/src/com/android/intentresolver/ChooserHelper.kt +++ b/java/src/com/android/intentresolver/ChooserHelper.kt @@ -32,7 +32,6 @@ import com.android.intentresolver.annotation.JavaInterop import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ActivityResultRepository import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PendingSelectionCallbackRepository -import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository import com.android.intentresolver.data.model.ChooserRequest import com.android.intentresolver.platform.GlobalSettings import com.android.intentresolver.ui.viewmodel.ChooserViewModel @@ -92,7 +91,6 @@ constructor( hostActivity: Activity, private val activityResultRepo: ActivityResultRepository, private val pendingSelectionCallbackRepo: PendingSelectionCallbackRepository, - private val selectionsRepo: PreviewSelectionsRepository, private val globalSettings: GlobalSettings, ) : DefaultLifecycleObserver { // This is guaranteed by Hilt, since only a ComponentActivity is injectable. @@ -168,11 +166,10 @@ constructor( viewModel.previewDataProvider.previewType == CONTENT_PREVIEW_PAYLOAD_SELECTION ) { - selectionsRepo.selections - .map { it.isNotEmpty() } - .distinctUntilChanged() - .stateIn(scope = this) - .also { flow -> launch { flow.collect { onHasSelections.accept(it) } } } + viewModel.shareouselViewModel.hasSelectedItems.stateIn(scope = this).also { + flow -> + launch { flow.collect { onHasSelections.accept(it) } } + } } else { MutableStateFlow(true).asStateFlow() } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt index 9762794e..ebcd58d1 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt @@ -41,6 +41,7 @@ import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -135,7 +136,8 @@ object ShareouselViewModelModule { } } }, - hasSelectedItems = selectionInteractor.selections.map { it.isNotEmpty() }, + hasSelectedItems = + selectionInteractor.selections.map { it.isNotEmpty() }.distinctUntilChanged(), preview = { key, previewHeight, index, previewScope -> keySet.value?.maybeLoad(index) val previewInteractor = interactor.preview(key) -- cgit v1.2.3-59-g8ed1b From 96a5e64e1974814bb56885eb62b89c65cb347194 Mon Sep 17 00:00:00 2001 From: Govinda Wasserman Date: Mon, 12 Aug 2024 15:12:52 -0400 Subject: Scroll partially offscreen items to be fully onscreen when toggled Test: manual test using ShareTest BUG: 351883537 FIX: 351883537 Flag: com.android.intentresolver.shareousel_scroll_offscreen_selections Change-Id: Id9c7e31169bce11b722868338f87228c9450c9aa --- aconfig/FeatureFlags.aconfig | 7 ++++ .../ui/composable/ShareouselComposable.kt | 40 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) (limited to 'java') diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig index 6f7e75d5..8396bc24 100644 --- a/aconfig/FeatureFlags.aconfig +++ b/aconfig/FeatureFlags.aconfig @@ -116,3 +116,10 @@ flag { description: "Allow toggling of final Shareousel item" bug: "349468879" } + +flag { + name: "shareousel_scroll_offscreen_selections" + namespace: "intentresolver" + description: "Whether to scroll items onscreen when they are partially offscreen and selected/unselected." + bug: "351883537" +} diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index f8cf243d..4b87d227 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.intentresolver.Flags.shareouselScrollOffscreenSelections import com.android.intentresolver.Flags.unselectFinalItem import com.android.intentresolver.R import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate @@ -189,6 +190,45 @@ private fun PreviewCarousel( firstSelectedIndex = min(index, firstSelectedIndex ?: Int.MAX_VALUE) } + if (shareouselScrollOffscreenSelections()) { + LaunchedEffect(index, model.uri) { + var current: Boolean? = null + previewModel.isSelected.collect { selected -> + when { + // First update will always be the current state, so we just want to + // record the state and do nothing else. + current == null -> current = selected + + // We only want to act when the state changes + current != selected -> { + current = selected + with(carouselState.layoutInfo) { + visibleItemsInfo + .firstOrNull { it.index == index } + ?.let { item -> + when { + // Item is partially past start of viewport + item.offset < viewportStartOffset -> + -viewportStartOffset + // Item is partially past end of viewport + (item.offset + item.size) > viewportEndOffset -> + item.size - viewportEndOffset + // Item is fully within viewport + else -> null + }?.let { scrollOffset -> + carouselState.animateScrollToItem( + index = index, + scrollOffset = scrollOffset, + ) + } + } + } + } + } + } + } + } + ShareouselCard( viewModel.preview( model, -- cgit v1.2.3-59-g8ed1b From 7b7c943272725f89495ade08b299ec4e431ded36 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Thu, 22 Aug 2024 19:32:32 -0700 Subject: Fix ChooserRequest.sharedText value parsing readChooserRequest was reading sharedText value from the chooser intent's extra instead of the target intent's extra. Additional refactorings: * sharedTextTitle is added to the ChooserReqeust (read from target intent's EXTRA_TITLE extra); * ChooserContentPreviewUi is modified to use ChooserReqeust instead of extracting values form the target intent. Fix: 361654855 Test: atest IntentResolver-tests-unit Test: manual testing various previw variations with the ShareTest app Flag: EXEMPT bugfix Change-Id: Ic9e0e200f4b9a84df4830e99dd42cb1bc7258caf --- .../android/intentresolver/ChooserActivity.java | 2 +- .../contentpreview/ChooserContentPreviewUi.java | 28 +++++++++++----------- .../intentresolver/data/model/ChooserRequest.kt | 2 ++ .../ui/viewmodel/ChooserRequestReader.kt | 13 ++++++---- .../contentpreview/ChooserContentPreviewUiTest.kt | 24 ++++++++++++------- .../ui/viewmodel/ChooserRequestTest.kt | 22 +++++++++++++++++ 6 files changed, 63 insertions(+), 28 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 8871ce3f..3db821c1 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -648,7 +648,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserContentPreviewUi = new ChooserContentPreviewUi( getCoroutineScope(getLifecycle()), mViewModel.getPreviewDataProvider(), - mRequest.getTargetIntent(), + mRequest, mViewModel.getImageLoader(), actionFactory, createModifyShareActionFactory(), diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index 4b955c49..1128ec5d 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -22,7 +22,6 @@ import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTE import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_TEXT; import android.content.ClipData; -import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.text.TextUtils; @@ -34,6 +33,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.intentresolver.ContentTypeHint; +import com.android.intentresolver.data.model.ChooserRequest; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; @@ -102,7 +102,7 @@ public final class ChooserContentPreviewUi { public ChooserContentPreviewUi( CoroutineScope scope, PreviewDataProvider previewData, - Intent targetIntent, + ChooserRequest chooserRequest, ImageLoader imageLoader, ActionFactory actionFactory, Supplier modifyShareActionFactory, @@ -117,7 +117,7 @@ public final class ChooserContentPreviewUi { mModifyShareActionFactory = modifyShareActionFactory; mContentPreviewUi = createContentPreview( previewData, - targetIntent, + chooserRequest, DefaultMimeTypeClassifier.INSTANCE, imageLoader, actionFactory, @@ -133,7 +133,7 @@ public final class ChooserContentPreviewUi { private ContentPreviewUi createContentPreview( PreviewDataProvider previewData, - Intent targetIntent, + ChooserRequest chooserRequest, MimeTypeClassifier typeClassifier, ImageLoader imageLoader, ActionFactory actionFactory, @@ -146,7 +146,9 @@ public final class ChooserContentPreviewUi { if (previewType == CONTENT_PREVIEW_TEXT) { return createTextPreview( mScope, - targetIntent, + chooserRequest.getTargetIntent().getClipData(), + chooserRequest.getSharedText(), + chooserRequest.getSharedTextTitle(), actionFactory, imageLoader, headlineGenerator, @@ -174,15 +176,14 @@ public final class ChooserContentPreviewUi { boolean isSingleImageShare = previewData.getUriCount() == 1 && typeClassifier.isImageType(previewData.getFirstFileInfo().getMimeType()); - CharSequence text = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); - if (!TextUtils.isEmpty(text)) { + if (!TextUtils.isEmpty(chooserRequest.getSharedText())) { FilesPlusTextContentPreviewUi previewUi = new FilesPlusTextContentPreviewUi( mScope, isSingleImageShare, previewData.getUriCount(), - targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT), - targetIntent.getType(), + chooserRequest.getSharedText(), + chooserRequest.getTargetType(), actionFactory, imageLoader, typeClassifier, @@ -201,7 +202,7 @@ public final class ChooserContentPreviewUi { return new UnifiedContentPreviewUi( mScope, isSingleImageShare, - targetIntent.getType(), + chooserRequest.getTargetType(), actionFactory, imageLoader, typeClassifier, @@ -243,16 +244,15 @@ public final class ChooserContentPreviewUi { private static TextContentPreviewUi createTextPreview( CoroutineScope scope, - Intent targetIntent, + ClipData previewData, + @Nullable CharSequence sharingText, + @Nullable CharSequence previewTitle, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, HeadlineGenerator headlineGenerator, ContentTypeHint contentTypeHint, @Nullable CharSequence metadata ) { - CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); - CharSequence previewTitle = targetIntent.getCharSequenceExtra(Intent.EXTRA_TITLE); - ClipData previewData = targetIntent.getClipData(); Uri previewThumbnail = null; if (previewData != null) { if (previewData.getItemCount() > 0) { diff --git a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt index 045a17f6..c4aa2b98 100644 --- a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt +++ b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt @@ -156,6 +156,8 @@ data class ChooserRequest( * TODO: Constrain length? */ val sharedText: CharSequence? = null, + /** Contains title to the text content to share supplied by the source app. */ + val sharedTextTitle: CharSequence? = null, /** * Supplied to diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt index f0f72489..4a194db9 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt @@ -18,7 +18,10 @@ package com.android.intentresolver.ui.viewmodel import android.content.ComponentName import android.content.Intent import android.content.Intent.EXTRA_ALTERNATE_INTENTS +import android.content.Intent.EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI +import android.content.Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT import android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS +import android.content.Intent.EXTRA_CHOOSER_FOCUSED_ITEM_POSITION 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 @@ -114,7 +117,8 @@ fun readChooserRequest( val retainInOnStop = optional(value(ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP)) ?: false - val sharedText = optional(value(EXTRA_TEXT)) + val sharedTextTitle = targetIntent.getCharSequenceExtra(EXTRA_TITLE) + val sharedText = targetIntent.getCharSequenceExtra(EXTRA_TEXT) val chooserActions = readChooserActions() ?: emptyList() @@ -123,15 +127,15 @@ fun readChooserRequest( val additionalContentUri: Uri? val focusedItemPos: Int if (isSendAction && flags.chooserPayloadToggling()) { - additionalContentUri = optional(value(Intent.EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI)) - focusedItemPos = optional(value(Intent.EXTRA_CHOOSER_FOCUSED_ITEM_POSITION)) ?: 0 + additionalContentUri = optional(value(EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI)) + focusedItemPos = optional(value(EXTRA_CHOOSER_FOCUSED_ITEM_POSITION)) ?: 0 } else { additionalContentUri = null focusedItemPos = 0 } val contentTypeHint = - when (optional(value(Intent.EXTRA_CHOOSER_CONTENT_TYPE_HINT))) { + when (optional(value(EXTRA_CHOOSER_CONTENT_TYPE_HINT))) { Intent.CHOOSER_CONTENT_TYPE_ALBUM -> ContentTypeHint.ALBUM else -> ContentTypeHint.NONE } @@ -161,6 +165,7 @@ fun readChooserRequest( chosenComponentSender = chosenComponentSender, refinementIntentSender = refinementIntentSender, sharedText = sharedText, + sharedTextTitle = sharedTextTitle, shareTargetFilter = targetIntent.toShareTargetFilter(), additionalContentUri = additionalContentUri, focusedItemPosition = focusedItemPos, diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index 27d98ece..a80cc02b 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -23,6 +23,7 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider import com.android.intentresolver.ContentTypeHint import com.android.intentresolver.FakeImageLoader import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory +import com.android.intentresolver.data.model.ChooserRequest import com.android.intentresolver.widget.ActionRow import com.android.intentresolver.widget.ImagePreviewView import com.google.common.truth.Truth.assertThat @@ -61,13 +62,18 @@ class ChooserContentPreviewUiTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() private fun createContentPreviewUi( - targetIntent: Intent, + action: String, + sharedText: CharSequence? = null, isPayloadTogglingEnabled: Boolean = false ) = ChooserContentPreviewUi( testScope, previewData, - targetIntent, + ChooserRequest( + targetIntent = Intent(action), + sharedText = sharedText, + launchedFromPackage = "org.pkg", + ), imageLoader, actionFactory, { null }, @@ -81,7 +87,7 @@ class ChooserContentPreviewUiTest { @Test fun test_textPreviewType_useTextPreviewUi() { whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_TEXT) - val testSubject = createContentPreviewUi(targetIntent = Intent(Intent.ACTION_VIEW)) + val testSubject = createContentPreviewUi(action = Intent.ACTION_VIEW) assertThat(testSubject.preferredContentPreview) .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) @@ -92,7 +98,7 @@ class ChooserContentPreviewUiTest { @Test fun test_filePreviewType_useFilePreviewUi() { whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_FILE) - val testSubject = createContentPreviewUi(targetIntent = Intent(Intent.ACTION_SEND)) + val testSubject = createContentPreviewUi(action = Intent.ACTION_SEND) assertThat(testSubject.preferredContentPreview) .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.mContentPreviewUi).isInstanceOf(FileContentPreviewUi::class.java) @@ -109,8 +115,8 @@ class ChooserContentPreviewUiTest { whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) val testSubject = createContentPreviewUi( - targetIntent = - Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Shared text") } + action = Intent.ACTION_SEND, + sharedText = "Shared text", ) assertThat(testSubject.mContentPreviewUi) .isInstanceOf(FilesPlusTextContentPreviewUi::class.java) @@ -126,7 +132,7 @@ class ChooserContentPreviewUiTest { whenever(previewData.firstFileInfo) .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) - val testSubject = createContentPreviewUi(targetIntent = Intent(Intent.ACTION_SEND)) + val testSubject = createContentPreviewUi(action = Intent.ACTION_SEND) assertThat(testSubject.preferredContentPreview) .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.mContentPreviewUi).isInstanceOf(UnifiedContentPreviewUi::class.java) @@ -146,8 +152,8 @@ class ChooserContentPreviewUiTest { whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) val testSubject = createContentPreviewUi( - targetIntent = Intent(Intent.ACTION_SEND), - isPayloadTogglingEnabled = true + action = Intent.ACTION_SEND, + isPayloadTogglingEnabled = true, ) assertThat(testSubject.mContentPreviewUi) .isInstanceOf(ShareouselContentPreviewUi::class.java) 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 2d5a44ed..01904c7f 100644 --- a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt +++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt @@ -25,6 +25,8 @@ import android.content.Intent.EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI import android.content.Intent.EXTRA_CHOOSER_FOCUSED_ITEM_POSITION import android.content.Intent.EXTRA_INTENT 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 @@ -271,4 +273,24 @@ class ChooserRequestTest { assertThat(result.value.metadataText).isEqualTo(metadataText) } + + @Test + fun textSharedTextAndTitle() { + val text: CharSequence = "Shared text" + val title: CharSequence = "Title" + val targetIntent = + Intent().apply { + putExtra(EXTRA_TITLE, title) + putExtra(EXTRA_TEXT, text) + } + val model = createActivityModel(targetIntent) + + val result = readChooserRequest(model, fakeChooserServiceFlags) + + assertThat(result).isInstanceOf(Valid::class.java) + (result as Valid).value.let { request -> + assertThat(request.sharedText).isEqualTo(text) + assertThat(request.sharedTextTitle).isEqualTo(title) + } + } } -- cgit v1.2.3-59-g8ed1b From d751405b76081d1abea5581c0a56b9252fb927cc Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 26 Aug 2024 18:41:48 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: If56d90961f430145e5b9b69f33fcb4866cfaafcb --- java/res/values-af/strings.xml | 3 +-- java/res/values-am/strings.xml | 3 +-- java/res/values-ar/strings.xml | 3 +-- java/res/values-as/strings.xml | 3 +-- java/res/values-az/strings.xml | 3 +-- java/res/values-b+sr+Latn/strings.xml | 3 +-- java/res/values-be/strings.xml | 3 +-- java/res/values-bg/strings.xml | 3 +-- java/res/values-bn/strings.xml | 3 +-- java/res/values-bs/strings.xml | 3 +-- java/res/values-ca/strings.xml | 3 +-- java/res/values-cs/strings.xml | 3 +-- java/res/values-da/strings.xml | 3 +-- java/res/values-de/strings.xml | 3 +-- java/res/values-el/strings.xml | 3 +-- java/res/values-en-rAU/strings.xml | 3 +-- java/res/values-en-rGB/strings.xml | 3 +-- java/res/values-en-rIN/strings.xml | 3 +-- java/res/values-es-rUS/strings.xml | 3 +-- java/res/values-es/strings.xml | 3 +-- java/res/values-et/strings.xml | 3 +-- java/res/values-eu/strings.xml | 3 +-- java/res/values-fa/strings.xml | 3 +-- java/res/values-fi/strings.xml | 3 +-- java/res/values-fr-rCA/strings.xml | 3 +-- java/res/values-fr/strings.xml | 3 +-- java/res/values-gl/strings.xml | 3 +-- java/res/values-gu/strings.xml | 3 +-- java/res/values-hi/strings.xml | 3 +-- java/res/values-hr/strings.xml | 3 +-- java/res/values-hu/strings.xml | 3 +-- java/res/values-hy/strings.xml | 3 +-- java/res/values-in/strings.xml | 3 +-- java/res/values-is/strings.xml | 3 +-- java/res/values-it/strings.xml | 3 +-- java/res/values-iw/strings.xml | 3 +-- java/res/values-ja/strings.xml | 3 +-- java/res/values-ka/strings.xml | 3 +-- java/res/values-kk/strings.xml | 3 +-- java/res/values-km/strings.xml | 3 +-- java/res/values-kn/strings.xml | 3 +-- java/res/values-ko/strings.xml | 3 +-- java/res/values-ky/strings.xml | 3 +-- java/res/values-lo/strings.xml | 3 +-- java/res/values-lt/strings.xml | 3 +-- java/res/values-lv/strings.xml | 3 +-- java/res/values-mk/strings.xml | 3 +-- java/res/values-ml/strings.xml | 3 +-- java/res/values-mn/strings.xml | 3 +-- java/res/values-mr/strings.xml | 3 +-- java/res/values-ms/strings.xml | 3 +-- java/res/values-my/strings.xml | 3 +-- java/res/values-nb/strings.xml | 3 +-- java/res/values-ne/strings.xml | 3 +-- java/res/values-nl/strings.xml | 3 +-- java/res/values-or/strings.xml | 5 ++--- java/res/values-pa/strings.xml | 3 +-- java/res/values-pl/strings.xml | 3 +-- java/res/values-pt-rBR/strings.xml | 3 +-- java/res/values-pt-rPT/strings.xml | 3 +-- java/res/values-pt/strings.xml | 3 +-- java/res/values-ro/strings.xml | 3 +-- java/res/values-ru/strings.xml | 3 +-- java/res/values-si/strings.xml | 3 +-- java/res/values-sk/strings.xml | 3 +-- java/res/values-sl/strings.xml | 3 +-- java/res/values-sq/strings.xml | 3 +-- java/res/values-sr/strings.xml | 3 +-- java/res/values-sv/strings.xml | 3 +-- java/res/values-sw/strings.xml | 3 +-- java/res/values-ta/strings.xml | 3 +-- java/res/values-te/strings.xml | 3 +-- java/res/values-th/strings.xml | 3 +-- java/res/values-tl/strings.xml | 3 +-- java/res/values-tr/strings.xml | 3 +-- java/res/values-uk/strings.xml | 3 +-- java/res/values-ur/strings.xml | 3 +-- java/res/values-uz/strings.xml | 3 +-- java/res/values-vi/strings.xml | 3 +-- java/res/values-zh-rCN/strings.xml | 3 +-- java/res/values-zh-rHK/strings.xml | 3 +-- java/res/values-zh-rTW/strings.xml | 3 +-- java/res/values-zu/strings.xml | 3 +-- 83 files changed, 84 insertions(+), 167 deletions(-) (limited to 'java') diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml index 43bc9604..55d84dfa 100644 --- a/java/res/values-af/strings.xml +++ b/java/res/values-af/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deel tans prent}other{Deel tans # prente}}" "{count,plural, =1{Deel tans video}other{Deel tans # video’s}}" "{count,plural, =1{Deel tans # lêer}other{Deel tans # lêers}}" - - + "Kies items om te deel" "{count,plural, =1{Deel tans prent met teks}other{Deel tans # prente met teks}}" "{count,plural, =1{Deel tans prent met skakel}other{Deel tans # prente met skakel}}" "{count,plural, =1{Deel tans video met teks}other{Deel tans # video’s met teks}}" diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml index 7d359304..a7b5922b 100644 --- a/java/res/values-am/strings.xml +++ b/java/res/values-am/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ምስልን በማጋራት ላይ}one{# ምስልን በማጋራት ላይ}other{# ምስሎችን በማጋራት ላይ}}" "{count,plural, =1{ቪድዮ በማጋራት ላይ}one{# ቪድዮ በማጋራት ላይ}other{# ቪድዮዎችን በማጋራት ላይ}}" "{count,plural, =1{# ፋይልን በማጋራት ላይ}one{# ፋይልን በማጋራት ላይ}other{# ፋይሎችን በማጋራት ላይ}}" - - + "ለማጋራት ንጥሎችን ምረጥ" "{count,plural, =1{ምስልን ከጽሑፍ ጋር በማጋራት ላይ}one{# ምስልን ከጽሑፍ ጋር በማጋራት ላይ}other{# ምስሎችን ከጽሑፍ ጋር በማጋራት ላይ}}" "{count,plural, =1{ምስልን ከአገናኝ ጋር በማጋራት ላይ}one{# ምስልን ከአገናኝ ጋር በማጋራት ላይ}other{# ምስሎችን ከአገናኝ ጋር በማጋራት ላይ}}" "{count,plural, =1{ቪድዮ ከጽሑፍ ጋር በማጋራት ላይ}one{# ቪድዮ ከጽሑፍ ጋር በማጋራት ላይ}other{# ቪድዮዎችን ከጽሑፍ ጋር በማጋራት ላይ}}" diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml index 25ed0ba5..88faf86c 100644 --- a/java/res/values-ar/strings.xml +++ b/java/res/values-ar/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{جارٍ مشاركة صورة واحدة}zero{جارٍ مشاركة # صورة}two{جارٍ مشاركة صورتَين}few{جارٍ مشاركة # صور}many{جارٍ مشاركة # صورة}other{جارٍ مشاركة # صورة}}" "{count,plural, =1{جارٍ مشاركة فيديو واحد}zero{جارٍ مشاركة # فيديو}two{جارٍ مشاركة فيديوهَين}few{جارٍ مشاركة # فيديوهات}many{جارٍ مشاركة # فيديو}other{جارٍ مشاركة # فيديو}}" "{count,plural, =1{مشاركة ملف واحد}zero{مشاركة # ملف}two{مشاركة ملفَّين}few{مشاركة # ملفات}many{مشاركة # ملفًّا}other{مشاركة # ملف}}" - - + "اختيار العناصر المراد مشاركتها" "{count,plural, =1{مشاركة صورة واحدة ونص}zero{مشاركة # صورة ونص}two{مشاركة صورتَين ونص}few{مشاركة # صور ونص}many{مشاركة # صورة ونص}other{مشاركة # صورة ونص}}" "{count,plural, =1{مشاركة صورة واحدة ورابط}zero{مشاركة # صورة ورابط}two{مشاركة # صورتَين ورابط}few{مشاركة # صور ورابط}many{مشاركة # صورة ورابط}other{مشاركة # صورة ورابط}}" "{count,plural, =1{مشاركة فيديو واحد ونص}zero{مشاركة # فيديو ونص}two{مشاركة فيديوهَين ونص}few{مشاركة # فيديوهات ونص}many{مشاركة # فيديو ونص}other{مشاركة # فيديو ونص}}" diff --git a/java/res/values-as/strings.xml b/java/res/values-as/strings.xml index 978af0c0..1983e4fe 100644 --- a/java/res/values-as/strings.xml +++ b/java/res/values-as/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{# খন প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{# খন প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}one{# টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}other{# টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}one{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}other{# টা ফাইল শ্বেয়াৰ কৰি থকা হৈছে}}" - - + "শ্বেয়াৰ কৰাৰ বাবে বস্তু বাছনি কৰক" "{count,plural, =1{পাঠৰ সৈতে প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{পাঠৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{পাঠৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{লিংকৰ সৈতে প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}one{লিংকৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}other{লিংকৰ সৈতে # টা প্ৰতিচ্ছবি শ্বেয়াৰ কৰি থকা হৈছে}}" "{count,plural, =1{পাঠৰ সৈতে ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}one{পাঠৰ সৈতে # টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}other{পাঠৰ সৈতে # টা ভিডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে}}" diff --git a/java/res/values-az/strings.xml b/java/res/values-az/strings.xml index 1ca43ae1..c5674b86 100644 --- a/java/res/values-az/strings.xml +++ b/java/res/values-az/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Şəkil paylaşılır}other{# şəkil paylaşılır}}" "{count,plural, =1{Video paylaşılır}other{# video paylaşılır}}" "{count,plural, =1{# fayl paylaşılır}other{# fayl paylaşılır}}" - - + "Paylaşmaq üçün elementlər seçin" "{count,plural, =1{Mətn olan şəkil paylaşılır}other{Mətn olan # şəkil paylaşılır}}" "{count,plural, =1{Link olan şəkil paylaşılır}other{Link olan # şəkil paylaşılır}}" "{count,plural, =1{Mətn olan video paylaşılır}other{Mətn olan # video paylaşılır}}" diff --git a/java/res/values-b+sr+Latn/strings.xml b/java/res/values-b+sr+Latn/strings.xml index 5bb53b2a..6d9dbd87 100644 --- a/java/res/values-b+sr+Latn/strings.xml +++ b/java/res/values-b+sr+Latn/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deljenje slike}one{Deljenje # slike}few{Deljenje # slike}other{Deljenje # slika}}" "{count,plural, =1{Deli se video}one{Deli se # video}few{Dele se # video snimka}other{Deli se # videa}}" "{count,plural, =1{Deli se # fajl}one{Deli se # fajl}few{Dele se # fajla}other{Deli se # fajlova}}" - - + "Izaberite stavke za deljenje" "{count,plural, =1{Deli se slika sa tekstom}one{Deli se # slika sa tekstom}few{Dele se # slike sa tekstom}other{Deli se # slika sa tekstom}}" "{count,plural, =1{Deli se slika sa linkom}one{Deli se # slika sa linkom}few{Dele se # slike sa linkom}other{Deli se # slika sa linkom}}" "{count,plural, =1{Deli se video sa tekstom}one{Deli se # video sa tekstom}few{Dele se # video snimka sa tekstom}other{Deli se # videa sa tekstom}}" diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml index 5f943f1e..2724855b 100644 --- a/java/res/values-be/strings.xml +++ b/java/res/values-be/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Абагульванне відарыса}one{Абагульванне # відарыса}few{Абагульванне # відарысаў}many{Абагульванне # відарысаў}other{Абагульванне # відарыса}}" "{count,plural, =1{Абагульванне відэа}one{Абагульванне # відэа}few{Абагульванне # відэа}many{Абагульванне # відэа}other{Абагульванне # відэа}}" "{count,plural, =1{Абагульваецца # файл}one{Абагульваецца # файл}few{Абагульваюцца # файлы}many{Абагульваюцца # файлаў}other{Абагульваюцца # файла}}" - - + "Выберыце элементы для абагульвання" "{count,plural, =1{Абагульванне відарыса з тэкстам}one{Абагульванне # відарыса з тэкстам}few{Абагульванне # відарысаў з тэкстам}many{Абагульванне # відарысаў з тэкстам}other{Абагульванне # відарыса з тэкстам}}" "{count,plural, =1{Абагульванне відарыса са спасылкай}one{Абагульванне # відарыса са спасылкай}few{Абагульванне # відарысаў са спасылкай}many{Абагульванне # відарысаў са спасылкай}other{Абагульванне # відарыса са спасылкай}}" "{count,plural, =1{Абагульванне відэа з тэкстам}one{Абагульванне # відэа з тэкстам}few{Абагульванне # відэа з тэкстам}many{Абагульванне # відэа з тэкстам}other{Абагульванне # відэа з тэкстам}}" diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml index 2c371364..450712b1 100644 --- a/java/res/values-bg/strings.xml +++ b/java/res/values-bg/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Изображението се споделя}other{# изображения се споделят}}" "{count,plural, =1{Видеоклипът се споделя}other{# видеоклипа се споделят}}" "{count,plural, =1{# файл се споделя}other{# файла се споделят}}" - - + "Изберете елементи за споделяне" "{count,plural, =1{Споделяне на изображението чрез SMS съобщение}other{Споделяне на # изображения чрез SMS съобщение}}" "{count,plural, =1{Споделяне на изображението чрез връзка}other{Споделяне на # изображения чрез връзка}}" "{count,plural, =1{Споделяне на видеоклипа чрез SMS съобщение}other{Споделяне на # видеоклипа чрез SMS съобщение}}" diff --git a/java/res/values-bn/strings.xml b/java/res/values-bn/strings.xml index e3290c22..2d33eb29 100644 --- a/java/res/values-bn/strings.xml +++ b/java/res/values-bn/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ছবি শেয়ার করা হচ্ছে}one{#টি ছবি শেয়ার করা হচ্ছে}other{#টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{ভিডিও শেয়ার করা হচ্ছে}one{#টি ভিডিও শেয়ার করা হচ্ছে}other{#টি ভিডিও শেয়ার করা হচ্ছে}}" "{count,plural, =1{#টি ফাইল শেয়ার করা হচ্ছে}one{#টি ফাইল শেয়ার করা হচ্ছে}other{#টি ফাইল শেয়ার করা হচ্ছে}}" - - + "শেয়ার করার জন্য আইটেম বেছে নিন" "{count,plural, =1{টেক্সট সহ ছবি শেয়ার করা হচ্ছে}one{টেক্সট সহ #টি ছবি শেয়ার করা হচ্ছে}other{টেক্সট সহ #টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{লিঙ্ক সহ ছবি শেয়ার করা হচ্ছে}one{লিঙ্ক সহ #টি ছবি শেয়ার করা হচ্ছে}other{লিঙ্ক সহ #টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{টেক্সট সহ ভিডিও শেয়ার করা হচ্ছে}one{টেক্সট সহ #টি ভিডিও শেয়ার করা হচ্ছে}other{টেক্সট সহ #টি ভিডিও শেয়ার করা হচ্ছে}}" diff --git a/java/res/values-bs/strings.xml b/java/res/values-bs/strings.xml index c844051b..10335fab 100644 --- a/java/res/values-bs/strings.xml +++ b/java/res/values-bs/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeljenje videozapisa}one{Dijeljenje # videozapisa}few{Dijeljenje # videozapisa}other{Dijeljenje # videozapisa}}" "{count,plural, =1{Dijeljenje # fajla}one{Dijeljenje # fajla}few{Dijeljenje # fajla}other{Dijeljenje # fajlova}}" - - + "Odaberite stavke za dijeljenje" "{count,plural, =1{Dijeljenje slike putem poruke}one{Dijeljenje # slike putem poruke}few{Dijeljenje # slike putem poruke}other{Dijeljenje # slika putem poruke}}" "{count,plural, =1{Dijeljenje slike putem linka}one{Dijeljenje # slike putem linka}few{Dijeljenje # slike putem linka}other{Dijeljenje # slika putem linka}}" "{count,plural, =1{Dijeljenje videozapisa putem poruke}one{Dijeljenje # videozapisa putem poruke}few{Dijeljenje # videozapisa putem poruke}other{Dijeljenje # videozapisa putem poruke}}" diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml index 1529787b..11029365 100644 --- a/java/res/values-ca/strings.xml +++ b/java/res/values-ca/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Comparteix una imatge}many{Comparteix # d\'imatges}other{Comparteix # imatges}}" "{count,plural, =1{S\'està compartint un vídeo}many{S\'estan compartint # de vídeos}other{S\'estan compartint # vídeos}}" "{count,plural, =1{S\'està compartint # fitxer}many{S\'estan compartint # de fitxers}other{S\'estan compartint # fitxers}}" - - + "Selecciona els elements que vols compartir" "{count,plural, =1{S\'està compartint la imatge amb text}many{S\'estan compartint # d\'imatges amb text}other{S\'estan compartint # imatges amb text}}" "{count,plural, =1{S\'està compartint la imatge amb un enllaç}many{S\'estan compartint # d\'imatges amb un enllaç}other{S\'estan compartint # imatges amb un enllaç}}" "{count,plural, =1{S\'està compartint el vídeo amb un enllaç}many{S\'estan compartint # de vídeos amb un enllaç}other{S\'estan compartint # vídeos amb un enllaç}}" diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml index 6c9e2e61..0ce7e140 100644 --- a/java/res/values-cs/strings.xml +++ b/java/res/values-cs/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Sdílení obrázku}few{Sdílení # obrázků}many{Sdílení # obrázku}other{Sdílení # obrázků}}" "{count,plural, =1{Sdílení videa}few{Sdílení # videí}many{Sdílení # videa}other{Sdílení # videí}}" "{count,plural, =1{Sdílení # souboru}few{Sdílení # souborů}many{Sdílení # souboru}other{Sdílení # souborů}}" - - + "Vyberte položky, které chcete sdílet" "{count,plural, =1{Sdílení obrázku s textem}few{Sdílení # obrázků s textem}many{Sdílení # obrázku s textem}other{Sdílení # obrázků s textem}}" "{count,plural, =1{Sdílení obrázku s odkazem}few{Sdílení # obrázků s odkazem}many{Sdílení # obrázku s odkazem}other{Sdílení # obrázků s odkazem}}" "{count,plural, =1{Sdílení videa s textem}few{Sdílení # videí s textem}many{Sdílení # videa s textem}other{Sdílení # videí s textem}}" diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml index e9faae8a..3a3e2062 100644 --- a/java/res/values-da/strings.xml +++ b/java/res/values-da/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deler billede}one{Deler # billede}other{Deler # billeder}}" "{count,plural, =1{Deler video}one{Deler # video}other{Deler # videoer}}" "{count,plural, =1{Deler # fil}one{Deler # fil}other{Deler # filer}}" - - + "Vælg elementer til deling" "{count,plural, =1{Deler billede med tekst}one{Deler # billede med tekst}other{Deler # billeder med tekst}}" "{count,plural, =1{Deler billede med et link}one{Deler # billede med et link}other{Deler # billeder med et link}}" "{count,plural, =1{Deler video med tekst}one{Deler # video med tekst}other{Deler # videoer med tekst}}" diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml index 2a96c519..3a561101 100644 --- a/java/res/values-de/strings.xml +++ b/java/res/values-de/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Bild wird geteilt}other{# Bilder werden geteilt}}" "{count,plural, =1{Video wird geteilt}other{# Videos werden geteilt}}" "{count,plural, =1{# Datei wird freigegeben}other{# Dateien werden freigegeben}}" - - + "Elemente zum Teilen auswählen" "{count,plural, =1{Bild wird mit Text geteilt}other{# Bilder werden mit Text geteilt}}" "{count,plural, =1{Bild wird per Link geteilt}other{# Bilder werden per Link geteilt}}" "{count,plural, =1{Video wird per SMS geteilt}other{# Videos werden per SMS geteilt}}" diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml index ef23cf91..8903eec1 100644 --- a/java/res/values-el/strings.xml +++ b/java/res/values-el/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Κοινοποίηση εικόνας}other{Κοινοποίηση # εικόνων}}" "{count,plural, =1{Κοινοποίηση βίντεο}other{Κοινοποίηση # βίντεο}}" "{count,plural, =1{Κοινή χρήση # αρχείου}other{Κοινή χρήση # αρχείων}}" - - + "Επιλογή στοιχείων για κοινή χρήση" "{count,plural, =1{Κοινοποίηση εικόνας με κείμενο}other{Κοινοποίηση # εικόνων με κείμενο}}" "{count,plural, =1{Κοινοποίηση εικόνας με σύνδεσμο}other{Κοινοποίηση # εικόνων με σύνδεσμο}}" "{count,plural, =1{Κοινοποίηση βίντεο με κείμενο}other{Κοινοποίηση # βίντεο με κείμενο}}" diff --git a/java/res/values-en-rAU/strings.xml b/java/res/values-en-rAU/strings.xml index 699b3bf0..53e64659 100644 --- a/java/res/values-en-rAU/strings.xml +++ b/java/res/values-en-rAU/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" - - + "Select items to share" "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml index 699b3bf0..53e64659 100644 --- a/java/res/values-en-rGB/strings.xml +++ b/java/res/values-en-rGB/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" - - + "Select items to share" "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-en-rIN/strings.xml b/java/res/values-en-rIN/strings.xml index 699b3bf0..53e64659 100644 --- a/java/res/values-en-rIN/strings.xml +++ b/java/res/values-en-rIN/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Sharing image}other{Sharing # images}}" "{count,plural, =1{Sharing video}other{Sharing # videos}}" "{count,plural, =1{Sharing # file}other{Sharing # files}}" - - + "Select items to share" "{count,plural, =1{Sharing image with text}other{Sharing # images with text}}" "{count,plural, =1{Sharing image with link}other{Sharing # images with link}}" "{count,plural, =1{Sharing video with text}other{Sharing # videos with text}}" diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml index 9b9ea1a1..f3b7fe85 100644 --- a/java/res/values-es-rUS/strings.xml +++ b/java/res/values-es-rUS/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Compartir la imagen}many{Compartir # de imágenes}other{Compartir # imágenes}}" "{count,plural, =1{Compartiendo video}many{Compartiendo # de videos}other{Compartiendo # videos}}" "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # de archivos}other{Compartiendo # archivos}}" - - + "Selecciona los elementos para compartir" "{count,plural, =1{Compartir imagen con texto}many{Compartir # de imágenes con texto}other{Compartir # imágenes con texto}}" "{count,plural, =1{Compartir imagen con vínculo}many{Compartir # de imágenes con vínculo}other{Compartir # imágenes con vínculo}}" "{count,plural, =1{Compartir video con texto}many{Compartir # de videos con texto}other{Compartir # videos con texto}}" diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml index 7a029873..460de896 100644 --- a/java/res/values-es/strings.xml +++ b/java/res/values-es/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Compartiendo imagen}many{Compartiendo # imágenes}other{Compartiendo # imágenes}}" "{count,plural, =1{Compartiendo vídeo}many{Compartiendo # vídeos}other{Compartiendo # vídeos}}" "{count,plural, =1{Compartiendo # archivo}many{Compartiendo # archivos}other{Compartiendo # archivos}}" - - + "Selecciona los elementos que quieres compartir" "{count,plural, =1{Compartiendo imagen con texto}many{Compartiendo # imágenes con texto}other{Compartiendo # imágenes con texto}}" "{count,plural, =1{Compartiendo imagen con enlace}many{Compartiendo # imágenes con enlace}other{Compartiendo # imágenes con enlace}}" "{count,plural, =1{Compartiendo vídeo con texto}many{Compartiendo # vídeos con texto}other{Compartiendo # vídeos con texto}}" diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml index 86023bc5..85fca08f 100644 --- a/java/res/values-et/strings.xml +++ b/java/res/values-et/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Pildi jagamine}other{# pildi jagamine}}" "{count,plural, =1{Video jagamine}other{# video jagamine}}" "{count,plural, =1{# faili jagamine}other{# faili jagamine}}" - - + "Jagatavate üksuste valimine" "{count,plural, =1{Teksti sisaldava pildi jagamine}other{# teksti sisaldava pildi jagamine}}" "{count,plural, =1{Linki sisaldava pildi jagamine}other{# linki sisaldava pildi jagamine}}" "{count,plural, =1{Teksti sisaldava video jagamine}other{# teksti sisaldava video jagamine}}" diff --git a/java/res/values-eu/strings.xml b/java/res/values-eu/strings.xml index 034abd34..5020f62d 100644 --- a/java/res/values-eu/strings.xml +++ b/java/res/values-eu/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Irudia partekatuko da}other{# irudi partekatuko dira}}" "{count,plural, =1{Bideoa partekatzen}other{# bideo partekatzen}}" "{count,plural, =1{# fitxategi partekatuko da}other{# fitxategi partekatuko dira}}" - - + "Hautatu partekatu beharreko elementuak" "{count,plural, =1{Irudi testudun bat partekatuko da}other{# irudi testudun partekatuko dira}}" "{count,plural, =1{Irudi estekadun bat partekatuko da}other{# irudi estekadun partekatuko dira}}" "{count,plural, =1{Bideo testudun bat partekatuko da}other{# bideo testudun partekatuko dira}}" diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index 85e2524d..ade41874 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{هم‌رسانی تصویر}one{هم‌رسانی ‍# تصویر}other{هم‌رسانی ‍# تصویر}}" "{count,plural, =1{درحال هم‌رسانی ویدیو}one{درحال هم‌رسانی # ویدیو}other{درحال هم‌رسانی # ویدیو}}" "{count,plural, =1{هم‌رسانی # فایل}one{هم‌رسانی # فایل}other{هم‌رسانی # فایل}}" - - + "انتخاب کردن موارد برای هم‌رسانی" "{count,plural, =1{درحال هم‌رسانی تصویر با نوشتار}one{درحال هم‌رسانی # تصویر با نوشتار}other{درحال هم‌رسانی # تصویر با نوشتار}}" "{count,plural, =1{درحال هم‌رسانی تصویر با پیوند}one{درحال هم‌رسانی # تصویر با پیوند}other{درحال هم‌رسانی # تصویر با پیوند}}" "{count,plural, =1{درحال هم‌رسانی ویدیو با نوشتار}one{درحال هم‌رسانی # ویدیو با نوشتار}other{درحال هم‌رسانی # ویدیو با نوشتار}}" diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml index 754fa720..65244293 100644 --- a/java/res/values-fi/strings.xml +++ b/java/res/values-fi/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Jaetaan kuvaa}other{Jaetaan # kuvaa}}" "{count,plural, =1{Jaetaan videota}other{Jaetaan # videota}}" "{count,plural, =1{Jaetaan # tiedosto}other{Jaetaan # tiedostoa}}" - - + "Valitse jaettavat kohteet" "{count,plural, =1{Kuvaa ja tekstiä jaetaan}other{# kuvaa ja tekstiä jaetaan}}" "{count,plural, =1{Kuvaa ja linkkiä jaetaan}other{# kuvaa ja linkkiä jaetaan}}" "{count,plural, =1{Videota ja tekstiä jaetaan}other{# videota ja tekstiä jaetaan}}" diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml index 76f50b21..b2ae5f5c 100644 --- a/java/res/values-fr-rCA/strings.xml +++ b/java/res/values-fr-rCA/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Partage d\'une image}one{Partage de # image}many{Partage de # d\'images}other{Partage de # images}}" "{count,plural, =1{Partage de la vidéo…}one{Partage de # vidéo…}many{Partage de # de vidéos…}other{Partage de # vidéos…}}" "{count,plural, =1{Partage de # fichier en cours…}one{Partage de # fichier en cours…}many{Partage de # de fichiers en cours…}other{Partage de # fichiers en cours…}}" - - + "Sélectionner les éléments à partager" "{count,plural, =1{Partage d\'une image avec du texte}one{Partage de # image avec du texte}many{Partage de # d\'images avec du texte}other{Partage de # images avec du texte}}" "{count,plural, =1{Partage d\'une image avec un lien}one{Partage de # image avec un lien}many{Partage de # d\'images avec un lien}other{Partage de # images avec un lien}}" "{count,plural, =1{Partage d\'une vidéo avec du texte}one{Partage de # vidéo avec du texte}many{Partage de # de vidéos avec du texte}other{Partage de # vidéos avec du texte}}" diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml index 1317bc71..2b96c92f 100644 --- a/java/res/values-fr/strings.xml +++ b/java/res/values-fr/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Partager l\'image}one{Partager # image}many{Partager # d\'images}other{Partager # images}}" "{count,plural, =1{Partage de la vidéo…}one{Partage de # vidéo…}many{Partage de # de vidéos…}other{Partage de # vidéos…}}" "{count,plural, =1{Partage de # fichier}one{Partage de # fichier}many{Partage de # fichiers}other{Partage de # fichiers}}" - - + "Sélectionner les éléments à partager" "{count,plural, =1{Partager 1 image avec du texte}one{Partager # image avec du texte}many{Partager # images avec du texte}other{Partager # images avec du texte}}" "{count,plural, =1{Partager 1 image avec un lien}one{Partager # image avec un lien}many{Partager # images avec un lien}other{Partager # images avec un lien}}" "{count,plural, =1{Partager 1 vidéo avec du texte}one{Partager # vidéo avec du texte}many{Partager # vidéos avec du texte}other{Partager # vidéos avec du texte}}" diff --git a/java/res/values-gl/strings.xml b/java/res/values-gl/strings.xml index 4eec6aa0..a8caf6f3 100644 --- a/java/res/values-gl/strings.xml +++ b/java/res/values-gl/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Compartindo imaxe}other{Compartindo # imaxes}}" "{count,plural, =1{Compartindo vídeo}other{Compartindo # vídeos}}" "{count,plural, =1{Compartindo # ficheiro}other{Compartindo # ficheiros}}" - - + "Seleccionar elementos para compartir" "{count,plural, =1{Compartindo imaxe con texto}other{Compartindo # imaxes con texto}}" "{count,plural, =1{Compartindo imaxe con ligazón}other{Compartindo # imaxes con ligazón}}" "{count,plural, =1{Compartindo vídeo con texto}other{Compartindo # vídeos con texto}}" diff --git a/java/res/values-gu/strings.xml b/java/res/values-gu/strings.xml index a857e943..a70a1b0f 100644 --- a/java/res/values-gu/strings.xml +++ b/java/res/values-gu/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{છબી શેર કરી રહ્યાં છીએ}one{# છબી શેર કરી રહ્યાં છીએ}other{# છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{વીડિયો શેર કરીએ છીએ}one{# વીડિયો શેર કરીએ છીએ}other{# વીડિયો શેર કરીએ છીએ}}" "{count,plural, =1{# ફાઇલ શેર કરી રહ્યાં છીએ}one{# ફાઇલ શેર કરી રહ્યાં છીએ}other{# ફાઇલ શેર કરી રહ્યાં છીએ}}" - - + "શેર કરવા માટે આઇટમ પસંદ કરો" "{count,plural, =1{ટેક્સ્ટ સાથે છબી શેર કરી રહ્યાં છીએ}one{ટેક્સ્ટ સાથે # છબી શેર કરી રહ્યાં છીએ}other{ટેક્સ્ટ સાથે # છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{લિંક સાથે છબી શેર કરી રહ્યાં છીએ}one{લિંક સાથે # છબી શેર કરી રહ્યાં છીએ}other{લિંક સાથે # છબી શેર કરી રહ્યાં છીએ}}" "{count,plural, =1{ટેક્સ્ટ સાથે વીડિયો શેર કરી રહ્યાં છીએ}one{ટેક્સ્ટ સાથે # વીડિયો શેર કરી રહ્યાં છીએ}other{ટેક્સ્ટ સાથે # વીડિયો શેર કરી રહ્યાં છીએ}}" diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml index f4f1846a..0cc9b219 100644 --- a/java/res/values-hi/strings.xml +++ b/java/res/values-hi/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{इमेज शेयर की जा रही है}one{# इमेज शेयर की जा रही है}other{# इमेज शेयर की जा रही हैं}}" "{count,plural, =1{वीडियो शेयर किया जा रहा है}one{# वीडियो शेयर किया जा रहा है}other{# वीडियो शेयर किए जा रहे हैं}}" "{count,plural, =1{# फ़ाइल शेयर की जा रही है}one{# फ़ाइल शेयर की जा रही है}other{# फ़ाइलें शेयर की जा रही हैं}}" - - + "शेयर करने के लिए आइटम चुनें" "{count,plural, =1{टेक्स्ट के साथ इमेज शेयर की जा रही है}one{टेक्स्ट के साथ # इमेज शेयर की जा रही है}other{टेक्स्ट के साथ # इमेज शेयर की जा रही हैं}}" "{count,plural, =1{लिंक के साथ इमेज शेयर की जा रही है}one{लिंक के साथ # इमेज शेयर की जा रही है}other{लिंक के साथ # इमेज शेयर की जा रही हैं}}" "{count,plural, =1{टेक्स्ट के साथ वीडियो शेयर किया जा रहा है}one{टेक्स्ट के साथ # वीडियो शेयर किया जा रहा है}other{टेक्स्ट के साथ # वीडियो शेयर किए जा रहे हैं}}" diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml index 02cc3391..85858303 100644 --- a/java/res/values-hr/strings.xml +++ b/java/res/values-hr/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeli se videozapis}one{Dijeli se # videozapis}few{Dijele se # videozapisa}other{Dijeli se # videozapisa}}" "{count,plural, =1{Dijeli se # datoteka}one{Dijeli se # datoteka}few{Dijele se # datoteke}other{Dijeli se # datoteka}}" - - + "Odaberite stavke za dijeljenje" "{count,plural, =1{Dijeli se slika s tekstom}one{Dijeli se # slika s tekstom}few{Dijele se # slike s tekstom}other{Dijeli se # slika s tekstom}}" "{count,plural, =1{Dijeli se slika s vezom}one{Dijeli se # slika s vezom}few{Dijele se # slike s vezom}other{Dijeli se # slika s vezom}}" "{count,plural, =1{Dijeli se videozapis s tekstom}one{Dijeli se # videozapis s tekstom}few{Dijele se # videozapisa s tekstom}other{Dijeli se # videozapisa s tekstom}}" diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml index 6874dbc7..792b07e2 100644 --- a/java/res/values-hu/strings.xml +++ b/java/res/values-hu/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Kép megosztása}other{# kép megosztása}}" "{count,plural, =1{Videó megosztása}other{# videó megosztása}}" "{count,plural, =1{# fájl megosztása}other{# fájl megosztása}}" - - + "Válassza ki a megosztani kívánt elemeket" "{count,plural, =1{Kép megosztása szöveggel}other{# kép megosztása szöveggel}}" "{count,plural, =1{Kép megosztása linkkel}other{# kép megosztása linkkel}}" "{count,plural, =1{Videó megosztása szöveggel}other{# videó megosztása szöveggel}}" diff --git a/java/res/values-hy/strings.xml b/java/res/values-hy/strings.xml index da5ae914..f9232a5a 100644 --- a/java/res/values-hy/strings.xml +++ b/java/res/values-hy/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Պատկերի ուղարկում}one{# պատկերի ուղարկում}other{# պատկերի ուղարկում}}" "{count,plural, =1{Տեսանյութի ուղարկում}one{# տեսանյութի ուղարկում}other{# տեսանյութի ուղարկում}}" "{count,plural, =1{Ուղարկվում է # ֆայլ}one{Ուղարկվում է # ֆայլ}other{Ուղարկվում է # ֆայլ}}" - - + "Ընտրեք տարրեր՝ կիսվելու համար" "{count,plural, =1{Պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}one{# պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}other{# պատկերի ուղարկում տեքստային հաղորդագրության միջոցով}}" "{count,plural, =1{Պատկերի ուղարկում հղման միջոցով}one{# պատկերի ուղարկում հղման միջոցով}other{# պատկերի ուղարկում հղման միջոցով}}" "{count,plural, =1{Տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}one{# տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}other{# տեսանյութի ուղարկում տեքստային հաղորդագրության միջոցով}}" diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml index 7bacf1b7..df05fdd0 100644 --- a/java/res/values-in/strings.xml +++ b/java/res/values-in/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Berbagi gambar}other{Berbagi # gambar}}" "{count,plural, =1{Membagikan video}other{Membagikan # video}}" "{count,plural, =1{Membagikan # file}other{Membagikan # file}}" - - + "Pilih item untuk dibagikan" "{count,plural, =1{Membagikan gambar dengan teks}other{Membagikan # gambar dengan teks}}" "{count,plural, =1{Membagikan gambar dengan link}other{Membagikan # gambar dengan link}}" "{count,plural, =1{Membagikan video dengan teks}other{Membagikan # video dengan teks}}" diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml index cb889e35..680ed17a 100644 --- a/java/res/values-is/strings.xml +++ b/java/res/values-is/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deilir mynd}one{Deilir # mynd}other{Deilir # myndum}}" "{count,plural, =1{Deilir myndskeiði}one{Deilir # myndskeiði}other{Deilir # myndskeiðum}}" "{count,plural, =1{Deilir # skrá}one{Deilir # skrá}other{Deilir # skrám}}" - - + "Veldu atriði til að deila" "{count,plural, =1{Deilir mynd með texta}one{Deilir # mynd með texta}other{Deilir # myndum með texta}}" "{count,plural, =1{Deilir mynd með tengli}one{Deilir # mynd með tengli}other{Deilir # myndum með tengli}}" "{count,plural, =1{Deilir myndskeiði með texta}one{Deilir # myndskeiði með texta}other{Deilir # myndskeiðum með texta}}" diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml index 9858a8cd..3762f58b 100644 --- a/java/res/values-it/strings.xml +++ b/java/res/values-it/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Condivisione dell\'immagine}many{Condivisione di # immagini}other{Condivisione di # immagini}}" "{count,plural, =1{Condivisione del video…}many{Condivisione di # video…}other{Condivisione di # video…}}" "{count,plural, =1{Condivisione di # file in corso…}many{Condivisione di # file in corso…}other{Condivisione di # file in corso…}}" - - + "Seleziona gli elementi da condividere" "{count,plural, =1{Condivisione immagine con testo in corso…}many{Condivisione # immagini con testo in corso…}other{Condivisione # immagini con testo in corso…}}" "{count,plural, =1{Condivisione immagine con link}many{Condivisione # immagini con link}other{Condivisione # immagini con link}}" "{count,plural, =1{Condivisione video con messaggio in corso…}many{Condivisione # video con messaggio in corso…}other{Condivisione # video con messaggio in corso…}}" diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml index ec489888..e7521e77 100644 --- a/java/res/values-iw/strings.xml +++ b/java/res/values-iw/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{שיתוף של תמונה}one{שיתוף של # תמונות}two{שיתוף של # תמונות}other{שיתוף של # תמונות}}" "{count,plural, =1{מתבצע שיתוף של סרטון}one{מתבצע שיתוף של # סרטונים}two{מתבצע שיתוף של # סרטונים}other{מתבצע שיתוף של # סרטונים}}" "{count,plural, =1{מתבצע שיתוף של קובץ אחד}one{מתבצע שיתוף של # קבצים}two{מתבצע שיתוף של # קבצים}other{מתבצע שיתוף של # קבצים}}" - - + "בחירת פריטים לשיתוף" "{count,plural, =1{שיתוף תמונה עם טקסט}one{שיתוף # תמונות עם טקסט}two{שיתוף # תמונות עם טקסט}other{שיתוף # תמונות עם טקסט}}" "{count,plural, =1{שיתוף תמונה עם קישור}one{שיתוף # תמונות עם קישור}two{שיתוף # תמונות עם קישור}other{שיתוף # תמונות עם קישור}}" "{count,plural, =1{שיתוף סרטון עם טקסט}one{שיתוף # סרטונים עם טקסט}two{שיתוף # סרטונים עם טקסט}other{שיתוף # סרטונים עם טקסט}}" diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml index 1d90a71b..1d2a2f06 100644 --- a/java/res/values-ja/strings.xml +++ b/java/res/values-ja/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{1 枚の画像を共有します}other{# 枚の画像を共有します}}" "{count,plural, =1{動画を共有中}other{# 個の動画を共有中}}" "{count,plural, =1{# 個のファイルを共有中}other{# 個のファイルを共有中}}" - - + "共有するアイテムの選択" "{count,plural, =1{テキスト付き画像を共有しています}other{テキスト付き画像を # 件共有しています}}" "{count,plural, =1{リンク付き画像を共有しています}other{リンク付き画像を # 件共有しています}}" "{count,plural, =1{テキスト付き動画を共有中}other{テキスト付き動画を # 件共有中}}" diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml index ad8e6dfb..4675734b 100644 --- a/java/res/values-ka/strings.xml +++ b/java/res/values-ka/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ზიარდება სურათი}other{ზიარდება # სურათი}}" "{count,plural, =1{ზიარდება ვიდეო}other{ზიარდება # ვიდეო}}" "{count,plural, =1{ზიარდება # ფაილი}other{ზიარდება # ფაილი}}" - - + "გასაზიარებელი ერთეულების არჩევა" "{count,plural, =1{სურათი ზიარდება ტექსტით}other{# სურათი ზიარდება ტექსტით}}" "{count,plural, =1{სურათი ზიარდება ბმულით}other{# სურათი ზიარდება ბმულით}}" "{count,plural, =1{ვიდეო ზიარდება ტექსტით}other{# ვიდეო ზიარდება ტექსტით}}" diff --git a/java/res/values-kk/strings.xml b/java/res/values-kk/strings.xml index ce71c71d..362db640 100644 --- a/java/res/values-kk/strings.xml +++ b/java/res/values-kk/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Сурет бөлісіп жатырсыз}other{# сурет бөлісіп жатырсыз}}" "{count,plural, =1{Бейне бөлісіліп жатыр}other{# бейне бөлісіліп жатыр}}" "{count,plural, =1{# файлды бөлісіп жатыр}other{# файлды бөлісіп жатыр}}" - - + "Бөлісетін элементтерді таңдау" "{count,plural, =1{Мәтіні бар сурет жіберу}other{Мәтіні бар # сурет жіберу}}" "{count,plural, =1{Сілтемесі бар сурет жіберу}other{Сілтемесі бар # сурет жіберу}}" "{count,plural, =1{Мәтіні бар бейне жіберу}other{Мәтіні бар # бейне жіберу}}" diff --git a/java/res/values-km/strings.xml b/java/res/values-km/strings.xml index 0e75c153..cee11e26 100644 --- a/java/res/values-km/strings.xml +++ b/java/res/values-km/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{កំពុងចែក​រំលែករូបភាព}other{កំពុងចែក​រំលែករូបភាព #}}" "{count,plural, =1{កំពុងចែករំលែកវីដេអូ}other{កំពុងចែករំលែកវីដេអូ #}}" "{count,plural, =1{កំពុង​ចែករំលែកឯកសារ #}other{កំពុង​ចែករំលែកឯកសារ #}}" - - + "ជ្រើសរើសធាតុដែលត្រូវចែករំលែក" "{count,plural, =1{ចែករំលែករូបភាពជាមួយអក្សរ}other{ចែករំលែករូបភាព # ជាមួយអក្សរ}}" "{count,plural, =1{ចែករំលែករូបភាពជាមួយតំណ}other{ចែករំលែករូបភាព # ជាមួយតំណ}}" "{count,plural, =1{ចែករំលែកវីដេអូជាមួយអក្សរ}other{ចែករំលែក # វីដេអូជាមួយអក្សរ}}" diff --git a/java/res/values-kn/strings.xml b/java/res/values-kn/strings.xml index 84c830d2..1143ac8f 100644 --- a/java/res/values-kn/strings.xml +++ b/java/res/values-kn/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{# ಫೈಲ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" - - + "ಹಂಚಿಕೊಳ್ಳಲು ಐಟಂಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" "{count,plural, =1{ಪಠ್ಯದೊಂದಿಗೆ ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಪಠ್ಯದೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಪಠ್ಯದೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ಲಿಂಕ್‌ನೊಂದಿಗೆ ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಲಿಂಕ್‌ನೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಲಿಂಕ್‌ನೊಂದಿಗೆ # ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ಪಠ್ಯದೊಂದಿಗೆ ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{ಪಠ್ಯದೊಂದಿಗೆ # ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{ಪಠ್ಯದೊಂದಿಗೆ # ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml index b9c27045..094f09b0 100644 --- a/java/res/values-ko/strings.xml +++ b/java/res/values-ko/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{이미지 공유}other{이미지 #개 공유}}" "{count,plural, =1{동영상 1개 공유 중}other{동영상 #개 공유 중}}" "{count,plural, =1{파일 #개 공유 중}other{파일 #개 공유 중}}" - - + "공유할 항목 선택" "{count,plural, =1{텍스트로 이미지 공유 중}other{텍스트로 이미지 #개 공유 중}}" "{count,plural, =1{링크로 이미지 공유 중}other{링크로 이미지 #개 공유 중}}" "{count,plural, =1{텍스트로 동영상 공유 중}other{텍스트로 동영상 #개 공유 중}}" diff --git a/java/res/values-ky/strings.xml b/java/res/values-ky/strings.xml index 55d91a02..610adaf2 100644 --- a/java/res/values-ky/strings.xml +++ b/java/res/values-ky/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Сүрөт бөлүшүү}other{# сүрөт бөлүшүлүүдө}}" "{count,plural, =1{Видео бөлүшүлүүдө}other{# видео бөлүшүлүүдө}}" "{count,plural, =1{# файл бөлүшүлүүдө}other{# файл бөлүшүлүүдө}}" - - + "Бөлүшө турган нерселерди тандаңыз" "{count,plural, =1{Сүрөттү текст менен жөнөтүү}other{# cүрөттү текст менен жөнөтүү}}" "{count,plural, =1{Сүрөттү шилтеме менен жөнөтүү}other{# сүрөттү шилтеме менен жөнөтүү}}" "{count,plural, =1{Видеону текст менен жөнөтүү}other{# видеону текст менен жөнөтүү}}" diff --git a/java/res/values-lo/strings.xml b/java/res/values-lo/strings.xml index cb6050d1..2cdea91f 100644 --- a/java/res/values-lo/strings.xml +++ b/java/res/values-lo/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບ}other{ກຳລັງແບ່ງປັນ # ຮູບ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນວິດີໂອ}other{ກຳລັງແບ່ງປັນ # ວິດີໂອ}}" "{count,plural, =1{ກຳລັງຈະແບ່ງປັນ # ໄຟລ໌}other{ກຳລັງຈະແບ່ງປັນ # ໄຟລ໌}}" - - + "ເລືອກລາຍການທີ່ຈະແບ່ງປັນ" "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບພ້ອມຂໍ້ຄວາມ}other{ກຳລັງແບ່ງປັນ # ຮູບພ້ອມຂໍ້ຄວາມ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນຮູບພ້ອມລິ້ງ}other{ກຳລັງແບ່ງປັນ # ຮູບພ້ອມລິ້ງ}}" "{count,plural, =1{ກຳລັງແບ່ງປັນວິດີໂອພ້ອມຂໍ້ຄວາມ}other{ກຳລັງແບ່ງປັນ # ວິດີໂອພ້ອມຂໍ້ຄວາມ}}" diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml index 706ed856..7b0c6695 100644 --- a/java/res/values-lt/strings.xml +++ b/java/res/values-lt/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Bendrinamas vaizdas}one{Bendrinamas # vaizdas}few{Bendrinami # vaizdai}many{Bendrinama # vaizdo}other{Bendrinama # vaizdų}}" "{count,plural, =1{Bendrinamas vaizdo įrašas}one{Bendrinamas # vaizdo įrašas}few{Bendrinami # vaizdo įrašai}many{Bendrinama # vaizdo įrašo}other{Bendrinama # vaizdo įrašų}}" "{count,plural, =1{Bendrinamas # failas}one{Bendrinamas # failas}few{Bendrinami # failai}many{Bendrinama # failo}other{Bendrinama # failų}}" - - + "Norimų bendrinti elementų pasirinkimas" "{count,plural, =1{Bendrinamas vaizdas su tekstu}one{Bendrinamas # vaizdas su tekstu}few{Bendrinami # vaizdai su tekstu}many{Bendrinama # vaizdo su tekstu}other{Bendrinama # vaizdų su tekstu}}" "{count,plural, =1{Bendrinamas vaizdas su nuoroda}one{Bendrinamas # vaizdas su nuoroda}few{Bendrinami # vaizdai su nuoroda}many{Bendrinama # vaizdo su nuoroda}other{Bendrinama # vaizdų su nuoroda}}" "{count,plural, =1{Bendrinamas vaizdo įrašas su tekstu}one{Bendrinamas # vaizdo įrašas su tekstu}few{Bendrinami # vaizdo įrašai su tekstu}many{Bendrinama # vaizdo įrašo su tekstu}other{Bendrinama # vaizdo įrašų su tekstu}}" diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml index a53bc08b..1c14c2b8 100644 --- a/java/res/values-lv/strings.xml +++ b/java/res/values-lv/strings.xml @@ -60,8 +60,7 @@ "{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}}" "{count,plural, =1{Tiek kopīgots video}zero{Tiek kopīgoti # video}one{Tiek kopīgots # video}other{Tiek kopīgoti # video}}" "{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}}" - - + "Atlasiet kopīgojamos vienumus" "{count,plural, =1{Tiek kopīgots attēls ar tekstu}zero{Tiek kopīgoti # attēli ar tekstu}one{Tiek kopīgots # attēls ar tekstu}other{Tiek kopīgoti # attēli ar tekstu}}" "{count,plural, =1{Tiek kopīgots attēls ar saiti}zero{Tiek kopīgoti # attēli ar saitēm}one{Tiek kopīgots # attēls ar saitēm}other{Tiek kopīgoti # attēli ar saitēm}}" "{count,plural, =1{Tiek kopīgots videoklips ar tekstu}zero{Tiek kopīgoti # videoklipi ar tekstu}one{Tiek kopīgots # videoklips ar tekstu}other{Tiek kopīgoti # videoklipi ar tekstu}}" diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml index 1d01459a..19ff3c67 100644 --- a/java/res/values-mk/strings.xml +++ b/java/res/values-mk/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Споделување слика}one{Споделување # слика}other{Споделување # слики}}" "{count,plural, =1{Се споделува видео}one{Се споделува # видео}other{Се споделуваат # видеа}}" "{count,plural, =1{Се споделува # датотека}one{Се споделуваат # датотека}other{Се споделуваат # датотеки}}" - - + "Изберете ставки за споделување" "{count,plural, =1{Се споделува слика со SMS}one{Се споделуваат # слика со SMS}other{Се споделуваат # слики со SMS}}" "{count,plural, =1{Се споделува слика со линк}one{Се споделуваат # слика со линк}other{Се споделуваат # слики со линк}}" "{count,plural, =1{Се споделува видео со SMS}one{Се споделуваат # видео со SMS}other{Се споделуваат # видеа со SMS}}" diff --git a/java/res/values-ml/strings.xml b/java/res/values-ml/strings.xml index 3faa7981..bcd07dd7 100644 --- a/java/res/values-ml/strings.xml +++ b/java/res/values-ml/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ചിത്രം പങ്കിടുന്നു}other{# ചിത്രങ്ങൾ പങ്കിടുന്നു}}" "{count,plural, =1{വീഡിയോ പങ്കിടുന്നു}other{# വീഡിയോകൾ പങ്കിടുന്നു}}" "{count,plural, =1{# ഫയൽ പങ്കിടുന്നു}other{# ഫയലുകൾ പങ്കിടുന്നു}}" - - + "പങ്കിടാൻ ഇനങ്ങൾ തിരഞ്ഞെടുക്കുക" "{count,plural, =1{ടെക്സ്റ്റിനൊപ്പം ചിത്രം പങ്കിടുന്നു}other{ടെക്സ്റ്റിനൊപ്പം # ചിത്രം പങ്കിടുന്നു}}" "{count,plural, =1{ലിങ്കിനൊപ്പം ചിത്രം പങ്കിടുന്നു}other{ലിങ്കിനൊപ്പം # ചിത്രങ്ങൾ പങ്കിടുന്നു}}" "{count,plural, =1{ടെക്സ്റ്റിനൊപ്പം വീഡിയോ പങ്കിടുന്നു}other{ടെക്സ്റ്റിനൊപ്പം # വീഡിയോകൾ പങ്കിടുന്നു}}" diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml index 4287f8c9..81d97d99 100644 --- a/java/res/values-mn/strings.xml +++ b/java/res/values-mn/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Зураг хуваалцаж байна}other{# зураг хуваалцаж байна}}" "{count,plural, =1{Видео хуваалцаж байна}other{# видео хуваалцаж байна}}" "{count,plural, =1{# файл хуваалцаж байна}other{# файл хуваалцаж байна}}" - - + "Хуваалцах зүйлс сонгох" "{count,plural, =1{Тексттэй зураг хуваалцаж байна}other{Тексттэй # зураг хуваалцаж байна}}" "{count,plural, =1{Холбоостой зураг хуваалцаж байна}other{Холбоостой # зураг хуваалцаж байна}}" "{count,plural, =1{Тексттэй видео хуваалцаж байна}other{Тексттэй # видео хуваалцаж байна}}" diff --git a/java/res/values-mr/strings.xml b/java/res/values-mr/strings.xml index f964e1f5..4a061601 100644 --- a/java/res/values-mr/strings.xml +++ b/java/res/values-mr/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{इमेज शेअर करत आहे}other{# इमेज शेअर करत आहे}}" "{count,plural, =1{व्हिडिओ शेअर करत आहे}other{# व्हिडिओ शेअर करत आहे}}" "{count,plural, =1{# फाइल शेअर करत आहे}other{# फाइल शेअर करत आहे}}" - - + "शेअर करण्यासाठी आयटम निवडा" "{count,plural, =1{मजकुरासह इमेज शेअर करत आहे}other{मजकुरासह # इमेज शेअर करत आहे}}" "{count,plural, =1{लिंकसह इमेज शेअर करत आहे}other{लिंकसह # इमेज शेअर करत आहे}}" "{count,plural, =1{मजकुरासह व्हिडिओ शेअर करत आहे}other{मजकुरासह # व्हिडिओ शेअर करत आहे}}" diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml index c851c9e2..a01376c6 100644 --- a/java/res/values-ms/strings.xml +++ b/java/res/values-ms/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Berkongsi imej}other{Berkongsi # imej}}" "{count,plural, =1{Berkongsi video}other{Berkongsi # video}}" "{count,plural, =1{Berkongsi # fail}other{Berkongsi # fail}}" - - + "Pilih item untuk dikongsi" "{count,plural, =1{Berkongsi imej dengan teks}other{Berkongsi # imej dengan teks}}" "{count,plural, =1{Berkongsi imej dengan pautan}other{Berkongsi # imej dengan pautan}}" "{count,plural, =1{Berkongsi video dengan teks}other{Berkongsi # video dengan teks}}" diff --git a/java/res/values-my/strings.xml b/java/res/values-my/strings.xml index b6de8795..9eeda078 100644 --- a/java/res/values-my/strings.xml +++ b/java/res/values-my/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ပုံ မျှဝေနေသည်}other{ပုံ # ပုံ မျှဝေနေသည်}}" "{count,plural, =1{ဗီဒီယို မျှဝေနေသည်}other{ဗီဒီယို # ခု မျှဝေနေသည်}}" "{count,plural, =1{# ဖိုင် မျှဝေနေသည်}other{# ဖိုင် မျှဝေနေသည်}}" - - + "မျှဝေမည့်အရာများ ရွေးခြင်း" "{count,plural, =1{စာသားပါသောပုံကို မျှဝေနေသည်}other{စာသားပါသောပုံ # ပုံကို မျှဝေနေသည်}}" "{count,plural, =1{လင့်ခ်ပါသောပုံကို မျှဝေနေသည်}other{လင့်ခ်ပါသောပုံ # ပုံကို မျှဝေနေသည်}}" "{count,plural, =1{စာသားပါသောဗီဒီယိုကို မျှဝေနေသည်}other{စာသားပါသောဗီဒီယို # ခုကို မျှဝေနေသည်}}" diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml index 66eb54a9..7a67bc34 100644 --- a/java/res/values-nb/strings.xml +++ b/java/res/values-nb/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deler bildet}other{Deler # bilder}}" "{count,plural, =1{Deler videoen}other{Deler # videoer}}" "{count,plural, =1{Deler # fil}other{Deler # filer}}" - - + "Velg elementene du vil dele" "{count,plural, =1{Deler bildet med tekst}other{Deler # bilder med tekst}}" "{count,plural, =1{Deler bildet med link}other{Deler # bilder med link}}" "{count,plural, =1{Deler videoen med tekst}other{Deler # videoer med tekst}}" diff --git a/java/res/values-ne/strings.xml b/java/res/values-ne/strings.xml index 22596863..76365455 100644 --- a/java/res/values-ne/strings.xml +++ b/java/res/values-ne/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{फोटो सेयर गरिँदै छ}other{# वटा फोटो सेयर गरिँदै छ}}" "{count,plural, =1{भिडियो सेयर गरिँदै छ}other{# वटा भिडियो सेयर गरिँदै छ}}" "{count,plural, =1{# वटा फाइल सेयर गरिँदै छ}other{# वटा फाइल सेयर गरिँदै छ}}" - - + "आफूले सेयर गर्न चाहेका सामग्री चयन गर्नुहोस्" "{count,plural, =1{टेक्स्ट भएको फोटो सेयर गरिँदै छ}other{टेक्स्ट भएका # वटा फोटो सेयर गरिँदै छन्}}" "{count,plural, =1{लिंक भएको फोटो सेयर गरिँदै छ}other{लिंक भएका # वटा फोटो सेयर गरिँदै छन्}}" "{count,plural, =1{टेक्स्ट भएको भिडियो सेयर गरिँदै छ}other{टेक्स्ट भएका # वटा भिडियो सेयर गरिँदै छन्}}" diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml index d1aee9cb..e452e98e 100644 --- a/java/res/values-nl/strings.xml +++ b/java/res/values-nl/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Afbeelding delen}other{# afbeeldingen delen}}" "{count,plural, =1{Video delen}other{# video\'s delen}}" "{count,plural, =1{# bestand delen}other{# bestanden delen}}" - - + "Items selecteren om te delen" "{count,plural, =1{Afbeelding met tekst wordt gedeeld}other{# afbeeldingen met tekst worden gedeeld}}" "{count,plural, =1{Afbeelding delen via link}other{# afbeeldingen delen via link}}" "{count,plural, =1{Video delen via tekstbericht}other{# video\'s delen via tekstbericht}}" diff --git a/java/res/values-or/strings.xml b/java/res/values-or/strings.xml index 0c1b03ff..0e2ece56 100644 --- a/java/res/values-or/strings.xml +++ b/java/res/values-or/strings.xml @@ -32,7 +32,7 @@ "ଏଡିଟ କରନ୍ତୁ" "ସେୟାର କରନ୍ତୁ" "%1$s ସହ ସେୟାର କରନ୍ତୁ" - "ସେୟାର୍‌ କରନ୍ତୁ" + "ସେୟାର କରନ୍ତୁ" "ଏହା ଜରିଆରେ ପଠାନ୍ତୁ" "%1$s ବ୍ୟବହାର କରି ପଠାନ୍ତୁ" "ପଠାନ୍ତୁ" @@ -60,8 +60,7 @@ "{count,plural, =1{ଇମେଜ ସେୟାର କରାଯାଉଛି}other{#ଟିି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଭିଡିଓ ସେୟାର କରାଯାଉଛି}other{#ଟି ଭିଡିଓ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{#ଟି ଫାଇଲ ସେୟାର କରାଯାଉଛି}other{#ଟି ଫାଇଲ ସେୟାର କରାଯାଉଛି}}" - - + "ସେୟାର କରିବା ପାଇଁ ଆଇଟମଗୁଡ଼ିକ ଚୟନ କରନ୍ତୁ" "{count,plural, =1{ଟେକ୍ସଟ ସହ ଇମେଜ ସେୟାର କରାଯାଉଛି}other{ଟେକ୍ସଟ ସହ #ଟି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଲିଙ୍କ ସହ ଇମେଜ ସେୟାର କରାଯାଉଛି}other{ଲିଙ୍କ ସହ #ଟି ଇମେଜ ସେୟାର କରାଯାଉଛି}}" "{count,plural, =1{ଟେକ୍ସଟ ସହ ଭିଡିଓ ସେୟାର କରାଯାଉଛି}other{ଟେକ୍ସଟ ସହ #ଟି ଭିଡିଓ ସେୟାର କରାଯାଉଛି}}" diff --git a/java/res/values-pa/strings.xml b/java/res/values-pa/strings.xml index b01de893..607f7d26 100644 --- a/java/res/values-pa/strings.xml +++ b/java/res/values-pa/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ਚਿੱਤਰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{# ਚਿੱਤਰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{# ਚਿੱਤਰ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" "{count,plural, =1{ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{# ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{# ਵੀਡੀਓ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" "{count,plural, =1{# ਫ਼ਾਈਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ}one{# ਫ਼ਾਈਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ}other{# ਫ਼ਾਈਲਾਂ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਜਾ ਰਹੀਆਂ ਹਨ}}" - - + "ਸਾਂਝਾ ਕਰਨ ਲਈ ਆਈਟਮਾਂ ਚੁਣੋ" "{count,plural, =1{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਚਿੱਤਰਾਂ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}}" "{count,plural, =1{ਲਿੰਕ ਨਾਲ ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿੰਕ ਨਾਲ # ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿੰਕ ਨਾਲ # ਚਿੱਤਰਾਂ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}}" "{count,plural, =1{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}one{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਵੀਡੀਓ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ}other{ਲਿਖਤ ਸੁਨੇਹੇ ਨਾਲ # ਵੀਡੀਓ ਸਾਂਝੇ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ}}" diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml index 78e6457c..10dda621 100644 --- a/java/res/values-pl/strings.xml +++ b/java/res/values-pl/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Udostępniam obraz}few{Udostępniam # obrazy}many{Udostępniam # obrazów}other{Udostępniam # obrazu}}" "{count,plural, =1{Udostępnianie filmu}few{Udostępnianie # filmów}many{Udostępnianie # filmów}other{Udostępnianie # filmu}}" "{count,plural, =1{Udostępnianie # pliku}few{Udostępnianie # plików}many{Udostępnianie # plików}other{Udostępnianie # pliku}}" - - + "Wybierz elementy do udostępnienia" "{count,plural, =1{Udostępnianie obrazu przez SMS}few{Udostępnianie # obrazów przez SMS}many{Udostępnianie # obrazów przez SMS}other{Udostępnianie # obrazu przez SMS}}" "{count,plural, =1{Udostępnianie obrazu przez link}few{Udostępnianie # obrazów przez link}many{Udostępnianie # obrazów przez link}other{Udostępnianie # obrazu przez link}}" "{count,plural, =1{Udostępnianie filmu przez SMS}few{Udostępnianie # filmów przez SMS}many{Udostępnianie # filmów przez SMS}other{Udostępnianie # filmu przez SMS}}" diff --git a/java/res/values-pt-rBR/strings.xml b/java/res/values-pt-rBR/strings.xml index 7c33d050..c8ce55a8 100644 --- a/java/res/values-pt-rBR/strings.xml +++ b/java/res/values-pt-rBR/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" - - + "Selecione os itens para compartilhar" "{count,plural, =1{Compartilhando imagem com texto}one{Compartilhando # imagem com texto}many{Compartilhando # de imagens com texto}other{Compartilhando # imagens com texto}}" "{count,plural, =1{Compartilhando imagem com link}one{Compartilhando # imagem com link}many{Compartilhando # de imagens com link}other{Compartilhando # imagens com link}}" "{count,plural, =1{Compartilhando vídeo com texto}one{Compartilhando # vídeo com texto}many{Compartilhando # de vídeos com texto}other{Compartilhando # vídeos com texto}}" diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml index 60abf642..ffcf9a1e 100644 --- a/java/res/values-pt-rPT/strings.xml +++ b/java/res/values-pt-rPT/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Partilhar imagem}many{Partilhar # imagens}other{Partilhar # imagens}}" "{count,plural, =1{A partilhar vídeo}many{A partilhar # vídeos}other{A partilhar # vídeos}}" "{count,plural, =1{A partilhar # ficheiro}many{A partilhar # ficheiros}other{A partilhar # ficheiros}}" - - + "Selecione itens para partilhar" "{count,plural, =1{A partilhar imagem com texto}many{A partilhar # imagens com texto}other{A partilhar # imagens com texto}}" "{count,plural, =1{A partilhar imagem com link}many{A partilhar # imagens com link}other{A partilhar # imagens com link}}" "{count,plural, =1{A partilhar vídeo com texto}many{A partilhar # vídeos com texto}other{A partilhar # vídeos com texto}}" diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml index 7c33d050..c8ce55a8 100644 --- a/java/res/values-pt/strings.xml +++ b/java/res/values-pt/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" - - + "Selecione os itens para compartilhar" "{count,plural, =1{Compartilhando imagem com texto}one{Compartilhando # imagem com texto}many{Compartilhando # de imagens com texto}other{Compartilhando # imagens com texto}}" "{count,plural, =1{Compartilhando imagem com link}one{Compartilhando # imagem com link}many{Compartilhando # de imagens com link}other{Compartilhando # imagens com link}}" "{count,plural, =1{Compartilhando vídeo com texto}one{Compartilhando # vídeo com texto}many{Compartilhando # de vídeos com texto}other{Compartilhando # vídeos com texto}}" diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml index e68e9b45..c2843bab 100644 --- a/java/res/values-ro/strings.xml +++ b/java/res/values-ro/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Se trimite imaginea}few{Se trimit # imagini}other{Se trimit # de imagini}}" "{count,plural, =1{Se trimite videoclipul}few{Se trimit # videoclipuri}other{Se trimit # de videoclipuri}}" "{count,plural, =1{Se trimite un fișier}few{Se trimit # fișiere}other{Se trimit # de fișiere}}" - - + "Selectează articole de trimis" "{count,plural, =1{Se trimite imaginea cu text}few{Se trimit # imagini cu text}other{Se trimit # de imagini cu text}}" "{count,plural, =1{Se trimite imaginea cu linkul}few{Se trimit # imagini cu linkul}other{Se trimit # de imagini cu linkul}}" "{count,plural, =1{Se trimite videoclipul cu text}few{Se trimit # videoclipuri cu text}other{Se trimit # de videoclipuri cu text}}" diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml index bf2f31c9..9b4c2d20 100644 --- a/java/res/values-ru/strings.xml +++ b/java/res/values-ru/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Отправка изображения}one{Отправка # изображения}few{Отправка # изображений}many{Отправка # изображений}other{Отправка # изображения}}" "{count,plural, =1{Отправка видео}one{Отправка # видео}few{Отправка # видео}many{Отправка # видео}other{Отправка # видео}}" "{count,plural, =1{Предоставляется доступ к # файлу}one{Предоставляется доступ к # файлу}few{Предоставляется доступ к # файлам}many{Предоставляется доступ к # файлам}other{Предоставляется доступ к # файла}}" - - + "Выберите объекты для отправки" "{count,plural, =1{Отправка изображения с текстом}one{Отправка # изображения с текстом}few{Отправка # изображений с текстом}many{Отправка # изображений с текстом}other{Отправка # изображения с текстом}}" "{count,plural, =1{Отправка изображения со ссылкой}one{Отправка # изображения со ссылкой}few{Отправка # изображений со ссылкой}many{Отправка # изображений со ссылкой}other{Отправка # изображения со ссылкой}}" "{count,plural, =1{Отправка видео с текстом}one{Отправка # видео с текстом}few{Отправка # видео с текстом}many{Отправка # видео с текстом}other{Отправка # видео с текстом}}" diff --git a/java/res/values-si/strings.xml b/java/res/values-si/strings.xml index cf890aca..1fc87e4d 100644 --- a/java/res/values-si/strings.xml +++ b/java/res/values-si/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{රූපය බෙදා ගැනීම}one{රූප #ක් බෙදා ගැනීම}other{රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{වීඩියෝව බෙදා ගැනීම}one{වීඩියෝ #ක් බෙදා ගැනීම}other{වීඩියෝ #ක් බෙදා ගැනීම}}" "{count,plural, =1{# ගොනුවක් බෙදා ගැනීම}one{ගොනු #ක් බෙදා ගැනීම}other{ගොනු #ක් බෙදා ගැනීම}}" - - + "බෙදා ගැනීමට අයිතම තෝරන්න" "{count,plural, =1{පෙළ සමග රූපය බෙදා ගැනීම}one{පෙළ සමග රූප #ක් බෙදා ගැනීම}other{පෙළ සමග රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{සබැඳිය සමග රූපය බෙදා ගැනීම}one{සබැඳිය සමග රූප #ක් බෙදා ගැනීම}other{සබැඳිය සමග රූප #ක් බෙදා ගැනීම}}" "{count,plural, =1{පෙළ සමග වීඩියෝව බෙදා ගැනීම}one{පෙළ සමග වීඩියෝ #ක් බෙදා ගැනීම}other{පෙළ සමග වීඩියෝ #ක් බෙදා ගැනීම}}" diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml index c5fb9984..9119aaa0 100644 --- a/java/res/values-sk/strings.xml +++ b/java/res/values-sk/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Zdieľanie obrázku}few{Zdieľanie # obrázkov}many{Sharing # images}other{Zdieľanie # obrázkov}}" "{count,plural, =1{Zdieľa sa video}few{Zdieľajú sa # videá}many{Sharing # videos}other{Zdieľa sa # videí}}" "{count,plural, =1{Zdieľa sa # súbor}few{Zdieľajú sa # súbory}many{Sharing # files}other{Zdieľa sa # súborov}}" - - + "Vyberte položky na zdieľanie" "{count,plural, =1{Zdieľa sa obrázok s textom}few{Zdieľajú sa # obrázky s textom}many{Sharing # images with text}other{Zdieľa sa # obrázkov s textom}}" "{count,plural, =1{Zdieľa sa obrázok s odkazom}few{Zdieľajú sa # obrázky s odkazom}many{Sharing # images with link}other{Zdieľa sa # obrázkov s odkazom}}" "{count,plural, =1{Zdieľa sa video s textom}few{Zdieľajú sa # videá s textom}many{Sharing # videos with text}other{Zdieľa sa # videí s textom}}" diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml index b7685e05..78e07ad1 100644 --- a/java/res/values-sl/strings.xml +++ b/java/res/values-sl/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Deljenje slike}one{Deljenje # slike}two{Deljenje # slik}few{Deljenje # slik}other{Deljenje # slik}}" "{count,plural, =1{Deljenje videoposnetka}one{Deljenje # videoposnetka}two{Deljenje # videoposnetkov}few{Deljenje # videoposnetkov}other{Deljenje # videoposnetkov}}" "{count,plural, =1{Deljenje # datoteke}one{Deljenje # datoteke}two{Deljenje # datotek}few{Deljenje # datotek}other{Deljenje # datotek}}" - - + "Izbira elementov za deljenje" "{count,plural, =1{Deljenje slike z besedilom}one{Deljenje # slike z besedilom}two{Deljenje # slik z besedilom}few{Deljenje # slik z besedilom}other{Deljenje # slik z besedilom}}" "{count,plural, =1{Deljenje slike s povezavo}one{Deljenje # slike s povezavo}two{Deljenje # slik s povezavo}few{Deljenje # slik s povezavo}other{Deljenje # slik s povezavo}}" "{count,plural, =1{Deljenje videoposnetka z besedilom}one{Deljenje # videoposnetka z besedilom}two{Deljenje # videoposnetkov z besedilom}few{Deljenje # videoposnetkov z besedilom}other{Deljenje # videoposnetkov z besedilom}}" diff --git a/java/res/values-sq/strings.xml b/java/res/values-sq/strings.xml index a661aecc..374b2e0a 100644 --- a/java/res/values-sq/strings.xml +++ b/java/res/values-sq/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Po ndahet imazh}other{Po ndahen # imazhe}}" "{count,plural, =1{Po ndahet videoja}other{Po ndahen # video}}" "{count,plural, =1{Po ndahet # skedar}other{Po ndahen # skedarë}}" - - + "Zgjidh artikujt për t\'i ndarë" "{count,plural, =1{Po ndahet një imazh me tekst}other{Po ndahen # imazhe me tekst}}" "{count,plural, =1{Po ndahet një imazh me lidhje}other{Po ndahen # imazhe me lidhje}}" "{count,plural, =1{Po ndahet një video me tekst}other{Po ndahen # video me tekst}}" diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml index 70310f44..8e7c57d1 100644 --- a/java/res/values-sr/strings.xml +++ b/java/res/values-sr/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Дељење слике}one{Дељење # слике}few{Дељење # слике}other{Дељење # слика}}" "{count,plural, =1{Дели се видео}one{Дели се # видео}few{Деле се # видео снимка}other{Дели се # видеа}}" "{count,plural, =1{Дели се # фајл}one{Дели се # фајл}few{Деле се # фајла}other{Дели се # фајлова}}" - - + "Изаберите ставке за дељење" "{count,plural, =1{Дели се слика са текстом}one{Дели се # слика са текстом}few{Деле се # слике са текстом}other{Дели се # слика са текстом}}" "{count,plural, =1{Дели се слика са линком}one{Дели се # слика са линком}few{Деле се # слике са линком}other{Дели се # слика са линком}}" "{count,plural, =1{Дели се видео са текстом}one{Дели се # видео са текстом}few{Деле се # видео снимка са текстом}other{Дели се # видеа са текстом}}" diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml index 02807b03..a21af078 100644 --- a/java/res/values-sv/strings.xml +++ b/java/res/values-sv/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Delar bild}other{Delar # bilder}}" "{count,plural, =1{Delar video}other{Delar # videor}}" "{count,plural, =1{Delar # fil}other{Delar # filer}}" - - + "Välj objekt att dela" "{count,plural, =1{Delar bild med text}other{Delar # bilder med text}}" "{count,plural, =1{Delar bild med länk}other{Delar # bilder med länk}}" "{count,plural, =1{Delar video med text}other{Delar # videor med text}}" diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml index b7814f7b..2f63e887 100644 --- a/java/res/values-sw/strings.xml +++ b/java/res/values-sw/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Inashiriki picha}other{Inashiriki picha #}}" "{count,plural, =1{Inashiriki video}other{Inashiriki video #}}" "{count,plural, =1{Inashiriki faili #}other{Inashiriki faili #}}" - - + "Chagua vipengee vya kutuma" "{count,plural, =1{Inashiriki picha na maandishi}other{Inashiriki picha # na maandishi}}" "{count,plural, =1{Inashiriki picha na kiungo}other{Inashiriki picha # na kiungo}}" "{count,plural, =1{Inashiriki video na maandishi}other{Inashiriki video # na maandishi}}" diff --git a/java/res/values-ta/strings.xml b/java/res/values-ta/strings.xml index 301efaf0..f1df5cba 100644 --- a/java/res/values-ta/strings.xml +++ b/java/res/values-ta/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{படத்தைப் பகிர்கிறது}other{# படங்களைப் பகிர்கிறது}}" "{count,plural, =1{வீடியோவைப் பகிர்கிறது}other{# வீடியோக்களை பகிர்கிறது}}" "{count,plural, =1{# ஃபைலைப் பகிர்கிறது}other{# ஃபைல்களைப் பகிர்கிறது}}" - - + "பகிர விரும்புபவற்றைத் தேர்ந்தெடுத்தல்" "{count,plural, =1{வார்த்தைகளுடன் படத்தைப் பகிர்கிறது}other{வார்த்தைகளுடன் # படங்களைப் பகிர்கிறது}}" "{count,plural, =1{இணைப்பைக் கொண்ட படத்தைப் பகிர்கிறது}other{இணைப்பைக் கொண்ட # படங்களைப் பகிர்கிறது}}" "{count,plural, =1{வார்த்தைகளைக் கொண்ட வீடியோவைப் பகிர்கிறது}other{வார்த்தைகளைக் கொண்ட # வீடியோக்களைப் பகிர்கிறது}}" diff --git a/java/res/values-te/strings.xml b/java/res/values-te/strings.xml index d0490613..b88d7d4e 100644 --- a/java/res/values-te/strings.xml +++ b/java/res/values-te/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{ఈ ఇమేజ్‌ను షేర్ చేస్తున్నారు}other{ఈ # ఇమేజ్‌లను షేర్ చేస్తున్నారు}}" "{count,plural, =1{వీడియోను షేర్ చేయడం}other{# వీడియోలను షేర్ చేయడం}}" "{count,plural, =1{# ఫైల్‌ను షేర్ చేస్తోంది}other{# ఫైళ్లను షేర్ చేస్తోంది}}" - - + "షేర్ చేయడానికి ఐటెమ్‌లను ఎంచుకోండి" "{count,plural, =1{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా ఇమేజ్‌ను షేర్ చేయడం}other{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా # ఇమేజ్‌లను షేర్ చేయడం}}" "{count,plural, =1{లింక్ చేయడం ద్వారా ఇమేజ్‌ను షేర్ చేయడం}other{లింక్ చేయడం ద్వారా # ఇమేజ్‌లను షేర్ చేయడం}}" "{count,plural, =1{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా వీడియోను షేర్ చేయడం}other{టెక్స్ట్ మెసేజ్ పంపడం ద్వారా # వీడియోలను షేర్ చేయడం}}" diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml index 7616b31c..5effd16c 100644 --- a/java/res/values-th/strings.xml +++ b/java/res/values-th/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{กำลังแชร์รูปภาพ}other{กำลังแชร์รูปภาพ # รายการ}}" "{count,plural, =1{กำลังแชร์วิดีโอ}other{กำลังแชร์วิดีโอ # รายการ}}" "{count,plural, =1{กำลังจะแชร์ # ไฟล์}other{กำลังจะแชร์ # ไฟล์}}" - - + "เลือกรายการที่จะแชร์" "{count,plural, =1{กำลังแชร์รูปภาพพร้อมข้อความ}other{กำลังแชร์รูปภาพ # รายการพร้อมข้อความ}}" "{count,plural, =1{กำลังแชร์รูปภาพพร้อมลิงก์}other{กำลังแชร์รูปภาพ # รายการพร้อมลิงก์}}" "{count,plural, =1{กำลังแชร์วิดีโอพร้อมข้อความ}other{กำลังแชร์วิดีโอ # รายการพร้อมข้อความ}}" diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml index 708be5e8..67782253 100644 --- a/java/res/values-tl/strings.xml +++ b/java/res/values-tl/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Shine-share ang larawan}one{Shine-share ang # larawan}other{Shine-share ang # na larawan}}" "{count,plural, =1{Ibinabahagi ang video}one{Ibinabahagi ang # video}other{Ibinabahagi ang # na video}}" "{count,plural, =1{Nagshe-share ng # file}one{Nagshe-share ng # file}other{Nagshe-share ng # na file}}" - - + "Pumili ng mga item na ibabahagi" "{count,plural, =1{Nagbabahagi ng larawang may text}one{Nagbabahagi ng # larawang may text}other{Nagbabahagi ng # na larawang may text}}" "{count,plural, =1{Nagbabahagi ng larawang may link}one{Nagbabahagi ng # larawang may link}other{Nagbabahagi ng # na larawang may link}}" "{count,plural, =1{Nagbabahagi ng video na may text}one{Nagbabahagi ng # video na may text}other{Nagbabahagi ng # na video na may text}}" diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml index a633d62e..5dee9296 100644 --- a/java/res/values-tr/strings.xml +++ b/java/res/values-tr/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Resim paylaşılıyor}other{# resim paylaşılıyor}}" "{count,plural, =1{Video paylaşılıyor}other{# video paylaşılıyor}}" "{count,plural, =1{# dosya paylaşılıyor}other{# dosya paylaşılıyor}}" - - + "Paylaşılacak öğeleri seçin" "{count,plural, =1{Metin ekli resim paylaşılıyor}other{Metin ekli # resim paylaşılıyor}}" "{count,plural, =1{Bağlantı ekli resim paylaşılıyor}other{Bağlantı ekli # resim paylaşılıyor}}" "{count,plural, =1{Metin ekli video paylaşılıyor}other{Metin ekli # video paylaşılıyor}}" diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml index a694ee77..e7392852 100644 --- a/java/res/values-uk/strings.xml +++ b/java/res/values-uk/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Надсилається зображення}one{Надсилається # зображення}few{Надсилаються # зображення}many{Надсилаються # зображень}other{Надсилається # зображення}}" "{count,plural, =1{Надсилається відео}one{Надсилається # відео}few{Надсилаються # відео}many{Надсилаються # відео}other{Надсилається # відео}}" "{count,plural, =1{Надсилається # файл}one{Надсилається # файл}few{Надсилаються # файли}many{Надсилаються # файлів}other{Надсилається # файлу}}" - - + "Виберіть об’єкти, якими хочете поділитися" "{count,plural, =1{Надсилання зображення з текстом}one{Надсилання # зображення з текстом}few{Надсилання # зображень із текстом}many{Надсилання # зображень із текстом}other{Надсилання # зображення з текстом}}" "{count,plural, =1{Надсилання зображення з посиланням}one{Надсилання # зображення з посиланням}few{Надсилання # зображень із посиланням}many{Надсилання # зображень із посиланням}other{Надсилання # зображення з посиланням}}" "{count,plural, =1{Надсилання відео з текстом}one{Надсилання # відео з текстом}few{Надсилання # відео з текстом}many{Надсилання # відео з текстом}other{Надсилання # відео з текстом}}" diff --git a/java/res/values-ur/strings.xml b/java/res/values-ur/strings.xml index dfdbc4c3..9ecc8443 100644 --- a/java/res/values-ur/strings.xml +++ b/java/res/values-ur/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{تصویر کا اشتراک کیا جا رہا ہے}other{# تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{ویڈیو کا اشتراک کیا جا رہا ہے}other{# ویڈیوز کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{# فائل کا اشتراک کیا جا رہا ہے}other{# فائلز کا اشتراک کیا جا رہا ہے}}" - - + "اشتراک کرنے کے لیے آئٹمز منتخب کریں" "{count,plural, =1{ٹیکسٹ کے ساتھ تصویر کا اشتراک کیا جا رہا ہے}other{ٹیکسٹ کے ساتھ # تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{لنک کے ساتھ تصویر کا اشتراک کیا جا رہا ہے}other{لنک کے ساتھ # تصاویر کا اشتراک کیا جا رہا ہے}}" "{count,plural, =1{ٹیکسٹ کے ساتھ ویڈیو کا اشتراک کیا جا رہا ہے}other{ٹیکسٹ کے ساتھ # ویڈیوز کا اشتراک کیا جا رہا ہے}}" diff --git a/java/res/values-uz/strings.xml b/java/res/values-uz/strings.xml index 21aea564..f9434b18 100644 --- a/java/res/values-uz/strings.xml +++ b/java/res/values-uz/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Rasm ulashilmoqda}other{# ta rasm ulashilmoqda}}" "{count,plural, =1{Video ulashilmoqda}other{# ta video ulashilmoqda}}" "{count,plural, =1{# ta fayl ulashilmoqda}other{# ta fayl ulashilmoqda}}" - - + "Ulashish uchun elementlarni tanlang" "{count,plural, =1{Matnli havolani yuborish}other{# ta matnli havolani yuborish}}" "{count,plural, =1{Havolali rasmni yuborish}other{# ta havolali rasmni yuborish}}" "{count,plural, =1{Matnli videoni yuborish}other{# ta matnli videoni yuborish}}" diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml index 170be22f..eb789f5e 100644 --- a/java/res/values-vi/strings.xml +++ b/java/res/values-vi/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Chia sẻ hình ảnh}other{Chia sẻ # hình ảnh}}" "{count,plural, =1{Đang chia sẻ video}other{Đang chia sẻ # video}}" "{count,plural, =1{Đang chia sẻ # tệp}other{Đang chia sẻ # tệp}}" - - + "Chọn mục muốn chia sẻ" "{count,plural, =1{Đang chia sẻ hình ảnh có văn bản}other{Đang chia sẻ # hình ảnh có văn bản}}" "{count,plural, =1{Đang chia sẻ hình ảnh có đường liên kết}other{Đang chia sẻ # hình ảnh có đường liên kết}}" "{count,plural, =1{Đang chia sẻ video có văn bản}other{Đang chia sẻ # video có văn bản}}" diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml index aed7679f..a2401ce7 100644 --- a/java/res/values-zh-rCN/strings.xml +++ b/java/res/values-zh-rCN/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{分享图片}other{分享 # 张图片}}" "{count,plural, =1{正在分享视频}other{正在分享 # 个视频}}" "{count,plural, =1{正在分享 # 个文件}other{正在分享 # 个文件}}" - - + "选择要分享的内容" "{count,plural, =1{正在分享带有文本的图片}other{正在分享带有文本的 # 个图片}}" "{count,plural, =1{正在分享带有链接的图片}other{正在分享带有链接的 # 个图片}}" "{count,plural, =1{正在分享带有文本的视频}other{正在分享带有文本的 # 个视频}}" diff --git a/java/res/values-zh-rHK/strings.xml b/java/res/values-zh-rHK/strings.xml index c4360bc0..54a61c7e 100644 --- a/java/res/values-zh-rHK/strings.xml +++ b/java/res/values-zh-rHK/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{分享圖片}other{分享 # 張圖片}}" "{count,plural, =1{正在分享影片}other{正在分享 # 部影片}}" "{count,plural, =1{正在分享 # 個檔案}other{正在分享 # 個檔案}}" - - + "選取要分享的項目" "{count,plural, =1{正在分享圖片 (含有文字)}other{正在分享 # 張圖片 (含有文字)}}" "{count,plural, =1{正在分享圖片 (含有連結)}other{正在分享 # 張圖片 (含有連結)}}" "{count,plural, =1{正在分享影片 (含有文字)}other{正在分享 # 部影片 (含有文字)}}" diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml index 5fdeff86..0d369318 100644 --- a/java/res/values-zh-rTW/strings.xml +++ b/java/res/values-zh-rTW/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{分享圖片}other{分享 # 張圖片}}" "{count,plural, =1{正在分享影片}other{正在分享 # 部影片}}" "{count,plural, =1{正在分享 # 個檔案}other{正在分享 # 個檔案}}" - - + "選取要分享的項目" "{count,plural, =1{分享含有文字的圖片}other{分享 # 張含有文字的圖片}}" "{count,plural, =1{分享含有連結的圖片}other{分享 # 張含有連結的圖片}}" "{count,plural, =1{分享含有文字的影片}other{分享 # 部含有文字的影片}}" diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml index 3697df56..9d6d13dc 100644 --- a/java/res/values-zu/strings.xml +++ b/java/res/values-zu/strings.xml @@ -60,8 +60,7 @@ "{count,plural, =1{Yabelana ngomfanekiso}one{Yabelana ngemifanekiso engu-#}other{Yabelana ngemifanekiso engu-#}}" "{count,plural, =1{Yabelana ngevidiyo}one{Yabelana ngamavidiyo angu-#}other{Yabelana ngamavidiyo angu-#}}" "{count,plural, =1{Yabelana ngefayela eli-#}one{Yabelana ngamafayela angu-#}other{Yabelana ngamafayela angu-#}}" - - + "Khetha izinto ongabelana ngazo" "{count,plural, =1{Yabelana ngomfanekiso ngombhalo}one{Yabelana ngemifanekiso engu-# ngombhalo}other{Yabelana ngemifanekiso engu-# ngombhalo}}" "{count,plural, =1{Yabelana ngomfanekiso ngelinki}one{Yabelana ngemifanekiso engu-# ngelinki}other{Yabelana ngemifanekiso engu-# ngelinki}}" "{count,plural, =1{Yabelana ngevidiyo ngombhalo}one{Yabelana ngamavidiyo angu-# ngombhalo}other{Yabelana ngamavidiyo angu-# ngombhalo}}" -- cgit v1.2.3-59-g8ed1b From e277a3f5330ad3bcd8ed6ffb6cba73b39d30c093 Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Mon, 19 Aug 2024 16:06:35 -0700 Subject: Report Shareousel preview type Bug: 329068612 Test: atest IntentResolver-tests-unit Flag: EXEMPT ui events logging Change-Id: I0a9cd4d50f4a883c162be153c9e0a0a8f10816c2 --- .../contentpreview/ShareouselContentPreviewUi.kt | 2 +- .../intentresolver/logging/EventLogImpl.java | 3 ++ .../contentpreview/ChooserContentPreviewUiTest.kt | 2 ++ .../intentresolver/logging/EventLogImplTest.java | 39 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt b/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt index 57a51239..ff52556a 100644 --- a/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt +++ b/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt @@ -39,7 +39,7 @@ import kotlinx.coroutines.launch @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) class ShareouselContentPreviewUi : ContentPreviewUi() { - override fun getType(): Int = ContentPreviewType.CONTENT_PREVIEW_IMAGE + override fun getType(): Int = ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION override fun display( resources: Resources, diff --git a/java/src/com/android/intentresolver/logging/EventLogImpl.java b/java/src/com/android/intentresolver/logging/EventLogImpl.java index 39d23865..8d04dd28 100644 --- a/java/src/com/android/intentresolver/logging/EventLogImpl.java +++ b/java/src/com/android/intentresolver/logging/EventLogImpl.java @@ -402,6 +402,9 @@ public class EventLogImpl implements EventLog { case ContentPreviewType.CONTENT_PREVIEW_FILE: return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE; case ContentPreviewType.CONTENT_PREVIEW_TEXT: + case ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION: + return FrameworkStatsLog + .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TOGGLEABLE_MEDIA; default: return FrameworkStatsLog .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TYPE_UNKNOWN; diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index a80cc02b..905c8517 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -157,5 +157,7 @@ class ChooserContentPreviewUiTest { ) assertThat(testSubject.mContentPreviewUi) .isInstanceOf(ShareouselContentPreviewUi::class.java) + assertThat(testSubject.preferredContentPreview) + .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION) } } diff --git a/tests/unit/src/com/android/intentresolver/logging/EventLogImplTest.java b/tests/unit/src/com/android/intentresolver/logging/EventLogImplTest.java index feb277ea..528c4613 100644 --- a/tests/unit/src/com/android/intentresolver/logging/EventLogImplTest.java +++ b/tests/unit/src/com/android/intentresolver/logging/EventLogImplTest.java @@ -151,6 +151,45 @@ public final class EventLogImplTest { /* reselection action provided */ eq(modifyShareProvided)); } + @Test + public void shareStartedWithShareouselAndEnabledReportingFlag_imagePreviewTypeReported() { + final String packageName = "com.test.foo"; + final String mimeType = "text/plain"; + final int appProvidedDirectTargets = 123; + final int appProvidedAppTargets = 456; + final boolean workProfile = true; + final int previewType = ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION; + final String intentAction = Intent.ACTION_SENDTO; + final int numCustomActions = 3; + final boolean modifyShareProvided = true; + + mChooserLogger.logShareStarted( + packageName, + mimeType, + appProvidedDirectTargets, + appProvidedAppTargets, + workProfile, + previewType, + intentAction, + numCustomActions, + modifyShareProvided); + + verify(mFrameworkLog).write( + eq(FrameworkStatsLog.SHARESHEET_STARTED), + eq(SharesheetStartedEvent.SHARE_STARTED.getId()), + eq(packageName), + /* instanceId=*/ gt(0), + eq(mimeType), + eq(appProvidedDirectTargets), + eq(appProvidedAppTargets), + eq(workProfile), + eq(FrameworkStatsLog + .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TOGGLEABLE_MEDIA), + eq(FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO), + /* custom actions provided */ eq(numCustomActions), + /* reselection action provided */ eq(modifyShareProvided)); + } + @Test public void testLogShareTargetSelected() { final int targetType = EventLogImpl.SELECTION_TYPE_SERVICE; -- cgit v1.2.3-59-g8ed1b From f760ae087ee9f3dae6b7533d5d4c2525103fd1cf Mon Sep 17 00:00:00 2001 From: Andrey Yepin Date: Mon, 19 Aug 2024 21:23:31 -0700 Subject: Log payload selection changes Bug: 329068612 Test: manual testing with an injected debug logging Test: atest IntentResolver-tests-unit Flag: EXEMPT ui events logging Change-Id: Iac9e2bcf93375d5761be5287b6cf9297be399e77 --- .../domain/interactor/SelectablePreviewInteractor.kt | 3 +++ .../domain/interactor/SelectablePreviewsInteractor.kt | 4 +++- java/src/com/android/intentresolver/logging/EventLog.kt | 15 +++++++++++++++ .../com/android/intentresolver/logging/EventLogImpl.java | 5 +++++ .../domain/interactor/PayloadToggleInteractorKosmos.kt | 1 + .../com/android/intentresolver/logging/FakeEventLog.kt | 9 +++++++++ .../domain/interactor/SelectablePreviewInteractorTest.kt | 6 ++++++ 7 files changed, 42 insertions(+), 1 deletion(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractor.kt index d52a71a1..8f18ebe0 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractor.kt @@ -18,6 +18,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interacto import android.net.Uri import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel +import com.android.intentresolver.logging.EventLog import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.map class SelectablePreviewInteractor( private val key: PreviewModel, private val selectionInteractor: SelectionInteractor, + private val eventLog: EventLog, ) { val uri: Uri = key.uri @@ -33,6 +35,7 @@ class SelectablePreviewInteractor( /** Sets whether this preview is selected by the user. */ fun setSelected(isSelected: Boolean) { + eventLog.logPayloadSelectionChanged() if (isSelected) { selectionInteractor.select(key) } else { diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractor.kt index a578d0e2..d0ac8d4a 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractor.kt @@ -19,6 +19,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interacto import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.CursorPreviewsRepository import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel +import com.android.intentresolver.logging.EventLog import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -27,6 +28,7 @@ class SelectablePreviewsInteractor constructor( private val previewsRepo: CursorPreviewsRepository, private val selectionInteractor: SelectionInteractor, + private val eventLog: EventLog, ) { /** Keys of previews available for display in Shareousel. */ val previews: Flow @@ -36,5 +38,5 @@ constructor( * Returns a [SelectablePreviewInteractor] that can be used to interact with the individual * preview associated with [key]. */ - fun preview(key: PreviewModel) = SelectablePreviewInteractor(key, selectionInteractor) + fun preview(key: PreviewModel) = SelectablePreviewInteractor(key, selectionInteractor, eventLog) } diff --git a/java/src/com/android/intentresolver/logging/EventLog.kt b/java/src/com/android/intentresolver/logging/EventLog.kt index 476bd4bf..b92f0732 100644 --- a/java/src/com/android/intentresolver/logging/EventLog.kt +++ b/java/src/com/android/intentresolver/logging/EventLog.kt @@ -47,6 +47,7 @@ interface EventLog { ) fun logCustomActionSelected(positionPicked: Int) + fun logShareTargetSelected( targetType: Int, packageName: String?, @@ -60,15 +61,29 @@ interface EventLog { ) fun logDirectShareTargetReceived(category: Int, latency: Int) + fun logActionShareWithPreview(previewType: Int) + fun logActionSelected(targetType: Int) + fun logContentPreviewWarning(uri: Uri?) + fun logSharesheetTriggered() + fun logSharesheetAppLoadComplete() + fun logSharesheetDirectLoadComplete() + fun logSharesheetDirectLoadTimeout() + fun logSharesheetProfileChanged() + fun logSharesheetExpansionChanged(isCollapsed: Boolean) + fun logSharesheetAppShareRankingTimeout() + fun logSharesheetEmptyDirectShareRow() + + /** Log payload selection */ + fun logPayloadSelectionChanged() } diff --git a/java/src/com/android/intentresolver/logging/EventLogImpl.java b/java/src/com/android/intentresolver/logging/EventLogImpl.java index 8d04dd28..8e9543bc 100644 --- a/java/src/com/android/intentresolver/logging/EventLogImpl.java +++ b/java/src/com/android/intentresolver/logging/EventLogImpl.java @@ -273,6 +273,11 @@ public class EventLogImpl implements EventLog { log(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW, mInstanceId); } + @Override + public void logPayloadSelectionChanged() { + log(SharesheetStandardEvent.SHARESHEET_PAYLOAD_TOGGLED, mInstanceId); + } + /** * Logs a UiEventReported event for a given share activity * @param event diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt index cb88cd9e..7cca414f 100644 --- a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt +++ b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt @@ -91,6 +91,7 @@ val Kosmos.selectablePreviewsInteractor SelectablePreviewsInteractor( previewsRepo = cursorPreviewsRepository, selectionInteractor = selectionInteractor, + eventLog = eventLog, ) val Kosmos.selectionInteractor diff --git a/tests/shared/src/com/android/intentresolver/logging/FakeEventLog.kt b/tests/shared/src/com/android/intentresolver/logging/FakeEventLog.kt index 9ed47db6..c2d13f1e 100644 --- a/tests/shared/src/com/android/intentresolver/logging/FakeEventLog.kt +++ b/tests/shared/src/com/android/intentresolver/logging/FakeEventLog.kt @@ -164,14 +164,22 @@ class FakeEventLog @Inject constructor(private val instanceId: InstanceId) : Eve log { "logSharesheetEmptyDirectShareRow()" } } + override fun logPayloadSelectionChanged() { + log { "logPayloadSelectionChanged" } + } + data class ActionSelected(val targetType: Int) + data class CustomActionSelected(val positionPicked: Int) + data class ActionShareWithPreview(val previewType: Int) + data class ChooserActivityShown( val isWorkProfile: Boolean, val targetMimeType: String?, val systemCost: Long ) + data class ShareStarted( val packageName: String?, val mimeType: String?, @@ -183,6 +191,7 @@ class FakeEventLog @Inject constructor(private val instanceId: InstanceId) : Eve val customActionCount: Int, val modifyShareActionProvided: Boolean ) + data class ShareTargetSelected( val targetType: Int, val packageName: String?, diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt index f329b8a7..5d9ddbb6 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt @@ -26,13 +26,16 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.Tar import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.targetIntentModifier import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel import com.android.intentresolver.data.repository.chooserRequestRepository +import com.android.intentresolver.logging.FakeEventLog import com.android.intentresolver.util.runKosmosTest +import com.android.internal.logging.InstanceId import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import org.junit.Test class SelectablePreviewInteractorTest { + private val eventLog = FakeEventLog(InstanceId.fakeInstanceId(0)) @Test fun reflectPreviewRepo_initState() = runKosmosTest { @@ -46,6 +49,7 @@ class SelectablePreviewInteractorTest { order = 0, ), selectionInteractor = selectionInteractor, + eventLog = eventLog, ) runCurrent() @@ -64,6 +68,7 @@ class SelectablePreviewInteractorTest { order = 0, ), selectionInteractor = selectionInteractor, + eventLog = eventLog, ) assertThat(underTest.isSelected.first()).isFalse() @@ -93,6 +98,7 @@ class SelectablePreviewInteractorTest { order = 0, ), selectionInteractor = selectionInteractor, + eventLog = eventLog, ) underTest.setSelected(true) -- cgit v1.2.3-59-g8ed1b From 4d1932e5c13e00d5a9bb7453927479acc936d4fc Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 10 Sep 2024 15:39:36 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I44752e030d10f135671046403fc867bece2de6c1 --- java/res/values-fa/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index ade41874..7b3dc6ea 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -71,9 +71,9 @@ "{count,plural, =1{فقط تصویر}one{فقط تصویر}other{فقط تصویر}}" "{count,plural, =1{فقط ویدیو}one{فقط ویدیو}other{فقط ویدیو}}" "{count,plural, =1{فقط فایل}one{فقط فایل}other{فقط فایل}}" - "تصویر کوچک پیش‌نمای تصویر" - "تصویر کوچک پیش‌نمای ویدیو" - "تصویر کوچک پیش‌نمای فایل" + "ریزعکس پیش‌نمای تصویر" + "ریزعکس پیش‌نمای ویدیو" + "ریزعکس پیش‌نمای فایل" "هیچ فردی که با او هم‌رسانی کنید توصیه نشده است" "‏مجوز ضبط به این برنامه داده نشده است اما می‌تواند صدا را ازطریق این دستگاه USB ضبط کند." "شخصی" -- cgit v1.2.3-59-g8ed1b From 65940e4509b44828acc131401d7f8e43ddc02c1f Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 18 Sep 2024 17:18:01 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I4585376ea7bb4b45f668ab1debd5bfe5773ad660 --- java/res/values-kn/strings.xml | 4 ++-- java/res/values-uk/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/java/res/values-kn/strings.xml b/java/res/values-kn/strings.xml index 1143ac8f..35bf148c 100644 --- a/java/res/values-kn/strings.xml +++ b/java/res/values-kn/strings.xml @@ -45,8 +45,8 @@ "ಬೇರೊಂದು ಆ್ಯಪ್ ಬಳಸಿ" "ಕ್ರಿಯೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ" "ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಈ ಕ್ರಿಯೆಗಾಗಿ ಬದ್ಧತೆ ತೋರಿಸುವುದಿಲ್ಲ." - "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನ ಹೊರಗೆ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" - "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" + "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನ ಹೊರಗೆ ನೀವು ಈ ಆ್ಯಪ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" + "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ನೀವು ಈ ಆ್ಯಪ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" "ಯಾವಾಗಲೂ" "ಒಮ್ಮೆ ಮಾತ್ರ" "%1$s ಉದ್ಯೋಗದ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ" diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml index e7392852..293696fd 100644 --- a/java/res/values-uk/strings.xml +++ b/java/res/values-uk/strings.xml @@ -57,7 +57,7 @@ "{count,plural, =1{і ще # файл}one{і ще # файл}few{і ще # файли}many{і ще # файлів}other{і ще # файлу}}" "Надсилається текст" "Надсилається посилання" - "{count,plural, =1{Надсилається зображення}one{Надсилається # зображення}few{Надсилаються # зображення}many{Надсилаються # зображень}other{Надсилається # зображення}}" + "{count,plural, =1{Надсилання зображення}one{Надсилання # зображення}few{Надсилання # зображень}many{Надсилання # зображень}other{Надсилання # зображення}}" "{count,plural, =1{Надсилається відео}one{Надсилається # відео}few{Надсилаються # відео}many{Надсилаються # відео}other{Надсилається # відео}}" "{count,plural, =1{Надсилається # файл}one{Надсилається # файл}few{Надсилаються # файли}many{Надсилаються # файлів}other{Надсилається # файлу}}" "Виберіть об’єкти, якими хочете поділитися" -- cgit v1.2.3-59-g8ed1b From 6952e28dff44a02244f104aae45f9d0e6fa9166f Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 18 Sep 2024 19:55:15 -0700 Subject: Replace atomicfu with AtomicReference atomicfu internally replaces it with AtomicReference during compile This avoids an additional preprocessing step during compile, and also makes the gradle setup easier Bug: 368177559 Test: Presubmit Flag: EXEMPT refactor Change-Id: I6840e0687b78b38df7ac5d187bf147e0c5a33e24 --- .../intentresolver/shortcuts/ShortcutLoader.kt | 38 +++++++++--------- .../contentpreview/PreviewImageLoaderTest.kt | 45 +++++++++++----------- 2 files changed, 41 insertions(+), 42 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt index 1fedee30..828d8561 100644 --- a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt +++ b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt @@ -41,8 +41,8 @@ import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.measurements.Tracer import com.android.intentresolver.measurements.runTracing import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicReference import java.util.function.Consumer -import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart @@ -78,20 +78,20 @@ constructor( private val isPersonalProfile: Boolean, private val targetIntentFilter: IntentFilter?, private val dispatcher: CoroutineDispatcher, - private val callback: Consumer + private val callback: Consumer, ) { private val scope = if (fixShortcutLoaderJobLeak()) parentScope.createChildScope() else parentScope private val shortcutToChooserTargetConverter = ShortcutToChooserTargetConverter() private val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager - private val appPredictorWatchdog = atomic(null) + private val appPredictorWatchdog = AtomicReference(null) private val appPredictorCallback = ScopedAppTargetListCallback(scope) { onAppPredictorCallback(it) }.toAppPredictorCallback() private val appTargetSource = MutableSharedFlow?>( replay = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST + onBufferOverflow = BufferOverflow.DROP_OLDEST, ) private val shortcutSource = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) @@ -108,7 +108,7 @@ constructor( appPredictor: AppPredictor?, userHandle: UserHandle, targetIntentFilter: IntentFilter?, - callback: Consumer + callback: Consumer, ) : this( context, scope, @@ -117,7 +117,7 @@ constructor( userHandle == UserHandle.of(ActivityManager.getCurrentUser()), targetIntentFilter, Dispatchers.IO, - callback + callback, ) init { @@ -134,7 +134,7 @@ constructor( appTargets, shortcutData.shortcuts, shortcutData.isFromAppPredictor, - shortcutData.appPredictorTargets + shortcutData.appPredictorTargets, ) } } @@ -230,7 +230,7 @@ constructor( sendShareShortcutInfoList( emptyList(), isFromAppPredictor = false, - appPredictorTargets = null + appPredictorTargets = null, ) return } @@ -256,7 +256,7 @@ constructor( @WorkerThread private fun onAppPredictorCallback(appPredictorTargets: List) { - appPredictorWatchdog.value?.cancel() + appPredictorWatchdog.get()?.cancel() endAppPredictorQueryTrace(userHandle) Log.d(TAG, "[$id] receive app targets from AppPredictor") if (appPredictorTargets.isEmpty() && shouldQueryDirectShareTargets()) { @@ -288,7 +288,7 @@ constructor( private fun sendShareShortcutInfoList( shortcuts: List, isFromAppPredictor: Boolean, - appPredictorTargets: List? + appPredictorTargets: List?, ) { shortcutSource.tryEmit(ShortcutData(shortcuts, isFromAppPredictor, appPredictorTargets)) } @@ -297,7 +297,7 @@ constructor( appTargets: Array, shortcuts: List, isFromAppPredictor: Boolean, - appPredictorTargets: List? + appPredictorTargets: List?, ): Result { if (appPredictorTargets != null && appPredictorTargets.size != shortcuts.size) { throw RuntimeException( @@ -324,7 +324,7 @@ constructor( shortcuts, appPredictorTargets, directShareAppTargetCache, - directShareShortcutInfoCache + directShareShortcutInfoCache, ) val resultRecord = ShortcutResultInfo(displayResolveInfo, chooserTargets) resultRecords.add(resultRecord) @@ -334,7 +334,7 @@ constructor( appTargets, resultRecords.toTypedArray(), directShareAppTargetCache, - directShareShortcutInfoCache + directShareShortcutInfoCache, ) } @@ -354,7 +354,7 @@ constructor( private class ShortcutData( val shortcuts: List, val isFromAppPredictor: Boolean, - val appPredictorTargets: List? + val appPredictorTargets: List?, ) /** Resolved shortcuts with corresponding app targets. */ @@ -368,7 +368,7 @@ constructor( /** Shortcuts grouped by app target. */ val shortcutsByApp: Array, val directShareAppTargetCache: Map, - val directShareShortcutInfoCache: Map + val directShareShortcutInfoCache: Map, ) private fun endAppPredictorQueryTrace(userHandle: UserHandle) { @@ -379,12 +379,12 @@ constructor( /** Shortcuts grouped by app. */ class ShortcutResultInfo( val appTarget: DisplayResolveInfo, - val shortcuts: List + val shortcuts: List, ) private class ShortcutsAppTargetsPair( val shortcuts: List, - val appTargets: List? + val appTargets: List?, ) /** A wrapper around AppPredictor to facilitate unit-testing. */ @@ -393,7 +393,7 @@ constructor( /** [AppPredictor.registerPredictionUpdates] */ open fun registerPredictionUpdates( callbackExecutor: Executor, - callback: AppPredictor.Callback + callback: AppPredictor.Callback, ) = mAppPredictor.registerPredictionUpdates(callbackExecutor, callback) /** [AppPredictor.unregisterPredictionUpdates] */ @@ -418,7 +418,7 @@ constructor( packageName, PackageManager.ApplicationInfoFlags.of( PackageManager.GET_META_DATA.toLong() - ) + ), ) appInfo.enabled && (appInfo.flags and ApplicationInfo.FLAG_SUSPENDED) == 0 } diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt index 8293264c..8c810058 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewImageLoaderTest.kt @@ -21,7 +21,6 @@ import android.net.Uri import android.util.Size import com.google.common.truth.Truth.assertThat import java.util.concurrent.atomic.AtomicInteger -import kotlinx.atomicfu.atomic import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -55,7 +54,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val b1 = testSubject.invoke(uri, Size(200, 100)) @@ -78,7 +77,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) testSubject.invoke(uri, Size(200, 100), caching = false) @@ -105,7 +104,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val b1Deferred = async { testSubject.invoke(uri, Size(200, 100), caching = false) } @@ -148,7 +147,7 @@ class PreviewImageLoaderTest { cacheSize = 1, defaultPreviewSize = 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val jobOne = launch { testSubject.invoke(uriOne, Size(200, 100)) } @@ -189,7 +188,7 @@ class PreviewImageLoaderTest { cacheSize = 1, defaultPreviewSize = 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val job = launch { testSubject.invoke(uri, Size(200, 100), caching = false) } @@ -214,7 +213,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val b1 = testSubject.invoke(uri, Size(100, 100)) @@ -240,7 +239,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val bitmap = testSubject.invoke(uri, Size(100, 100)) @@ -251,7 +250,7 @@ class PreviewImageLoaderTest { fun test_requestHigherResImage_cancelsLowerResLoading() = scope.runTest { val uri = createUri(0) - val cancelledRequestCount = atomic(0) + val cancelledRequestCount = AtomicInteger(0) val imageLoadingStarted = CompletableDeferred() val bitmapDeferred = CompletableDeferred() val thumbnailLoader = @@ -272,7 +271,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val lowResSize = 100 @@ -283,7 +282,7 @@ class PreviewImageLoaderTest { imageLoadingStarted.await() val result = async { testSubject.invoke(uri, Size(highResSize, highResSize)) } runCurrent() - assertThat(cancelledRequestCount.value).isEqualTo(1) + assertThat(cancelledRequestCount.get()).isEqualTo(1) bitmapDeferred.complete(createBitmap(highResSize, highResSize)) val bitmap = result.await() @@ -308,7 +307,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val b1 = testSubject.invoke(uri, Size(highResSize, highResSize)) @@ -333,7 +332,7 @@ class PreviewImageLoaderTest { cacheSize = 1, defaultPreviewSize, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) val b1 = testSubject(uri, Size(0, 0)) @@ -349,7 +348,7 @@ class PreviewImageLoaderTest { scope.runTest { val previewSize = Size(100, 100) val uris = List(2) { createUri(it) } - val loadingCount = atomic(0) + val loadingCount = AtomicInteger(0) val thumbnailLoader = FakeThumbnailLoader().apply { for (uri in uris) { @@ -365,19 +364,19 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) testSubject.prePopulate(uris.map { it to previewSize }) runCurrent() - assertThat(loadingCount.value).isEqualTo(1) + assertThat(loadingCount.get()).isEqualTo(1) assertThat(thumbnailLoader.invokeCalls).containsExactly(uris[0]) testSubject(uris[0], previewSize) runCurrent() - assertThat(loadingCount.value).isEqualTo(1) + assertThat(loadingCount.get()).isEqualTo(1) } @Test @@ -402,7 +401,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) testSubject(uriOne, previewSize) @@ -419,7 +418,7 @@ class PreviewImageLoaderTest { scope.runTest { val previewSize = Size(100, 100) val uri = createUri(1) - val loadingCount = atomic(0) + val loadingCount = AtomicInteger(0) val thumbnailLoader = FakeThumbnailLoader().apply { fakeInvoke[uri] = { @@ -433,13 +432,13 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) testSubject(uri, previewSize) testSubject(uri, previewSize) - assertThat(loadingCount.value).isEqualTo(2) + assertThat(loadingCount.get()).isEqualTo(2) } @Test(expected = CancellationException::class) @@ -454,7 +453,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) imageLoaderScope.cancel() testSubject(uri, Size(200, 200)) @@ -480,7 +479,7 @@ class PreviewImageLoaderTest { 1, 100, thumbnailLoader, - StandardTestDispatcher(scope.testScheduler) + StandardTestDispatcher(scope.testScheduler), ) launch { -- cgit v1.2.3-59-g8ed1b From 86608fc6b4bcf2b594d3ba054d758656b29d2538 Mon Sep 17 00:00:00 2001 From: Joshua Trask Date: Fri, 27 Sep 2024 14:16:08 +0000 Subject: Add other null check in decorateActionFactoryWithRefinement The nullability for `getEditButtonRunnable()` was addressed in ag/28555982 but a similar fix is still needed for the copy button. Bug: 353397828 Flag: EXEMPT bugfix Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:148852fb8d8a0d182b7918852c62d73a43d4855e) Merged-In: I193eaeba55abded5f4726976dc0839d44215726c Change-Id: I193eaeba55abded5f4726976dc0839d44215726c --- java/src/com/android/intentresolver/ChooserActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java') diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 3db821c1..4fc8fd9d 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -2174,6 +2174,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override @Nullable public Runnable getCopyButtonRunnable() { + if (originalFactory.getCopyButtonRunnable() == null) return null; return () -> { if (!mRefinementManager.maybeHandleSelection( RefinementType.COPY_ACTION, -- cgit v1.2.3-59-g8ed1b From a3ba3d88d2872f8412e816f13920225c5c9fd182 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 1 Oct 2024 23:54:33 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Bug: 369418613 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:31625fb93279e79cf0f81fa9d25caa19a84f0b56) Merged-In: I7c747cdd264d3b12efecc08af77f6368c234c3ff Change-Id: I7c747cdd264d3b12efecc08af77f6368c234c3ff --- java/res/values-ar/strings.xml | 2 +- java/res/values-hi/strings.xml | 2 +- java/res/values-iw/strings.xml | 2 +- java/res/values-sv/strings.xml | 2 +- java/res/values-vi/strings.xml | 2 +- java/res/values-zh-rCN/strings.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'java') diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml index 88faf86c..49769c57 100644 --- a/java/res/values-ar/strings.xml +++ b/java/res/values-ar/strings.xml @@ -76,7 +76,7 @@ "صورة مصغّرة لمعاينة ملف" "ما مِن أشخاص مقترحين للمشاركة معهم" "‏لم يتم منح هذا التطبيق إذن تسجيل، ولكن يمكنه تسجيل الصوت من خلال جهاز USB هذا." - "مساحة شخصية" + "المساحة الشخصية" "مساحة العمل" "المساحة الخاصّة" "عرض المحتوى الشخصي" diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml index 0cc9b219..3f6db1be 100644 --- a/java/res/values-hi/strings.xml +++ b/java/res/values-hi/strings.xml @@ -76,7 +76,7 @@ "फ़ाइल के थंबनेल की झलक" "शेयर करने के लिए, किसी व्यक्ति का सुझाव नहीं दिया गया है" "इस ऐप्लिकेशन को रिकॉर्ड करने की अनुमति नहीं दी गई है. हालांकि, ऐप्लिकेशन इस यूएसबी डिवाइस से ऐसा कर सकता है." - "निजी प्रोफ़ाइल" + "निजी" "वर्क प्रोफ़ाइल" "प्राइवेट" "निजी व्यू" diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml index e7521e77..bed01ff0 100644 --- a/java/res/values-iw/strings.xml +++ b/java/res/values-iw/strings.xml @@ -78,7 +78,7 @@ "‏לאפליקציה זו לא ניתנה הרשאת הקלטה, אבל אפשר להקליט אודיו באמצעות התקן ה-USB הזה." "אישי" "עבודה" - "פרטי" + "מרחב פרטי" "תצוגה אישית" "תצוגת עבודה" "תצוגה פרטית" diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml index a21af078..d48cc781 100644 --- a/java/res/values-sv/strings.xml +++ b/java/res/values-sv/strings.xml @@ -76,7 +76,7 @@ "Miniatyr av förhandsgranskning av fil" "Inga rekommenderade personer att dela med" "Appen har inte fått inspelningsbehörighet men kan spela in ljud via denna USB-enhet." - "Privat" + "Personlig" "Jobb" "Privat" "Personlig vy" diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml index eb789f5e..4c84256e 100644 --- a/java/res/values-vi/strings.xml +++ b/java/res/values-vi/strings.xml @@ -89,7 +89,7 @@ "Bạn không thể mở nội dung này bằng ứng dụng cá nhân" "Không chia sẻ được nội dung này bằng ứng dụng riêng tư" "Không mở được nội dung này bằng ứng dụng riêng tư" - "Các ứng dụng công việc đã bị tạm dừng" + "Các ứng dụng trong hồ sơ Công việc đã bị tạm dừng" "Tiếp tục" "Không có ứng dụng công việc" "Không có ứng dụng cá nhân" diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml index a2401ce7..c2fa444f 100644 --- a/java/res/values-zh-rCN/strings.xml +++ b/java/res/values-zh-rCN/strings.xml @@ -67,7 +67,7 @@ "{count,plural, =1{正在分享带有链接的视频}other{正在分享带有链接的 # 个视频}}" "{count,plural, =1{正在分享带有文本的文件}other{正在分享带有文本的 # 个文件}}" "{count,plural, =1{正在分享带有链接的文件}other{正在分享带有链接的 # 个文件}}" - "分享影集" + "分享相册" "{count,plural, =1{仅限图片}other{仅限图片}}" "{count,plural, =1{仅限视频}other{仅限视频}}" "{count,plural, =1{仅限文件}other{仅限文件}}" -- cgit v1.2.3-59-g8ed1b From 47d954e052c979c58eedd72194fcd01c540b18a4 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 26 Nov 2024 07:30:17 -0800 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I5f87c5b20781b733bf0b57b7845fcf74ee48ec93 --- java/res/values-af/strings.xml | 10 +++++----- java/res/values-bg/strings.xml | 2 +- java/res/values-bn/strings.xml | 2 +- java/res/values-fa/strings.xml | 2 +- java/res/values-hr/strings.xml | 2 +- java/res/values-kn/strings.xml | 2 +- java/res/values-lv/strings.xml | 2 +- java/res/values-ne/strings.xml | 2 +- java/res/values-pt-rBR/strings.xml | 2 +- java/res/values-pt/strings.xml | 2 +- java/res/values-ro/strings.xml | 2 +- java/res/values-sq/strings.xml | 2 +- java/res/values-sw/strings.xml | 6 +++--- java/res/values-tr/strings.xml | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) (limited to 'java') diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml index 55d84dfa..7bc3d8fe 100644 --- a/java/res/values-af/strings.xml +++ b/java/res/values-af/strings.xml @@ -36,17 +36,17 @@ "Stuur met" "Stuur met %1$s" "Stuur" - "Kies \'n Tuis-program" + "Kies \'n Tuis-app" "Gebruik %1$s as Tuis" "Vang prent vas" "Vang prent vas met" "Vang prent vas met %1$s" "Vang prent vas" - "Gebruik \'n ander program" + "Gebruik ’n ander app" "Kies \'n handeling" "Geen programme kan hierdie handeling uitvoer nie." - "Jy gebruik hierdie program buite jou werkprofiel" - "Jy gebruik tans hierdie program in jou werkprofiel" + "Jy gebruik hierdie app buite jou werkprofiel" + "Jy gebruik tans hierdie app in jou werkprofiel" "Altyd" "Net een keer" "%1$s steun nie werkprofiel nie" @@ -75,7 +75,7 @@ "Videovoorskouminiprent" "Lêervoorskouminiprent" "Geen mense om mee te deel is aanbeveel nie" - "Opneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel opneem." + "Opneemtoestemming is nie aan hierdie app verleen nie, maar dit kan oudio deur hierdie USB-toestel opneem." "Persoonlik" "Werk" "Privaat" diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml index 450712b1..00e06c77 100644 --- a/java/res/values-bg/strings.xml +++ b/java/res/values-bg/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{+ # файл}other{+ # файла}}" "{count,plural, =1{+ още # файл}other{+ още # файла}}" "Текстът се споделя" - "Връзката се споделя" + "Споделяне на връзката" "{count,plural, =1{Изображението се споделя}other{# изображения се споделят}}" "{count,plural, =1{Видеоклипът се споделя}other{# видеоклипа се споделят}}" "{count,plural, =1{# файл се споделя}other{# файла се споделят}}" diff --git a/java/res/values-bn/strings.xml b/java/res/values-bn/strings.xml index 2d33eb29..8cd95cd6 100644 --- a/java/res/values-bn/strings.xml +++ b/java/res/values-bn/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{আরও #টি ফাইল}one{আরও #টি ফাইল}other{আরও #টি ফাইল}}" "{count,plural, =1{আরও #টি ফাইল}one{আরও #টি ফাইল}other{আরও #টি ফাইল}}" "টেক্সট শেয়ার করা হচ্ছে" - "শেয়ার করা লিঙ্ক" + "শেয়ার করার জন্য লিঙ্ক" "{count,plural, =1{ছবি শেয়ার করা হচ্ছে}one{#টি ছবি শেয়ার করা হচ্ছে}other{#টি ছবি শেয়ার করা হচ্ছে}}" "{count,plural, =1{ভিডিও শেয়ার করা হচ্ছে}one{#টি ভিডিও শেয়ার করা হচ্ছে}other{#টি ভিডিও শেয়ার করা হচ্ছে}}" "{count,plural, =1{#টি ফাইল শেয়ার করা হচ্ছে}one{#টি ফাইল শেয়ার করা হচ্ছে}other{#টি ফাইল শেয়ার করা হচ্ছে}}" diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index 7b3dc6ea..9b99fb2f 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -74,7 +74,7 @@ "ریزعکس پیش‌نمای تصویر" "ریزعکس پیش‌نمای ویدیو" "ریزعکس پیش‌نمای فایل" - "هیچ فردی که با او هم‌رسانی کنید توصیه نشده است" + "هیچ فرد توصیه‌شده‌ای برای هم‌رسانی وجود ندارد" "‏مجوز ضبط به این برنامه داده نشده است اما می‌تواند صدا را ازطریق این دستگاه USB ضبط کند." "شخصی" "کاری" diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml index 85858303..b5009a6c 100644 --- a/java/res/values-hr/strings.xml +++ b/java/res/values-hr/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{+ # datoteka}one{+ # datoteka}few{+ # datoteke}other{+ # datoteka}}" "{count,plural, =1{i još # datoteka}one{i još # datoteka}few{i još # datoteke}other{i još # datoteka}}" "Dijeli se tekst" - "Dijeli se veza" + "Veza za dijeljenje" "{count,plural, =1{Podijelite sliku}one{Podijelite # sliku}few{Podijelite # slike}other{Podijelite # slika}}" "{count,plural, =1{Dijeli se videozapis}one{Dijeli se # videozapis}few{Dijele se # videozapisa}other{Dijeli se # videozapisa}}" "{count,plural, =1{Dijeli se # datoteka}one{Dijeli se # datoteka}few{Dijele se # datoteke}other{Dijeli se # datoteka}}" diff --git a/java/res/values-kn/strings.xml b/java/res/values-kn/strings.xml index 35bf148c..5e0a4e1f 100644 --- a/java/res/values-kn/strings.xml +++ b/java/res/values-kn/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{+ # ಫೈಲ್‌}one{+ # ಫೈಲ್‌ಗಳು}other{+ # ಫೈಲ್‌ಗಳು}}" "{count,plural, =1{+ # ಇನ್ನಷ್ಟು ಫೈಲ್}one{+ # ಇನ್ನಷ್ಟು ಫೈಲ್‌ಗಳು}other{+ # ಇನ್ನಷ್ಟು ಫೈಲ್‌ಗಳು}}" "ಪಠ್ಯ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ" - "ಲಿಂಕ್ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ" + "ಹಂಚಿಕೊಳ್ಳಬಹುದಾದ ಲಿಂಕ್" "{count,plural, =1{ಚಿತ್ರವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಚಿತ್ರಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{ವೀಡಿಯೊವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ವೀಡಿಯೊಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" "{count,plural, =1{# ಫೈಲ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}one{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}other{# ಫೈಲ್‌ಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ}}" diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml index 1c14c2b8..b0e5bc72 100644 --- a/java/res/values-lv/strings.xml +++ b/java/res/values-lv/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{un vēl # fails}zero{un vēl # faili}one{un vēl # fails}other{un vēl # faili}}" "{count,plural, =1{Un vēl # fails}zero{Un vēl # failu}one{Un vēl # fails}other{Un vēl # faili}}" "Tiek kopīgots teksts" - "Tiek kopīgota saite" + "Kopīgošanas saite" "{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}}" "{count,plural, =1{Tiek kopīgots video}zero{Tiek kopīgoti # video}one{Tiek kopīgots # video}other{Tiek kopīgoti # video}}" "{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}}" diff --git a/java/res/values-ne/strings.xml b/java/res/values-ne/strings.xml index 76365455..3926323c 100644 --- a/java/res/values-ne/strings.xml +++ b/java/res/values-ne/strings.xml @@ -78,7 +78,7 @@ "यो एपलाई रेकर्ड गर्ने अनुमति प्रदान गरिएको छैन तर यसले यो USB यन्त्रमार्फत अडियो क्याप्चर गर्न सक्छ।" "व्यक्तिगत" "काम" - "निजी" + "निजी स्पेस" "व्यक्तिगत दृश्य" "कार्य दृश्य" "निजी भ्यू" diff --git a/java/res/values-pt-rBR/strings.xml b/java/res/values-pt-rBR/strings.xml index c8ce55a8..c5514237 100644 --- a/java/res/values-pt-rBR/strings.xml +++ b/java/res/values-pt-rBR/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}" "{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}" "Compartilhando texto" - "Compartilhando link" + "Compartilhar link" "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml index c8ce55a8..c5514237 100644 --- a/java/res/values-pt/strings.xml +++ b/java/res/values-pt/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}" "{count,plural, =1{Mais # arquivo}one{Mais # arquivo}many{Mais # de arquivos}other{Mais # arquivos}}" "Compartilhando texto" - "Compartilhando link" + "Compartilhar link" "{count,plural, =1{Compartilhar imagem}one{Compartilhar # imagem}many{Compartilhar # de imagens}other{Compartilhar # imagens}}" "{count,plural, =1{Compartilhando vídeo}one{Compartilhando # vídeo}many{Compartilhando # de vídeos}other{Compartilhando # vídeos}}" "{count,plural, =1{Compartilhando # arquivo}one{Compartilhando # arquivo}many{Compartilhando # de arquivos}other{Compartilhando # arquivos}}" diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml index c2843bab..4368d22a 100644 --- a/java/res/values-ro/strings.xml +++ b/java/res/values-ro/strings.xml @@ -74,7 +74,7 @@ "Miniatură pentru previzualizarea imaginii" "Miniatură pentru previzualizarea videoclipului" "Miniatură pentru previzualizarea fișierului" - "Nu există persoane recomandate pentru permiterea accesului" + "Nu există persoane recomandate pentru trimitere" "Permisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB." "Personal" "Serviciu" diff --git a/java/res/values-sq/strings.xml b/java/res/values-sq/strings.xml index 374b2e0a..b1c3672f 100644 --- a/java/res/values-sq/strings.xml +++ b/java/res/values-sq/strings.xml @@ -74,7 +74,7 @@ "Miniatura e pamjes paraprake të imazhit" "Miniatura e pamjes paraprake të videos" "Miniatura e pamjes paraprake të skedarit" - "Nuk ka persona të rekomanduar për ta ndarë" + "Nuk ka persona të rekomanduar për të ndarë" "Këtij aplikacioni nuk i është dhënë leje për regjistrim, por mund të regjistrojë audio përmes kësaj pajisjeje USB." "Personal" "Puna" diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml index 2f63e887..e440315b 100644 --- a/java/res/values-sw/strings.xml +++ b/java/res/values-sw/strings.xml @@ -56,9 +56,9 @@ "{count,plural, =1{+ faili #}other{+ faili #}}" "{count,plural, =1{Faili nyingine #}other{Faili zingine #}}" "Kutuma maandishi" - "Inashiriki kiungo" - "{count,plural, =1{Inashiriki picha}other{Inashiriki picha #}}" - "{count,plural, =1{Inashiriki video}other{Inashiriki video #}}" + "Inatuma kiungo" + "{count,plural, =1{Kutuma picha}other{Kutuma picha #}}" + "{count,plural, =1{Inatuma video}other{Inatuma video #}}" "{count,plural, =1{Inashiriki faili #}other{Inashiriki faili #}}" "Chagua vipengee vya kutuma" "{count,plural, =1{Inashiriki picha na maandishi}other{Inashiriki picha # na maandishi}}" diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml index 5dee9296..0edacf5b 100644 --- a/java/res/values-tr/strings.xml +++ b/java/res/values-tr/strings.xml @@ -56,7 +56,7 @@ "{count,plural, =1{+ # dosya}other{+ # dosya}}" "{count,plural, =1{+ # dosya daha}other{+ # dosya daha}}" "Metin paylaşılıyor" - "Bağlantı paylaşılıyor" + "Paylaşım bağlantısı" "{count,plural, =1{Resim paylaşılıyor}other{# resim paylaşılıyor}}" "{count,plural, =1{Video paylaşılıyor}other{# video paylaşılıyor}}" "{count,plural, =1{# dosya paylaşılıyor}other{# dosya paylaşılıyor}}" -- cgit v1.2.3-59-g8ed1b