summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Andrey Epin <ayepin@google.com> 2024-06-04 10:46:04 -0700
committer Andrey Epin <ayepin@google.com> 2024-06-07 09:59:08 -0700
commit1975528de9f1abcbfcebd4a4dadbf9858e9fe764 (patch)
tree24b5d0d9ca23a0d52cfff084a9e67dda7c32f3d1
parent2735c39b5f7c83e697af406ba7bef1cde926546f (diff)
Shareousel: Maintain cursor order for shated items
Add a position property to PreviewModel class to track relative order of items. For each item, the initial value is artificial and derived from the order of the initially shared items and is updated upon reading the additional items cursor. Upon sharing, If the selection has not change, the items will be shared in their original order; If the selection has changed, the order of the items will be affected by the observed items order in the cursor. Fix: 329683774 Test: manual testing Test: atest IntentResolver-tests-unit Test: atest IntentResolver-tests-activity Flag: android.service.chooser.chooser_payload_toggling Change-Id: Ie552887702cde75cb1a05ed3ec5415f4f4a5c8dc
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/data/repository/PreviewSelectionsRepository.kt3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolver.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractor.kt32
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractor.kt14
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractor.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractor.kt30
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/CursorRow.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt4
-rw-r--r--java/src/com/android/intentresolver/util/ParallelIteration.kt21
-rw-r--r--tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt1
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/cursor/PayloadToggleCursorResolverTest.kt32
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CursorPreviewsInteractorTest.kt45
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt32
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt29
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt54
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectionInteractorTest.kt30
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt3
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt31
18 files changed, 282 insertions, 85 deletions
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 48c06192..81c56d1e 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
@@ -16,6 +16,7 @@
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 javax.inject.Inject
@@ -24,5 +25,5 @@ import kotlinx.coroutines.flow.MutableStateFlow
/** Stores set of selected previews. */
@ViewModelScoped
class PreviewSelectionsRepository @Inject constructor() {
- val selections = MutableStateFlow(emptyList<PreviewModel>())
+ val selections = MutableStateFlow(emptyMap<Uri, PreviewModel>())
}
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 d9612696..148310e6 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
@@ -55,7 +55,7 @@ constructor(
)
}
.getOrNull()
- ?.viewBy { readUri()?.let { uri -> CursorRow(uri, readSize()) } }
+ ?.viewBy { readUri()?.let { uri -> CursorRow(uri, readSize(), position) } }
}
private fun Cursor.readUri(): Uri? {
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 fa600c86..a475263c 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
@@ -56,6 +56,7 @@ class CursorPreviewsInteractor
@Inject
constructor(
private val interactor: SetCursorPreviewsInteractor,
+ private val selectionInteractor: SelectionInteractor,
@FocusedItemIndex private val focusedItemIdx: Int,
private val uriMetadataReader: UriMetadataReader,
@PageSize private val pageSize: Int,
@@ -287,19 +288,26 @@ constructor(
private fun createPreviewModel(
row: CursorRow,
unclaimedRecords: MutableUnclaimedMap,
- ): PreviewModel =
- unclaimedRecords.remove(row.uri)?.second
- ?: 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),
- )
+ ): 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 : MutablePreviewMap> M.putAllUnclaimedRight(unclaimed: UnclaimedMap): M =
putAllUnclaimedWhere(unclaimed) { it >= focusedItemIdx }
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractor.kt
index c9c9a9b3..50086a23 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractor.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractor.kt
@@ -25,7 +25,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.Curs
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
import com.android.intentresolver.inject.ContentUris
import com.android.intentresolver.inject.FocusedItemIndex
-import com.android.intentresolver.util.mapParallel
+import com.android.intentresolver.util.mapParallelIndexed
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
@@ -45,7 +45,7 @@ constructor(
suspend fun activate() = coroutineScope {
val cursor = async { cursorResolver.getCursor() }
val initialPreviewMap = getInitialPreviews()
- selectionRepository.selections.value = initialPreviewMap
+ selectionRepository.selections.value = initialPreviewMap.associateBy { it.uri }
setCursorPreviews.setPreviews(
previews = initialPreviewMap,
startIndex = focusedItemIdx,
@@ -61,7 +61,7 @@ constructor(
selectedItems
// Restrict parallelism so as to not overload the metadata reader; anecdotally, too
// many parallel queries causes failures.
- .mapParallel(parallelism = 4) { uri ->
+ .mapParallelIndexed(parallelism = 4) { index, uri ->
val metadata = uriMetadataReader.getMetadata(uri)
PreviewModel(
uri = uri,
@@ -70,8 +70,12 @@ constructor(
aspectRatio =
metadata.previewUri?.let {
uriMetadataReader.readPreviewSize(it).aspectRatioOrDefault(1f)
- }
- ?: 1f,
+ } ?: 1f,
+ order = when {
+ index < focusedItemIdx -> Int.MIN_VALUE + index
+ index == focusedItemIdx -> 0
+ else -> Int.MAX_VALUE - selectedItems.size + index + 1
+ }
)
}
}
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 55a995f5..d52a71a1 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
@@ -29,7 +29,7 @@ class SelectablePreviewInteractor(
val uri: Uri = key.uri
/** Whether or not this preview is selected by the user. */
- val isSelected: Flow<Boolean> = selectionInteractor.selections.map { key in it }
+ val isSelected: Flow<Boolean> = selectionInteractor.selections.map { key.uri in it }
/** Sets whether this preview is selected by the user. */
fun setSelected(isSelected: Boolean) {
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 13af92cb..97d9fa66 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
@@ -16,6 +16,7 @@
package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor
+import android.net.Uri
import com.android.intentresolver.contentpreview.MimeTypeClassifier
import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier
@@ -23,8 +24,9 @@ import com.android.intentresolver.contentpreview.payloadtoggle.shared.ContentTyp
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.updateAndGet
class SelectionInteractor
@@ -36,31 +38,41 @@ constructor(
private val mimeTypeClassifier: MimeTypeClassifier,
) {
/** List of selected previews. */
- val selections: StateFlow<List<PreviewModel>>
- get() = selectionsRepo.selections
+ val selections: Flow<Set<Uri>> =
+ selectionsRepo.selections.map { it.keys }.distinctUntilChanged()
/** Amount of selected previews. */
val amountSelected: Flow<Int> = selectionsRepo.selections.map { it.size }
- val aggregateContentType: Flow<ContentType> = selections.map { aggregateContentType(it) }
+ val aggregateContentType: Flow<ContentType> =
+ selectionsRepo.selections.map { aggregateContentType(it.values) }
+
+ fun updateSelection(model: PreviewModel) {
+ selectionsRepo.selections.update {
+ if (it.containsKey(model.uri)) it + (model.uri to model) else it
+ }
+ }
fun select(model: PreviewModel) {
- updateChooserRequest(selectionsRepo.selections.updateAndGet { it + model })
+ updateChooserRequest(
+ selectionsRepo.selections.updateAndGet { it + (model.uri to model) }.values
+ )
}
fun unselect(model: PreviewModel) {
if (selectionsRepo.selections.value.size > 1) {
- updateChooserRequest(selectionsRepo.selections.updateAndGet { it - model })
+ updateChooserRequest(selectionsRepo.selections.updateAndGet { it - model.uri }.values)
}
}
- private fun updateChooserRequest(selections: List<PreviewModel>) {
- val intent = targetIntentModifier.intentFromSelection(selections)
+ private fun updateChooserRequest(selections: Collection<PreviewModel>) {
+ val sorted = selections.sortedBy { it.order }
+ val intent = targetIntentModifier.intentFromSelection(sorted)
updateTargetIntentInteractor.updateTargetIntent(intent)
}
private fun aggregateContentType(
- items: List<PreviewModel>,
+ items: Collection<PreviewModel>,
): ContentType {
if (items.isEmpty()) {
return ContentType.Other
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/CursorRow.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/CursorRow.kt
index f1d856ac..aae29102 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/CursorRow.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/model/CursorRow.kt
@@ -20,4 +20,4 @@ import android.net.Uri
import android.util.Size
/** Represents additional content cursor row */
-data class CursorRow(val uri: Uri, val previewSize: Size?)
+data class CursorRow(val uri: Uri, val previewSize: Size?, val position: Int)
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt
index 85c70004..8a479156 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/shared/model/PreviewModel.kt
@@ -27,4 +27,8 @@ data class PreviewModel(
/** Mimetype for the data [uri] points to. */
val mimeType: String?,
val aspectRatio: Float = 1f,
+ /**
+ * Relative item position in the list that is used to determine items order in the target intent
+ */
+ val order: Int,
)
diff --git a/java/src/com/android/intentresolver/util/ParallelIteration.kt b/java/src/com/android/intentresolver/util/ParallelIteration.kt
index 70c46c47..745bcdbf 100644
--- a/java/src/com/android/intentresolver/util/ParallelIteration.kt
+++ b/java/src/com/android/intentresolver/util/ParallelIteration.kt
@@ -48,3 +48,24 @@ private suspend fun <A, B> Iterable<A>.mapParallel(block: suspend (A) -> B): Lis
}
.awaitAll()
}
+
+suspend fun <A, B> Iterable<A>.mapParallelIndexed(
+ parallelism: Int? = null,
+ block: suspend (Int, A) -> B,
+): List<B> =
+ parallelism?.let { permits ->
+ withSemaphore(permits = permits) {
+ mapParallelIndexed { idx, item -> withPermit { block(idx, item) } }
+ }
+ } ?: mapParallelIndexed(block)
+
+private suspend fun <A, B> Iterable<A>.mapParallelIndexed(block: suspend (Int, A) -> B): List<B> =
+ coroutineScope {
+ mapIndexed { index, item ->
+ async {
+ yield()
+ block(index, item)
+ }
+ }
+ .awaitAll()
+ }
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 8f7c59de..cb88cd9e 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
@@ -48,6 +48,7 @@ val Kosmos.cursorPreviewsInteractor
get() =
CursorPreviewsInteractor(
interactor = setCursorPreviewsInteractor,
+ selectionInteractor = selectionInteractor,
focusedItemIdx = focusedItemIndex,
uriMetadataReader = uriMetadataReader,
pageSize = pageSize,
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 9eaee233..5d81ec2a 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
@@ -26,6 +26,7 @@ import android.service.chooser.AdditionalContentContract.Columns.URI
import android.util.Size
import com.android.intentresolver.util.cursor.get
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.any
@@ -101,6 +102,37 @@ class PayloadToggleCursorResolverTest {
assertThat(row.previewSize).isEqualTo(Size(100, 50))
}
}
+
+ @Test
+ fun testRowPositionValues() = runTest {
+ val rowCount = 10
+ val sourceCursor =
+ MatrixCursor(arrayOf(URI)).apply {
+ for (i in 1..rowCount) {
+ addRow(arrayOf(createUri(i).toString()))
+ }
+ }
+ val fakeContentProvider =
+ mock<ContentInterface> {
+ on { query(eq(cursorUri), any(), any(), any()) } doReturn sourceCursor
+ }
+ val testSubject =
+ PayloadToggleCursorResolver(
+ fakeContentProvider,
+ cursorUri,
+ chooserIntent,
+ )
+
+ val cursor = testSubject.getCursor()
+ assertThat(cursor).isNotNull()
+ assertThat(cursor!!.count).isEqualTo(rowCount)
+ for (i in 0 until rowCount) {
+ cursor[i].let { row ->
+ assertWithMessage("Row $i").that(row).isNotNull()
+ assertWithMessage("Row $i").that(row!!.position).isEqualTo(i)
+ }
+ }
+ }
}
private fun createUri(id: Int) = Uri.parse("content://org.pkg/app/img-$id.png")
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 0036e803..48e43190 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
@@ -27,6 +27,9 @@ import androidx.core.os.bundleOf
import com.android.intentresolver.contentpreview.FileInfo
import com.android.intentresolver.contentpreview.UriMetadataReader
import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.cursorPreviewsRepository
+import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.previewSelectionsRepository
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.targetIntentModifier
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.CursorRow
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
import com.android.intentresolver.contentpreview.readSize
@@ -59,6 +62,7 @@ class CursorPreviewsInteractorTest {
this.focusedItemIndex = focusedItemIndex
this.pageSize = pageSize
this.maxLoadedPages = maxLoadedPages
+ this.targetIntentModifier = TargetIntentModifier { error("unexpected invocation") }
uriMetadataReader =
object : UriMetadataReader {
override fun getMetadata(uri: Uri): FileInfo =
@@ -103,9 +107,15 @@ class CursorPreviewsInteractorTest {
)
}
}
- .viewBy { getString(0)?.let { uriStr -> CursorRow(Uri.parse(uriStr), readSize()) } }
+ .viewBy {
+ getString(0)?.let { uriStr ->
+ CursorRow(Uri.parse(uriStr), readSize(), position)
+ }
+ }
val initialPreviews: List<PreviewModel> =
- initialSelectionRange.map { i -> PreviewModel(uri = uri(i), mimeType = "image/bitmap") }
+ initialSelectionRange.map { i ->
+ PreviewModel(uri = uri(i), mimeType = "image/bitmap", order = i)
+ }
}
@Test
@@ -136,7 +146,8 @@ class CursorPreviewsInteractorTest {
0 -> 2f
3 -> 4f
else -> 1f
- }
+ },
+ order = it,
)
}
)
@@ -257,6 +268,34 @@ class CursorPreviewsInteractorTest {
.isEqualTo(Uri.fromParts("scheme24", "ssp24", "fragment24"))
assertThat(cursorPreviewsRepository.previewsModel.value!!.loadMoreLeft).isNull()
}
+
+ @Test
+ fun unclaimedRecordsGotUpdatedInSelectionInteractor() =
+ runTestWithDeps(
+ initialSelection = listOf(1),
+ focusedItemIndex = 0,
+ cursor = listOf(0, 1),
+ cursorStartPosition = 1,
+ ) { deps ->
+ previewSelectionsRepository.selections.value =
+ PreviewModel(
+ 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,
+ )
+ )
+ }
}
private fun uri(index: Int) = Uri.fromParts("scheme$index", "ssp$index", "fragment$index")
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt
index d04c984f..27c98dc0 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/FetchPreviewsInteractorTest.kt
@@ -27,6 +27,8 @@ import com.android.intentresolver.contentpreview.UriMetadataReader
import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.cursorPreviewsRepository
import com.android.intentresolver.contentpreview.payloadtoggle.domain.cursor.CursorResolver
import com.android.intentresolver.contentpreview.payloadtoggle.domain.cursor.payloadToggleCursorResolver
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier
+import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.targetIntentModifier
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.CursorRow
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel
@@ -76,6 +78,7 @@ class FetchPreviewsInteractorTest {
}
this.pageSize = pageSize
this.maxLoadedPages = maxLoadedPages
+ this.targetIntentModifier = TargetIntentModifier { error("unexpected invocation") }
runKosmosTest { block() }
}
}
@@ -99,13 +102,15 @@ class FetchPreviewsInteractorTest {
newRow().add("uri", uri(i).toString())
}
}
- .viewBy { getString(0)?.let(Uri::parse)?.let { CursorRow(it, null) } }
+ .viewBy { getString(0)?.let(Uri::parse)?.let { CursorRow(it, null, position) } }
}
}
@Test
fun setsInitialPreviews() =
- runTest(previewSizes = mapOf(1 to Size(100, 50))) {
+ runTest(
+ initialSelection = (1..3),
+ previewSizes = mapOf(1 to Size(100, 50))) {
backgroundScope.launch { fetchPreviewsInteractor.activate() }
runCurrent()
@@ -117,18 +122,25 @@ class FetchPreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
mimeType = "image/bitmap",
- aspectRatio = 2f
+ aspectRatio = 2f,
+ order = Int.MIN_VALUE,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
mimeType = "image/bitmap",
+ order = 0,
+ ),
+ PreviewModel(
+ uri = Uri.fromParts("scheme3", "ssp3", "fragment3"),
+ mimeType = "image/bitmap",
+ order = Int.MAX_VALUE,
),
),
startIdx = 1,
loadMoreLeft = null,
loadMoreRight = null,
leftTriggerIndex = 0,
- rightTriggerIndex = 1,
+ rightTriggerIndex = 2,
)
)
}
@@ -148,19 +160,23 @@ class FetchPreviewsInteractorTest {
.containsExactly(
PreviewModel(
uri = Uri.fromParts("scheme0", "ssp0", "fragment0"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 1,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 2,
),
PreviewModel(
uri = Uri.fromParts("scheme3", "ssp3", "fragment3"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 3,
),
)
.inOrder()
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 0275a9c3..f329b8a7 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
@@ -40,7 +40,11 @@ class SelectablePreviewInteractorTest {
val underTest =
SelectablePreviewInteractor(
key =
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null),
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ ),
selectionInteractor = selectionInteractor,
)
runCurrent()
@@ -56,7 +60,8 @@ class SelectablePreviewInteractorTest {
key =
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
),
selectionInteractor = selectionInteractor,
)
@@ -64,12 +69,12 @@ class SelectablePreviewInteractorTest {
assertThat(underTest.isSelected.first()).isFalse()
previewSelectionsRepository.selections.value =
- listOf(
- PreviewModel(
+ PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
)
- )
+ .let { mapOf(it.uri to it) }
runCurrent()
assertThat(underTest.isSelected.first()).isTrue()
@@ -84,7 +89,8 @@ class SelectablePreviewInteractorTest {
key =
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
),
selectionInteractor = selectionInteractor,
)
@@ -92,13 +98,8 @@ class SelectablePreviewInteractorTest {
underTest.setSelected(true)
runCurrent()
- assertThat(previewSelectionsRepository.selections.value)
- .containsExactly(
- PreviewModel(
- uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
- )
- )
+ assertThat(previewSelectionsRepository.selections.value.keys)
+ .containsExactly(Uri.fromParts("scheme", "ssp", "fragment"))
assertThat(chooserRequestRepository.chooserRequest.value.targetIntent)
.isSameInstanceAs(modifiedIntent)
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt
index 14b9c49c..c50d2d3f 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewsInteractorTest.kt
@@ -43,10 +43,12 @@ class SelectablePreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = "image/bitmap",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
mimeType = "image/bitmap",
+ order = 1,
),
),
startIdx = 0,
@@ -56,9 +58,12 @@ class SelectablePreviewsInteractorTest {
rightTriggerIndex = 1,
)
previewSelectionsRepository.selections.value =
- listOf(
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null),
- )
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ )
+ .let { mapOf(it.uri to it) }
targetIntentModifier = TargetIntentModifier { error("unexpected invocation") }
val underTest = selectablePreviewsInteractor
val keySet = underTest.previews.stateIn(backgroundScope)
@@ -68,11 +73,13 @@ class SelectablePreviewsInteractorTest {
.containsExactly(
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 1,
),
)
.inOrder()
@@ -82,13 +89,21 @@ class SelectablePreviewsInteractorTest {
val firstModel =
underTest.preview(
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ )
)
assertThat(firstModel.isSelected.first()).isTrue()
val secondModel =
underTest.preview(
- PreviewModel(uri = Uri.fromParts("scheme2", "ssp2", "fragment2"), mimeType = null)
+ PreviewModel(
+ uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
+ mimeType = null,
+ order = 1,
+ )
)
assertThat(secondModel.isSelected.first()).isFalse()
}
@@ -96,16 +111,23 @@ class SelectablePreviewsInteractorTest {
@Test
fun keySet_reflectsRepositoryUpdate() = runKosmosTest {
previewSelectionsRepository.selections.value =
- listOf(
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null),
- )
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ )
+ .let { mapOf(it.uri to it) }
targetIntentModifier = TargetIntentModifier { error("unexpected invocation") }
val underTest = selectablePreviewsInteractor
val previews = underTest.previews.stateIn(backgroundScope)
val firstModel =
underTest.preview(
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ )
)
assertThat(previews.value).isNull()
@@ -120,10 +142,12 @@ class SelectablePreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = "image/bitmap",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
mimeType = "image/bitmap",
+ order = 1,
),
),
startIdx = 5,
@@ -132,7 +156,7 @@ class SelectablePreviewsInteractorTest {
leftTriggerIndex = 0,
rightTriggerIndex = 1,
)
- previewSelectionsRepository.selections.value = emptyList()
+ previewSelectionsRepository.selections.value = emptyMap()
runCurrent()
assertThat(previews.value).isNotNull()
@@ -140,11 +164,13 @@ class SelectablePreviewsInteractorTest {
.containsExactly(
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
- mimeType = "image/bitmap"
+ mimeType = "image/bitmap",
+ order = 1,
),
)
.inOrder()
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 a50efebf..87db243d 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
@@ -23,14 +23,19 @@ import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.p
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.Test
class SelectionInteractorTest {
@Test
fun singleSelection_removalPrevented() = runKosmosTest {
val initialPreview =
- PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)
- previewSelectionsRepository.selections.value = listOf(initialPreview)
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0
+ )
+ previewSelectionsRepository.selections.value = mapOf(initialPreview.uri to initialPreview)
val underTest =
SelectionInteractor(
@@ -40,20 +45,29 @@ class SelectionInteractorTest {
mimetypeClassifier,
)
- assertThat(underTest.selections.value).containsExactly(initialPreview)
+ assertThat(underTest.selections.first()).containsExactly(initialPreview.uri)
// Shouldn't do anything!
underTest.unselect(initialPreview)
- assertThat(underTest.selections.value).containsExactly(initialPreview)
+ assertThat(underTest.selections.first()).containsExactly(initialPreview.uri)
}
@Test
fun multipleSelections_removalAllowed() = runKosmosTest {
- val first = PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null)
+ val first =
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0
+ )
val second =
- PreviewModel(uri = Uri.fromParts("scheme2", "ssp2", "fragment2"), mimeType = null)
- previewSelectionsRepository.selections.value = listOf(first, second)
+ PreviewModel(
+ uri = Uri.fromParts("scheme2", "ssp2", "fragment2"),
+ mimeType = null,
+ order = 1
+ )
+ previewSelectionsRepository.selections.value = listOf(first, second).associateBy { it.uri }
val underTest =
SelectionInteractor(
@@ -65,6 +79,6 @@ class SelectionInteractorTest {
underTest.unselect(first)
- assertThat(underTest.selections.value).containsExactly(second)
+ assertThat(underTest.selections.first()).containsExactly(second.uri)
}
}
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt
index a165b41e..748459cb 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SetCursorPreviewsInteractorTest.kt
@@ -39,6 +39,7 @@ class SetCursorPreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = null,
+ order = 0,
)
),
startIndex = 100,
@@ -60,6 +61,7 @@ class SetCursorPreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = null,
+ order = 0
)
)
.inOrder()
@@ -76,6 +78,7 @@ class SetCursorPreviewsInteractorTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = null,
+ order = 0,
)
),
startIndex = 100,
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 a26b4288..bb67e084 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
@@ -85,12 +85,14 @@ class ShareouselViewModelTest {
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 }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("IMAGES: 2")
}
@@ -102,12 +104,14 @@ class ShareouselViewModelTest {
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 }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("VIDEOS: 2")
}
@@ -119,12 +123,14 @@ class ShareouselViewModelTest {
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 }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 2")
}
@@ -154,10 +160,12 @@ class ShareouselViewModelTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = "image/png",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
mimeType = "video/mpeg",
+ order = 1,
)
),
startIdx = 1,
@@ -183,7 +191,8 @@ class ShareouselViewModelTest {
shareouselViewModel.preview.invoke(
PreviewModel(
uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
- mimeType = "video/mpeg"
+ mimeType = "video/mpeg",
+ order = 0,
),
/* index = */ 1,
viewModelScope,
@@ -199,8 +208,7 @@ class ShareouselViewModelTest {
previewVm.setSelected(true)
- assertThat(previewSelectionsRepository.selections.value)
- .comparingElementsUsingTransform("has uri of") { model: PreviewModel -> model.uri }
+ assertThat(previewSelectionsRepository.selections.value.keys)
.contains(Uri.fromParts("scheme1", "ssp1", "fragment1"))
}
@@ -214,10 +222,12 @@ class ShareouselViewModelTest {
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
mimeType = "image/png",
+ order = 0,
),
PreviewModel(
uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
mimeType = "video/mpeg",
+ order = 1,
)
),
startIdx = 1,
@@ -232,7 +242,8 @@ class ShareouselViewModelTest {
shareouselViewModel.preview.invoke(
PreviewModel(
uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "video/mpeg"
+ mimeType = "video/mpeg",
+ order = 1,
),
/* index = */ 1,
viewModelScope,
@@ -296,7 +307,11 @@ class ShareouselViewModelTest {
this.pendingIntentSender = pendingIntentSender
this.targetIntentModifier = targetIntentModifier
previewSelectionsRepository.selections.value =
- listOf(PreviewModel(uri = Uri.fromParts("scheme", "ssp", "fragment"), mimeType = null))
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ ).let { mapOf(it.uri to it) }
payloadToggleImageLoader =
FakeImageLoader(
initialBitmaps =