From 8ac875364c8d77b6f4a0ff2a10f9b4b5976618f6 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Tue, 15 Aug 2023 00:53:21 -0700 Subject: Fix PreviewDataProvider previewType and firstFileInfo timeout logic Execute time-critical operations within `withTimeoutOrNull` function on a different task launched from a separate scope. The separate scope is need as, according to structural concurrency principals, `withTimeout`, upon timeout, canceling all its child jobs and completes only when all of them complete. The core of the logic we're wrapping with the timeout are ContentProvider calls which are not cancellable (and the whole operation can be canceled in some intermediate points between the calls). Calling them from another scope allows us just abandon them on timeout which is OK as we run the logic once anyway (and cache the results). Bug: 295985906 Test: Use the ShareTest app with metadata timeout and media type disguise option. Test: Integration tests. Change-Id: I922142385804abd78c2ad880d88d416d78155800 --- .../contentpreview/PreviewDataProvider.kt | 8 ++-- .../UnbundledChooserActivityTest.java | 49 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt index fd5ce3f8..9f1cc6c1 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt @@ -72,7 +72,6 @@ private const val TIMEOUT_MS = 1_000L */ @OpenForTesting open class PreviewDataProvider -@VisibleForTesting @JvmOverloads constructor( private val scope: CoroutineScope, @@ -128,7 +127,8 @@ constructor( } else { try { runBlocking(scope.coroutineContext) { - withTimeoutOrNull(TIMEOUT_MS) { loadPreviewType() } ?: CONTENT_PREVIEW_FILE + withTimeoutOrNull(TIMEOUT_MS) { scope.async { loadPreviewType() }.await() } + ?: CONTENT_PREVIEW_FILE } } catch (e: CancellationException) { Log.w( @@ -152,7 +152,9 @@ constructor( val builder = FileInfo.Builder(record.uri) try { runBlocking(scope.coroutineContext) { - withTimeoutOrNull(TIMEOUT_MS) { builder.readFromRecord(record) } + withTimeoutOrNull(TIMEOUT_MS) { + scope.async { builder.readFromRecord(record) }.await() + } } } catch (e: CancellationException) { Log.w( diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index 5709c912..b8b57403 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -1009,6 +1009,55 @@ public class UnbundledChooserActivityTest { .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); } + @Test + public void testSlowUriMetadata_fallbackToFilePreview() throws InterruptedException { + Uri uri = createTestContentProviderUri( + "application/pdf", "image/png", /*streamTypeTimeout=*/4_000); + ArrayList uris = new ArrayList<>(1); + uris.add(uri); + Intent sendIntent = createSendUriIntentWithPreview(uris); + ChooserActivityOverrideData.getInstance().imageLoader = + createImageLoader(uri, createBitmap()); + + List resolvedComponentInfos = createResolvedComponentsForTest(2); + + setupResolverControllers(resolvedComponentInfos); + assertThat(launchActivityWithTimeout(Intent.createChooser(sendIntent, null), 2_000)) + .isTrue(); + waitForIdle(); + + onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); + onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + } + + @Test + public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi() + throws InterruptedException { + Uri fileUri = createTestContentProviderUri( + "application/pdf", "application/pdf", /*streamTypeTimeout=*/150); + Uri imageUri = createTestContentProviderUri("application/pdf", "image/png"); + ArrayList uris = new ArrayList<>(50); + for (int i = 0; i < 49; i++) { + uris.add(fileUri); + } + uris.add(imageUri); + Intent sendIntent = createSendUriIntentWithPreview(uris); + ChooserActivityOverrideData.getInstance().imageLoader = + createImageLoader(imageUri, createBitmap()); + + List resolvedComponentInfos = createResolvedComponentsForTest(2); + setupResolverControllers(resolvedComponentInfos); + assertThat(launchActivityWithTimeout(Intent.createChooser(sendIntent, null), 2_000)) + .isTrue(); + + waitForIdle(); + + onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); + onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + } + @Test public void testManyVisibleImagePreview_ScrollableImagePreview() { Uri uri = createTestContentProviderUri("image/png", null); -- cgit v1.2.3-59-g8ed1b