From ef59bb80955fe53c24da4d6d40331e1d463c3f36 Mon Sep 17 00:00:00 2001 From: 1 Date: Mon, 22 May 2023 21:01:06 +0000 Subject: Move copy and edit actions into preview space. Omitting edit action from landscape phones for now. Bug: 283245199 Test: Manual test with ShareTest with varying combinations of text length, title, icon, image shares. Test: atest IntentResolverUnitTests Change-Id: Iea40bd95db5665fb59684bf317bcac5e33b080bb --- java/res/drawable/edit_action_background.xml | 29 ++++ .../res/layout-h480dp/image_preview_image_item.xml | 22 +++ java/res/layout/chooser_grid_preview_text.xml | 54 ++++++-- .../intentresolver/ChooserActionFactory.java | 58 +++----- .../contentpreview/ChooserContentPreviewUi.java | 13 +- .../contentpreview/ContentPreviewUi.java | 16 --- .../contentpreview/FileContentPreviewUi.java | 4 +- .../FilesPlusTextContentPreviewUi.java | 17 +-- .../contentpreview/TextContentPreviewUi.java | 22 ++- .../contentpreview/UnifiedContentPreviewUi.java | 20 +-- .../widget/ScrollableImagePreviewView.kt | 17 ++- .../UnbundledChooserActivityTest.java | 8 +- .../contentpreview/ChooserContentPreviewUiTest.kt | 18 +-- .../contentpreview/ContentPreviewUiTest.kt | 44 +----- .../widget/BatchPreviewLoaderTest.kt | 148 +++++++++------------ 15 files changed, 221 insertions(+), 269 deletions(-) create mode 100644 java/res/drawable/edit_action_background.xml (limited to 'java') diff --git a/java/res/drawable/edit_action_background.xml b/java/res/drawable/edit_action_background.xml new file mode 100644 index 00000000..91726f49 --- /dev/null +++ b/java/res/drawable/edit_action_background.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/java/res/layout-h480dp/image_preview_image_item.xml b/java/res/layout-h480dp/image_preview_image_item.xml index db44c8be..ac63b2d5 100644 --- a/java/res/layout-h480dp/image_preview_image_item.xml +++ b/java/res/layout-h480dp/image_preview_image_item.xml @@ -17,6 +17,7 @@ @@ -52,4 +53,25 @@ android:tint="@android:color/white" android:layout_gravity="top|end" /> + + + + diff --git a/java/res/layout/chooser_grid_preview_text.xml b/java/res/layout/chooser_grid_preview_text.xml index 44163b49..96496a30 100644 --- a/java/res/layout/chooser_grid_preview_text.xml +++ b/java/res/layout/chooser_grid_preview_text.xml @@ -29,7 +29,7 @@ - - + + + + + diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java index f355d9d4..6ec62753 100644 --- a/java/src/com/android/intentresolver/ChooserActionFactory.java +++ b/java/src/com/android/intentresolver/ChooserActionFactory.java @@ -84,11 +84,8 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image"; private final Context mContext; - private final String mCopyButtonLabel; - private final Drawable mCopyButtonDrawable; - private final Runnable mOnCopyButtonClicked; - private final TargetInfo mEditSharingTarget; - private final Runnable mOnEditButtonClicked; + private final Runnable mCopyButtonRunnable; + private final Runnable mEditButtonRunnable; private final ImmutableList mCustomActions; private final @Nullable ChooserAction mModifyShareAction; private final Consumer mExcludeSharedTextAction; @@ -119,19 +116,13 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio Consumer finishCallback) { this( context, - context.getString(com.android.internal.R.string.copy), - context.getDrawable(com.android.internal.R.drawable.ic_menu_copy_material), - makeOnCopyRunnable( + makeCopyButtonRunnable( context, chooserRequest.getTargetIntent(), chooserRequest.getReferrerPackageName(), finishCallback, logger), - getEditSharingTarget( - context, - chooserRequest.getTargetIntent(), - integratedDeviceComponents), - makeOnEditRunnable( + makeEditButtonRunnable( getEditSharingTarget( context, chooserRequest.getTargetIntent(), @@ -149,22 +140,16 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio @VisibleForTesting ChooserActionFactory( Context context, - String copyButtonLabel, - Drawable copyButtonDrawable, - Runnable onCopyButtonClicked, - TargetInfo editSharingTarget, - Runnable onEditButtonClicked, + Runnable copyButtonRunnable, + Runnable editButtonRunnable, List customActions, @Nullable ChooserAction modifyShareAction, Consumer onUpdateSharedTextIsExcluded, ChooserActivityLogger logger, Consumer finishCallback) { mContext = context; - mCopyButtonLabel = copyButtonLabel; - mCopyButtonDrawable = copyButtonDrawable; - mOnCopyButtonClicked = onCopyButtonClicked; - mEditSharingTarget = editSharingTarget; - mOnEditButtonClicked = onEditButtonClicked; + mCopyButtonRunnable = copyButtonRunnable; + mEditButtonRunnable = editButtonRunnable; mCustomActions = ImmutableList.copyOf(customActions); mModifyShareAction = modifyShareAction; mExcludeSharedTextAction = onUpdateSharedTextIsExcluded; @@ -172,29 +157,16 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio mFinishCallback = finishCallback; } - /** Create an action that copies the share content to the clipboard. */ @Override - public ActionRow.Action createCopyButton() { - return new ActionRow.Action( - com.android.internal.R.id.chooser_copy_button, - mCopyButtonLabel, - mCopyButtonDrawable, - mOnCopyButtonClicked); + @Nullable + public Runnable getEditButtonRunnable() { + return mEditButtonRunnable; } - /** Create an action that opens the share content in a system-default editor. */ @Override @Nullable - public ActionRow.Action createEditButton() { - if (mEditSharingTarget == null) { - return null; - } - - return new ActionRow.Action( - com.android.internal.R.id.chooser_edit_button, - mEditSharingTarget.getDisplayLabel(), - mEditSharingTarget.getDisplayIconHolder().getDisplayIcon(), - mOnEditButtonClicked); + public Runnable getCopyButtonRunnable() { + return mCopyButtonRunnable; } /** Create custom actions */ @@ -247,7 +219,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio return mExcludeSharedTextAction; } - private static Runnable makeOnCopyRunnable( + private static Runnable makeCopyButtonRunnable( Context context, Intent targetIntent, String referrerPackageName, @@ -344,7 +316,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio return dri; } - private static Runnable makeOnEditRunnable( + private static Runnable makeEditButtonRunnable( TargetInfo editSharingTarget, Callable firstVisibleImageQuery, ActionActivityStarter activityStarter, diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index 9100b392..e8367c4e 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -53,12 +53,17 @@ public final class ChooserContentPreviewUi { * TODO: clarify why action buttons are part of preview logic. */ public interface ActionFactory { - /** Create an action that copies the share content to the clipboard. */ - ActionRow.Action createCopyButton(); + /** + * @return Runnable to be run when an edit button is clicked (if available). + */ + @Nullable + Runnable getEditButtonRunnable(); - /** Create an action that opens the share content in a system-default editor. */ + /** + * @return Runnable to be run when a copy button is clicked (if available). + */ @Nullable - ActionRow.Action createEditButton(); + Runnable getCopyButtonRunnable(); /** Create custom actions */ List createCustomActions(); diff --git a/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java index 9699594e..07071236 100644 --- a/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java @@ -32,9 +32,6 @@ import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ScrollableImagePreviewView; -import java.util.ArrayList; -import java.util.List; - abstract class ContentPreviewUi { private static final int IMAGE_FADE_IN_MILLIS = 150; static final String TAG = "ChooserPreview"; @@ -45,19 +42,6 @@ abstract class ContentPreviewUi { public abstract ViewGroup display( Resources resources, LayoutInflater layoutInflater, ViewGroup parent); - protected static List createActions( - List systemActions, - List customActions) { - ArrayList actions = - new ArrayList<>(systemActions.size() + customActions.size()); - if (customActions.isEmpty()) { - actions.addAll(systemActions); - } else { - actions.addAll(customActions); - } - return actions; - } - protected static void updateViewWithImage(ImageView imageView, Bitmap image) { if (image == null) { imageView.setVisibility(View.GONE); diff --git a/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java index 16ff6c23..8d3e62aa 100644 --- a/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java @@ -30,7 +30,6 @@ import androidx.annotation.Nullable; import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -108,8 +107,7 @@ class FileContentPreviewUi extends ContentPreviewUi { final ActionRow actionRow = mContentPreview.findViewById(com.android.internal.R.id.chooser_action_row); - List actions = - createActions(new ArrayList<>(), mActionFactory.createCustomActions()); + List actions = mActionFactory.createCustomActions(); actionRow.setActions(actions); if (actions.isEmpty()) { mContentPreview.findViewById(R.id.actions_top_divider).setVisibility(View.GONE); diff --git a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java index e4e33839..61ca44e0 100644 --- a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java @@ -37,7 +37,6 @@ import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ScrollableImagePreviewView; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; @@ -123,9 +122,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi { final ActionRow actionRow = mContentPreviewView.findViewById(com.android.internal.R.id.chooser_action_row); - List actions = createActions( - createImagePreviewActions(), - mActionFactory.createCustomActions()); + List actions = mActionFactory.createCustomActions(); actionRow.setActions(actions); if (actions.isEmpty()) { @@ -141,18 +138,6 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi { return mContentPreviewView; } - private List createImagePreviewActions() { - ArrayList actions = new ArrayList<>(2); - //TODO: add copy action; - if (mIsSingleImage) { - ActionRow.Action action = mActionFactory.createEditButton(); - if (action != null) { - actions.add(action); - } - } - return actions; - } - private void updateUiWithMetadata(ViewGroup contentPreviewView) { prepareTextPreview(contentPreviewView, mActionFactory); updateHeadline(contentPreviewView); diff --git a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java index 19fd3bb4..c38ed03a 100644 --- a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java @@ -33,9 +33,6 @@ import androidx.lifecycle.Lifecycle; import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; -import java.util.ArrayList; -import java.util.List; - class TextContentPreviewUi extends ContentPreviewUi { private final Lifecycle mLifecycle; @Nullable @@ -85,10 +82,7 @@ class TextContentPreviewUi extends ContentPreviewUi { final ActionRow actionRow = contentPreviewLayout.findViewById(com.android.internal.R.id.chooser_action_row); - actionRow.setActions( - createActions( - createTextPreviewActions(), - mActionFactory.createCustomActions())); + actionRow.setActions(mActionFactory.createCustomActions()); if (mSharingText == null) { contentPreviewLayout @@ -129,14 +123,16 @@ class TextContentPreviewUi extends ContentPreviewUi { bitmap)); } + Runnable onCopy = mActionFactory.getCopyButtonRunnable(); + View copyButton = contentPreviewLayout.findViewById(R.id.copy); + if (onCopy != null) { + copyButton.setOnClickListener((v) -> onCopy.run()); + } else { + copyButton.setVisibility(View.GONE); + } + displayHeadline(contentPreviewLayout, mHeadlineGenerator.getTextHeadline(mSharingText)); return contentPreviewLayout; } - - private List createTextPreviewActions() { - ArrayList actions = new ArrayList<>(2); - actions.add(mActionFactory.createCopyButton()); - return actions; - } } diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java index 26f4d007..eb3e8e72 100644 --- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java @@ -91,9 +91,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { final ActionRow actionRow = mContentPreviewView.findViewById(com.android.internal.R.id.chooser_action_row); - List actions = createActions( - createImagePreviewActions(), - mActionFactory.createCustomActions()); + List actions = mActionFactory.createCustomActions(); actionRow.setActions(actions); if (actions.isEmpty()) { mContentPreviewView.findViewById(R.id.actions_top_divider).setVisibility(View.GONE); @@ -135,9 +133,11 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video; if (fileInfo.getPreviewUri() != null) { + Runnable editAction = + mShowEditAction ? mActionFactory.getEditButtonRunnable() : null; previews.add( new ScrollableImagePreviewView.Preview( - previewType, fileInfo.getPreviewUri())); + previewType, fileInfo.getPreviewUri(), editAction)); } } @@ -151,16 +151,4 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { displayHeadline(contentPreviewView, mHeadlineGenerator.getFilesHeadline(count)); } } - - private List createImagePreviewActions() { - ArrayList actions = new ArrayList<>(1); - //TODO: add copy action; - if (mShowEditAction) { - ActionRow.Action action = mActionFactory.createEditButton(); - if (action != null) { - actions.add(action); - } - } - return actions; - } } diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt index e761c0aa..9c948bd9 100644 --- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt +++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt @@ -237,9 +237,14 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { internal constructor( val type: PreviewType, val uri: Uri, + val editAction: Runnable?, internal var aspectRatioString: String ) { - constructor(type: PreviewType, uri: Uri) : this(type, uri, "1:1") + constructor( + type: PreviewType, + uri: Uri, + editAction: Runnable? + ) : this(type, uri, editAction, "1:1") } enum class PreviewType { @@ -370,6 +375,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { val image = view.requireViewById(R.id.image) private val badgeFrame = view.requireViewById(R.id.badge_frame) private val badge = view.requireViewById(R.id.badge) + private val editActionContainer = view.findViewById(R.id.edit) private var scope: CoroutineScope? = null fun bind( @@ -404,6 +410,12 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { badgeFrame.visibility = View.VISIBLE } } + preview.editAction?.also { onClick -> + editActionContainer?.apply { + setOnClickListener { onClick.run() } + visibility = View.VISIBLE + } + } resetScope().launch { loadImage(preview, imageLoader) if (preview.type == PreviewType.Image) { @@ -545,7 +557,8 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { bitmap.width, bitmap.height ) - } ?: 0 + } + ?: 0 } .getOrDefault(0) diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index c2212bc2..99564ae3 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -868,8 +868,8 @@ public class UnbundledChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click()); + onView(withId(R.id.copy)).check(matches(isDisplayed())); + onView(withId(R.id.copy)).perform(click()); ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); @@ -892,8 +892,8 @@ public class UnbundledChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click()); + onView(withId(R.id.copy)).check(matches(isDisplayed())); + onView(withId(R.id.copy)).perform(click()); ChooserActivityLogger logger = activity.getChooserActivityLogger(); verify(logger, times(1)).logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_COPY)); diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index c62f36ce..9bfd2052 100644 --- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -51,8 +51,8 @@ class ChooserContentPreviewUiTest { } private val actionFactory = object : ActionFactory { - override fun createCopyButton() = ActionRow.Action(label = "Copy", icon = null) {} - override fun createEditButton(): ActionRow.Action? = null + override fun getCopyButtonRunnable(): Runnable? = null + override fun getEditButtonRunnable(): Runnable? = null override fun createCustomActions(): List = emptyList() override fun getModifyShareAction(): ActionRow.Action? = null override fun getExcludeSharedTextAction(): Consumer = Consumer {} @@ -103,12 +103,7 @@ class ChooserContentPreviewUiTest { whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_IMAGE) whenever(previewData.uriCount).thenReturn(2) whenever(previewData.firstFileInfo) - .thenReturn( - FileInfo.Builder(uri) - .withPreviewUri(uri) - .withMimeType("image/png") - .build() - ) + .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) val testSubject = ChooserContentPreviewUi( lifecycle, @@ -131,12 +126,7 @@ class ChooserContentPreviewUiTest { whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_IMAGE) whenever(previewData.uriCount).thenReturn(2) whenever(previewData.firstFileInfo) - .thenReturn( - FileInfo.Builder(uri) - .withPreviewUri(uri) - .withMimeType("image/png") - .build() - ) + .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) val testSubject = ChooserContentPreviewUi( lifecycle, diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt index 6c30fc9e..6db53a9e 100644 --- a/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt +++ b/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt @@ -16,41 +16,18 @@ package com.android.intentresolver.contentpreview -import android.content.res.Resources -import android.view.LayoutInflater -import android.view.ViewGroup -import com.android.intentresolver.widget.ActionRow import com.android.intentresolver.widget.ScrollableImagePreviewView.PreviewType import com.google.common.truth.Truth.assertThat import org.junit.Test class ContentPreviewUiTest { - private class TestablePreview() : ContentPreviewUi() { - override fun getType() = 0 - - override fun display( - resources: Resources?, - layoutInflater: LayoutInflater?, - parent: ViewGroup? - ): ViewGroup { - throw IllegalStateException() - } - - // exposing for testing - fun makeActions( - system: List, - custom: List - ): List { - return createActions(system, custom) - } - } - @Test fun testPreviewTypes() { - val typeClassifier = object : MimeTypeClassifier { - override fun isImageType(type: String?) = (type == "image") - override fun isVideoType(type: String?) = (type == "video") - } + val typeClassifier = + object : MimeTypeClassifier { + override fun isImageType(type: String?) = (type == "image") + override fun isVideoType(type: String?) = (type == "video") + } assertThat(ContentPreviewUi.getPreviewType(typeClassifier, "image")) .isEqualTo(PreviewType.Image) @@ -61,15 +38,4 @@ class ContentPreviewUiTest { assertThat(ContentPreviewUi.getPreviewType(typeClassifier, null)) .isEqualTo(PreviewType.File) } - - @Test - fun testCreateActions() { - val preview = TestablePreview() - - val system = listOf(ActionRow.Action(label="system", icon=null) {}) - val custom = listOf(ActionRow.Action(label="custom", icon=null) {}) - - assertThat(preview.makeActions(system, custom)).isEqualTo(custom) - assertThat(preview.makeActions(system, listOf())).isEqualTo(system) - } } diff --git a/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt b/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt index c1d7451f..e65cba5f 100644 --- a/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt +++ b/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt @@ -18,29 +18,29 @@ package com.android.intentresolver.widget import android.graphics.Bitmap import android.net.Uri +import com.android.intentresolver.captureMany +import com.android.intentresolver.mock import com.android.intentresolver.widget.ScrollableImagePreviewView.BatchPreviewLoader import com.android.intentresolver.widget.ScrollableImagePreviewView.Preview import com.android.intentresolver.widget.ScrollableImagePreviewView.PreviewType +import com.android.intentresolver.withArgCaptor +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.cancel import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Test -import com.android.intentresolver.mock -import com.android.intentresolver.captureMany -import com.android.intentresolver.withArgCaptor -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Before +import org.junit.Test import org.mockito.Mockito.atLeast import org.mockito.Mockito.times import org.mockito.Mockito.verify -import com.google.common.truth.Truth.assertThat @OptIn(ExperimentalCoroutinesApi::class) class BatchPreviewLoaderTest { @@ -67,22 +67,21 @@ class BatchPreviewLoaderTest { val uriOne = createUri(1) val uriTwo = createUri(2) imageLoader.setUriLoadingOrder(succeed(uriTwo), succeed(uriOne)) - val testSubject = BatchPreviewLoader( - imageLoader, - previews(uriOne, uriTwo), - 0, - onReset, - onUpdate, - onCompletion - ) + val testSubject = + BatchPreviewLoader( + imageLoader, + previews(uriOne, uriTwo), + 0, + onReset, + onUpdate, + onCompletion + ) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() verify(onCompletion, times(1)).invoke() verify(onReset, times(1)).invoke(2) - val list = withArgCaptor { - verify(onUpdate, times(1)).invoke(capture()) - }.map { it.uri } + val list = withArgCaptor { verify(onUpdate, times(1)).invoke(capture()) }.map { it.uri } assertThat(list).containsExactly(uriOne, uriTwo).inOrder() } @@ -93,22 +92,21 @@ class BatchPreviewLoaderTest { val uriTwo = createUri(2) val uriThree = createUri(3) imageLoader.setUriLoadingOrder(succeed(uriThree), fail(uriTwo), succeed(uriOne)) - val testSubject = BatchPreviewLoader( - imageLoader, - previews(uriOne, uriTwo, uriThree), - 0, - onReset, - onUpdate, - onCompletion - ) + val testSubject = + BatchPreviewLoader( + imageLoader, + previews(uriOne, uriTwo, uriThree), + 0, + onReset, + onUpdate, + onCompletion + ) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() verify(onCompletion, times(1)).invoke() verify(onReset, times(1)).invoke(3) - val list = withArgCaptor { - verify(onUpdate, times(1)).invoke(capture()) - }.map { it.uri } + val list = withArgCaptor { verify(onUpdate, times(1)).invoke(capture()) }.map { it.uri } assertThat(list).containsExactly(uriOne, uriThree).inOrder() } @@ -116,35 +114,28 @@ class BatchPreviewLoaderTest { fun test_imagesLoadedNotInOrder_updatedInOrder() { val imageLoader = TestImageLoader(testScope) val uris = Array(10) { createUri(it) } - val loadingOrder = Array(uris.size) { i -> - val uriIdx = when { - i % 2 == 1 -> i - 1 - i % 2 == 0 && i < uris.size - 1 -> i + 1 - else -> i + val loadingOrder = + Array(uris.size) { i -> + val uriIdx = + when { + i % 2 == 1 -> i - 1 + i % 2 == 0 && i < uris.size - 1 -> i + 1 + else -> i + } + succeed(uris[uriIdx]) } - succeed(uris[uriIdx]) - } imageLoader.setUriLoadingOrder(*loadingOrder) - val testSubject = BatchPreviewLoader( - imageLoader, - previews(*uris), - 0, - onReset, - onUpdate, - onCompletion - ) + val testSubject = + BatchPreviewLoader(imageLoader, previews(*uris), 0, onReset, onUpdate, onCompletion) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() verify(onCompletion, times(1)).invoke() verify(onReset, times(1)).invoke(uris.size) - val list = captureMany { - verify(onUpdate, atLeast(1)).invoke(capture()) - }.fold(ArrayList()) { acc, update -> - acc.apply { - addAll(update) - } - }.map { it.uri } + val list = + captureMany { verify(onUpdate, atLeast(1)).invoke(capture()) } + .fold(ArrayList()) { acc, update -> acc.apply { addAll(update) } } + .map { it.uri } assertThat(list).containsExactly(*uris).inOrder() } @@ -152,36 +143,29 @@ class BatchPreviewLoaderTest { fun test_imagesLoadedNotInOrderSomeFailed_updatedInOrder() { val imageLoader = TestImageLoader(testScope) val uris = Array(10) { createUri(it) } - val loadingOrder = Array(uris.size) { i -> - val uriIdx = when { - i % 2 == 1 -> i - 1 - i % 2 == 0 && i < uris.size - 1 -> i + 1 - else -> i + val loadingOrder = + Array(uris.size) { i -> + val uriIdx = + when { + i % 2 == 1 -> i - 1 + i % 2 == 0 && i < uris.size - 1 -> i + 1 + else -> i + } + if (uriIdx % 2 == 0) fail(uris[uriIdx]) else succeed(uris[uriIdx]) } - if (uriIdx % 2 == 0) fail(uris[uriIdx]) else succeed(uris[uriIdx]) - } val expectedUris = Array(uris.size / 2) { createUri(it * 2 + 1) } imageLoader.setUriLoadingOrder(*loadingOrder) - val testSubject = BatchPreviewLoader( - imageLoader, - previews(*uris), - 0, - onReset, - onUpdate, - onCompletion - ) + val testSubject = + BatchPreviewLoader(imageLoader, previews(*uris), 0, onReset, onUpdate, onCompletion) testSubject.loadAspectRatios(200) { _, _, _ -> 100 } dispatcher.scheduler.advanceUntilIdle() verify(onCompletion, times(1)).invoke() verify(onReset, times(1)).invoke(uris.size) - val list = captureMany { - verify(onUpdate, atLeast(1)).invoke(capture()) - }.fold(ArrayList()) { acc, update -> - acc.apply { - addAll(update) - } - }.map { it.uri } + val list = + captureMany { verify(onUpdate, atLeast(1)).invoke(capture()) } + .fold(ArrayList()) { acc, update -> acc.apply { addAll(update) } } + .map { it.uri } assertThat(list).containsExactly(*expectedUris).inOrder() } @@ -191,21 +175,15 @@ class BatchPreviewLoaderTest { private fun succeed(uri: Uri) = uri to true private fun previews(vararg uris: Uri) = uris.fold(ArrayList(uris.size)) { acc, uri -> - acc.apply { - add(Preview(PreviewType.Image, uri)) - } + acc.apply { add(Preview(PreviewType.Image, uri, editAction = null)) } } } -private class TestImageLoader( - scope: CoroutineScope -) : suspend (Uri, Boolean) -> Bitmap? { +private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) -> Bitmap? { private val loadingOrder = ArrayDeque>() private val pendingRequests = LinkedHashMap>() private val flow = MutableSharedFlow(replay = 1) - private val bitmap by lazy { - Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) - } + private val bitmap by lazy { Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) } init { scope.launch { @@ -217,9 +195,7 @@ private class TestImageLoader( deferred.complete(if (isLoaded) bitmap else null) } if (loadingOrder.isEmpty()) { - pendingRequests.forEach { (uri, deferred) -> - deferred.complete(bitmap) - } + pendingRequests.forEach { (uri, deferred) -> deferred.complete(bitmap) } pendingRequests.clear() } } -- cgit v1.2.3-59-g8ed1b