diff options
| author | 2024-06-13 00:52:24 +0000 | |
|---|---|---|
| committer | 2024-06-13 00:52:24 +0000 | |
| commit | fdee007c90c3b06f5bba04c0730b20cc714c6a98 (patch) | |
| tree | 26f2cfe66276dc502c0fdb47d604e78b419377c2 /java/src | |
| parent | be10729ebacd2eeddc7f8828fcd2118878a3c68a (diff) | |
| parent | 1975528de9f1abcbfcebd4a4dadbf9858e9fe764 (diff) | |
Merge "Shareousel: Maintain cursor order for shated items" into main
Diffstat (limited to 'java/src')
9 files changed, 80 insertions, 30 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() + } |