summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Andrey Epin <ayepin@google.com> 2024-02-08 20:19:40 -0800
committer Andrey Epin <ayepin@google.com> 2024-02-12 21:06:03 -0800
commit3fa28761c639dfde7dbe1929de42cd015cf57af9 (patch)
treeb330139a01ac118b75d688d43ce752a1797db87f /java/src
parenta5162406bf48d155d3927c33e51aeee4368a24ff (diff)
Initialize PlayloadToggleInteractor in PreviewViewModel
Initialize PlayloadToggleInteractor in PreviewViewModel when the flag is enabled. Bug: 302691505 Test: IntentResolver-tests-unit (Both V2 flag values) Test: IntentResolver-tests-integration (Both V2 flag values) Test: IntentResolver-tests-activity (Both V2 flag values) Test: Functinality smoke test (orentation change, different preview types, target selection flow) for both V2 flag values. Change-Id: I5899b0dd25b9482e56d17bdcad57a0aaa4600734
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java8
-rw-r--r--java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt16
-rw-r--r--java/src/com/android/intentresolver/contentpreview/CursorUriReader.kt46
-rw-r--r--java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt105
-rw-r--r--java/src/com/android/intentresolver/contentpreview/TargetIntentModifier.kt7
-rw-r--r--java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt40
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java9
8 files changed, 203 insertions, 31 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 843ae809..9b4582df 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -303,9 +303,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
BasePreviewViewModel previewViewModel =
new ViewModelProvider(this, createPreviewViewModelFactory())
.get(BasePreviewViewModel.class);
+ previewViewModel.init(
+ mChooserRequest.getTargetIntent(),
+ getIntent(),
+ /*additionalContentUri = */ null,
+ /*focusedItemIdx = */ 0,
+ /*isPayloadTogglingEnabled = */ false);
mChooserContentPreviewUi = new ChooserContentPreviewUi(
getCoroutineScope(getLifecycle()),
- previewViewModel.createOrReuseProvider(mChooserRequest.getTargetIntent()),
+ previewViewModel.getPreviewDataProvider(),
mChooserRequest.getTargetIntent(),
previewViewModel.getImageLoader(),
createChooserActionFactory(),
diff --git a/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt
index 3b20a45c..21c909ea 100644
--- a/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/BasePreviewViewModel.kt
@@ -17,14 +17,22 @@
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() {
- @MainThread abstract fun createOrReuseProvider(targetIntent: Intent): PreviewDataProvider
-
- abstract val imageLoader: ImageLoader
-
+ @get:MainThread abstract val previewDataProvider: PreviewDataProvider
+ @get:MainThread abstract val imageLoader: ImageLoader
abstract val payloadToggleInteractor: PayloadToggleInteractor?
+
+ @MainThread
+ abstract fun init(
+ targetIntent: Intent,
+ chooserIntent: Intent,
+ additionalContentUri: Uri?,
+ focusedItemIdx: Int,
+ isPayloadTogglingEnabled: Boolean,
+ )
}
diff --git a/java/src/com/android/intentresolver/contentpreview/CursorUriReader.kt b/java/src/com/android/intentresolver/contentpreview/CursorUriReader.kt
index dbf27a88..7ff3b49e 100644
--- a/java/src/com/android/intentresolver/contentpreview/CursorUriReader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/CursorUriReader.kt
@@ -16,10 +16,22 @@
package com.android.intentresolver.contentpreview
+import android.content.ContentInterface
+import android.content.Intent
import android.database.Cursor
+import android.database.MatrixCursor
import android.net.Uri
+import android.os.Bundle
+import android.os.CancellationSignal
import android.util.Log
import android.util.SparseArray
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.coroutineScope
+
+// TODO: replace with the new API AdditionalContentContract$Columns#URI
+private const val ColumnUri = "uri"
+// TODO: replace with the new API AdditionalContentContract$CursorExtraKeys#POSITION
+private const val ExtraPosition = "position"
private const val TAG = ContentPreviewUi.TAG
@@ -98,4 +110,38 @@ class CursorUriReader(
override fun close() {
cursor.close()
}
+
+ companion object {
+ suspend fun createCursorReader(
+ contentResolver: ContentInterface,
+ uri: Uri,
+ chooserIntent: Intent
+ ): CursorUriReader {
+ val cancellationSignal = CancellationSignal()
+ val cursor =
+ try {
+ coroutineScope {
+ runCatching {
+ contentResolver.query(
+ uri,
+ arrayOf(ColumnUri),
+ Bundle().apply {
+ putParcelable(Intent.EXTRA_INTENT, chooserIntent)
+ },
+ cancellationSignal
+ )
+ }
+ .getOrNull()
+ ?: MatrixCursor(arrayOf(ColumnUri))
+ }
+ } catch (e: CancellationException) {
+ cancellationSignal.cancel()
+ throw e
+ }
+ return CursorUriReader(cursor, cursor.extras?.getInt(ExtraPosition, 0) ?: 0, 128) {
+ // TODO: check that authority is case-sensitive for resolution reasons
+ it.authority != uri.authority
+ }
+ }
+ }
}
diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
index 38918d79..659f7dc9 100644
--- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
+++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt
@@ -100,6 +100,9 @@ constructor(
open val uriCount: Int
get() = records.size
+ val uris: List<Uri>
+ get() = records.map { it.uri }
+
/**
* Returns a [Flow] of [FileInfo], for each shared URI in order, with [FileInfo.mimeType] and
* [FileInfo.previewUri] set (a data projection tailored for the image preview UI).
diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt
index 77cf0ac9..7369fa0f 100644
--- a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt
@@ -17,7 +17,9 @@
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
@@ -26,46 +28,90 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import com.android.intentresolver.R
import com.android.intentresolver.inject.Background
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
+import java.util.concurrent.Executors
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.plus
-/** A trivial view model to keep a [PreviewDataProvider] instance over a configuration change */
-@HiltViewModel
-class PreviewViewModel
-@Inject
-constructor(
- private val application: Application,
+/** 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 previewDataProvider: PreviewDataProvider? = null
+ private var targetIntent: Intent? = null
+ private var chooserIntent: Intent? = null
+ private var additionalContentUri: Uri? = null
+ private var focusedItemIdx: Int = 0
+ private var isPayloadTogglingEnabled = false
- @MainThread
- override fun createOrReuseProvider(targetIntent: Intent): PreviewDataProvider =
- previewDataProvider
- ?: PreviewDataProvider(
- viewModelScope + dispatcher,
- targetIntent,
- application.contentResolver
- )
- .also { previewDataProvider = it }
+ override val previewDataProvider by lazy {
+ val targetIntent = requireNotNull(this.targetIntent) { "Not initialized" }
+ PreviewDataProvider(viewModelScope + dispatcher, targetIntent, contentResolver)
+ }
override val imageLoader by lazy {
ImagePreviewImageLoader(
viewModelScope + dispatcher,
- thumbnailSize =
- application.resources.getDimensionPixelSize(
- R.dimen.chooser_preview_image_max_dimen
- ),
- application.contentResolver,
+ thumbnailSize,
+ contentResolver,
cacheSize = 16
)
}
override val payloadToggleInteractor: PayloadToggleInteractor? by lazy {
- null // TODO: initialize PayloadToggleInteractor()
+ val targetIntent = requireNotNull(targetIntent) { "Not initialized" }
+ // TODO: replace with flags injection
+ if (!isPayloadTogglingEnabled) return@lazy null
+ createPayloadToggleInteractor(
+ additionalContentUri ?: return@lazy null,
+ targetIntent,
+ chooserIntent ?: return@lazy null,
+ )
+ .apply { start() }
+ }
+
+ // TODO: make the view model injectable and inject these dependencies instead
+ @MainThread
+ override fun init(
+ targetIntent: Intent,
+ chooserIntent: Intent,
+ additionalContentUri: Uri?,
+ focusedItemIdx: Int,
+ isPayloadTogglingEnabled: Boolean,
+ ) {
+ if (this.targetIntent != null) return
+ this.targetIntent = targetIntent
+ this.chooserIntent = chooserIntent
+ this.additionalContentUri = additionalContentUri
+ this.focusedItemIdx = focusedItemIdx
+ this.isPayloadTogglingEnabled = isPayloadTogglingEnabled
+ }
+
+ private fun createPayloadToggleInteractor(
+ contentProviderUri: Uri,
+ targetIntent: Intent,
+ chooserIntent: Intent,
+ ): PayloadToggleInteractor {
+ return PayloadToggleInteractor(
+ // TODO: update PayloadToggleInteractor to support multiple threads
+ viewModelScope + Executors.newSingleThreadScheduledExecutor().asCoroutineDispatcher(),
+ previewDataProvider.uris,
+ maxOf(0, minOf(focusedItemIdx, previewDataProvider.uriCount - 1)),
+ DefaultMimeTypeClassifier,
+ {
+ CursorUriReader.createCursorReader(
+ contentResolver,
+ contentProviderUri,
+ chooserIntent
+ )
+ },
+ UriMetadataReader(contentResolver, DefaultMimeTypeClassifier),
+ TargetIntentModifier(targetIntent, getUri = { uri }, getMimeType = { mimeType }),
+ SelectionChangeCallback(contentProviderUri, chooserIntent, contentResolver)
+ )
}
companion object {
@@ -75,7 +121,16 @@ constructor(
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
- ): T = PreviewViewModel(checkNotNull(extras[APPLICATION_KEY])) as T
+ ): 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/contentpreview/TargetIntentModifier.kt b/java/src/com/android/intentresolver/contentpreview/TargetIntentModifier.kt
index 99cfc0f8..d7e04920 100644
--- a/java/src/com/android/intentresolver/contentpreview/TargetIntentModifier.kt
+++ b/java/src/com/android/intentresolver/contentpreview/TargetIntentModifier.kt
@@ -16,6 +16,7 @@
package com.android.intentresolver.contentpreview
+import android.content.ClipData
import android.content.ClipDescription.compareMimeTypes
import android.content.Intent
import android.content.Intent.ACTION_SEND
@@ -45,6 +46,12 @@ class TargetIntentModifier<Item>(
} else {
putParcelableArrayListExtra(EXTRA_STREAM, uris)
}
+ clipData =
+ ClipData("", arrayOf(targetMimeType), ClipData.Item(uris[0])).also {
+ for (i in 1 until uris.size) {
+ it.addItem(ClipData.Item(uris[i]))
+ }
+ }
}
}
diff --git a/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt b/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt
new file mode 100644
index 00000000..784cefa0
--- /dev/null
+++ b/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.content.ContentResolver
+import android.net.Uri
+
+// TODO: share this logic with PreviewDataProvider
+class UriMetadataReader(
+ private val contentResolver: ContentResolver,
+ private val mimeTypeClassifier: MimeTypeClassifier,
+) : (Uri) -> FileInfo {
+ fun getMetadata(uri: Uri): FileInfo =
+ FileInfo.Builder(uri)
+ .apply {
+ runCatching {
+ withMimeType(contentResolver.getType(uri))
+ if (mimeTypeClassifier.isImageType(mimeType)) {
+ withPreviewUri(uri)
+ }
+ }
+ }
+ .build()
+
+ override fun invoke(uri: Uri): FileInfo = getMetadata(uri)
+}
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
index 30845818..2ffd31d8 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -268,6 +268,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Inject public ActivityLaunch mActivityLaunch;
@Inject public FeatureFlags mFeatureFlags;
+ @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags;
@Inject public EventLog mEventLog;
@Inject @AppPredictionAvailable public boolean mAppPredictionAvailable;
@Inject @ImageEditor public Optional<ComponentName> mImageEditor;
@@ -480,9 +481,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
BasePreviewViewModel previewViewModel =
new ViewModelProvider(this, createPreviewViewModelFactory())
.get(BasePreviewViewModel.class);
+ previewViewModel.init(
+ chooserRequest.getTargetIntent(),
+ getIntent(),
+ chooserRequest.getAdditionalContentUri(),
+ chooserRequest.getFocusedItemPosition(),
+ mChooserServiceFeatureFlags.chooserPayloadToggling());
mChooserContentPreviewUi = new ChooserContentPreviewUi(
getCoroutineScope(getLifecycle()),
- previewViewModel.createOrReuseProvider(chooserRequest.getTargetIntent()),
+ previewViewModel.getPreviewDataProvider(),
chooserRequest.getTargetIntent(),
previewViewModel.getImageLoader(),
createChooserActionFactory(),