summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Diya <diyaanem@google.com> 2024-07-25 12:08:56 +0000
committer Diya <diyaanem@google.com> 2024-07-27 18:40:17 +0000
commit05f272fdfcf0a64d506800ee8bd7d67f87761028 (patch)
tree5a8b5d09db3b5390a6a94aa572b215ed251ec86e
parentb9f06bdddb4813ca12a016f664c017434744674a (diff)
[PhotoPickerToolV2] Include DocsUI screen using ACTION_GET_CONTENT, OPEN_DOCUMENT and OPEN_DOCUMENT_TREE intents
Added the DocsUI tab using ACTION_GET_CONTENT, OPEN_DOCUMENT and OPEN_DOCUMENT_TREE intents. Included features like show images only, show videos only, custom mime type and multiple selection. Bug: 349514760 Test: m PhotoPickerToolV2 -j64 & adb install -r -d out/target/product/<lunch target name>/system/app/PhotoPickerToolV2/PhotoPickerToolV2.apk Flag: TEST_ONLY Change-Id: I909b9f1a6d26f30b182cdc1e02e49e2fa0211ee3
-rw-r--r--tools/photopickerV2/res/values/strings.xml2
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt307
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt63
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt13
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt10
5 files changed, 369 insertions, 26 deletions
diff --git a/tools/photopickerV2/res/values/strings.xml b/tools/photopickerV2/res/values/strings.xml
index 87cf043d7..d3ed2da7d 100644
--- a/tools/photopickerV2/res/values/strings.xml
+++ b/tools/photopickerV2/res/values/strings.xml
@@ -12,7 +12,7 @@
<string name="display_images_in_order">Display Images in Order</string>
<string name="show_images_only"> Show Images Only</string>
<string name="show_videos_only"> Show Videos Only</string>
- <string name="select_mime_type">Select Mime Type</string>
+ <string name="enter_mime_type">Enter Mime Type</string>
<string name="select_launch_tab">Select Launch Tab</string>
<string name="allow_multiple_selection">Allow Multiple Selection</string>
<string name="allow_custom_mime_type">Allow Custom Mime Type</string>
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
index 0201acaf1..407677953 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
@@ -15,29 +15,137 @@
*/
package com.android.providers.media.tools.photopickerv2.docsui
+import android.app.Activity
+import android.net.Uri
+import android.widget.Toast
+import android.widget.VideoView
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.providers.media.tools.photopickerv2.R
+import com.android.providers.media.tools.photopickerv2.utils.ButtonComponent
+import com.android.providers.media.tools.photopickerv2.utils.SwitchComponent
+import com.android.providers.media.tools.photopickerv2.utils.TextFieldComponent
+import com.android.providers.media.tools.photopickerv2.utils.isImage
+import com.android.providers.media.tools.photopickerv2.utils.resetMedia
+import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
+import com.bumptech.glide.integration.compose.GlideImage
/**
* This is the screen for the DocsUI tab.
*/
+@OptIn(ExperimentalGlideComposeApi::class)
@Composable
-fun DocsUIScreen() {
- Column (
- modifier = Modifier.fillMaxSize()
+fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
+ val context = LocalContext.current
+
+ // The ACTION_GET_CONTENT intent is selected by default
+ var selectedButton by remember { mutableStateOf<Int?>(R.string.action_get_content) }
+
+ var allowMultiple by remember { mutableStateOf(false) }
+
+ var isActionGetContentSelected by remember { mutableStateOf(true) }
+ var isOpenDocumentSelected by remember { mutableStateOf(false) }
+
+ var allowCustomMimeType by remember { mutableStateOf(false) }
+ var selectedMimeType by remember { mutableStateOf("") }
+ var customMimeTypeInput by remember { mutableStateOf("") }
+
+ var showImagesOnly by remember { mutableStateOf(false) }
+ var showVideosOnly by remember { mutableStateOf(false) }
+
+ // Color of ACTION_GET_CONTENT and OPEN_DOCUMENT button
+ val getContentColor = if (isActionGetContentSelected){
+ ButtonDefaults.buttonColors()
+ } else ButtonDefaults.buttonColors(Color.Gray)
+
+ val openDocumentColor = if (isOpenDocumentSelected) {
+ ButtonDefaults.buttonColors()
+ } else ButtonDefaults.buttonColors(Color.Gray)
+
+ val openDocumentTreeColor = if (!isActionGetContentSelected && !isOpenDocumentSelected) {
+ ButtonDefaults.buttonColors()
+ } else ButtonDefaults.buttonColors(Color.Gray)
+
+ // For handling the result of the photo picking activity
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ // Get the clipData containing multiple selected items.
+ val clipData = result.data?.clipData
+ val uris = mutableListOf<Uri>() // An empty list to store the selected URIs
+
+ // If multiple items are selected (clipData is not null), iterate through the items.
+ if (clipData != null) {
+ // Add each selected item to the URIs list,
+ // up to the maxMediaItemsDisplayed limit if multiple selection is allowed
+ for (i in 0 until clipData.itemCount) {
+ uris.add(clipData.getItemAt(i).uri)
+ }
+ } else {
+ // If only a single item is selected, add its URI to the list
+ result.data?.data?.let { uris.add(it) }
+ }
+
+ // Update the ViewModel with the list of selected URIs
+ docsUIViewModel.updateSelectedMediaList(uris)
+ }
+ }
+
+ val resultMedia by docsUIViewModel.selectedMedia.collectAsState()
+
+ fun resetFeatureComponents(
+ isGetContentSelected: Boolean,
+ isOpenDocumentIntentSelected: Boolean,
+ selectedButtonType: Int
+ ) {
+ isActionGetContentSelected = isGetContentSelected
+ isOpenDocumentSelected = isOpenDocumentIntentSelected
+ selectedButton = selectedButtonType
+ allowMultiple = false
+ showImagesOnly = false
+ showVideosOnly = false
+ selectedMimeType = ""
+ resetMedia(docsUIViewModel)
+ allowCustomMimeType = false
+ customMimeTypeInput = ""
+ }
+
+ Column(
+ modifier = Modifier
+ .verticalScroll(rememberScrollState())
+ .padding(16.dp)
+ .fillMaxWidth()
){
Text(
text = stringResource(id = R.string.tab_docsui),
@@ -45,17 +153,194 @@ fun DocsUIScreen() {
fontSize = 25.sp,
modifier = Modifier.padding(16.dp)
)
- Row(modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 100.dp),
+
+ Row (
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ){
+ ButtonComponent(
+ label = stringResource(id = R.string.action_get_content),
+ onClick = {
+ resetFeatureComponents(
+ isGetContentSelected = true,
+ isOpenDocumentIntentSelected = false,
+ selectedButtonType = R.string.action_get_content
+ )
+ },
+ modifier = Modifier.weight(1f),
+ colors = getContentColor
+ )
+
+ ButtonComponent(
+ label = stringResource(R.string.open_document),
+ onClick = {
+ resetFeatureComponents(
+ isGetContentSelected = false,
+ isOpenDocumentIntentSelected = true,
+ selectedButtonType = R.string.open_document
+ )
+ },
+ modifier = Modifier.weight(1f),
+ colors = openDocumentColor,
+ )
+ }
+
+ Row (
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
){
- Text(
- text = stringResource(id = R.string.working_on_it),
- fontWeight = FontWeight.Medium,
- fontSize = 40.sp,
+ ButtonComponent(
+ label = stringResource(id = R.string.open_document_tree),
+ onClick = {
+ resetFeatureComponents(
+ isGetContentSelected = false,
+ isOpenDocumentIntentSelected = false,
+ selectedButtonType = R.string.open_document_tree
+ )
+ },
+ modifier = Modifier.weight(1f),
+ colors = openDocumentTreeColor
)
}
+
+ if (isActionGetContentSelected || isOpenDocumentSelected){
+ // SHOW ONLY IMAGES OR VIDEOS
+ if (!allowCustomMimeType) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column (modifier = Modifier.weight(1f)){
+ SwitchComponent(
+ label = stringResource(R.string.show_images_only),
+ checked = showImagesOnly,
+ onCheckedChange = {
+ showImagesOnly = it
+ if (it) {
+ showVideosOnly = false
+ selectedMimeType = "image/*"
+ } else if (!showImagesOnly && !showVideosOnly) {
+ selectedMimeType = ""
+ }
+ }
+ )
+ }
+
+ Spacer(modifier = Modifier.width(6.dp))
+
+ Column (modifier = Modifier.weight(1f)){
+ SwitchComponent(
+ label = stringResource(R.string.show_videos_only),
+ checked = showVideosOnly,
+ onCheckedChange = {
+ showVideosOnly = it
+ if (it) {
+ showImagesOnly = false
+ selectedMimeType = "video/*"
+ } else if (!showImagesOnly && !showVideosOnly) {
+ selectedMimeType = ""
+ }
+ }
+ )
+ }
+ }
+ }
+
+ // Allow Custom Mime Type
+ SwitchComponent(
+ label = stringResource(id = R.string.allow_custom_mime_type),
+ checked = allowCustomMimeType,
+ onCheckedChange = {
+ allowCustomMimeType = it
+ }
+ )
+
+ if (allowCustomMimeType){
+ TextFieldComponent(
+ // Custom Mime Type Input
+ value = customMimeTypeInput,
+ onValueChange = { customMimeType ->
+ customMimeTypeInput = customMimeType
+ },
+ label = stringResource(id = R.string.enter_mime_type)
+ )
+ }
+
+ // Multiple Selection
+ SwitchComponent(
+ label = stringResource(id = R.string.allow_multiple_selection),
+ checked = allowMultiple,
+ onCheckedChange = {
+ allowMultiple = it
+ }
+ )
+ }
+
+ // Pick Media Button
+ ButtonComponent(
+ label = stringResource(R.string.pick_media),
+ onClick = {
+ // Resetting the custom Mime Type Box when allowCustomMimeType is unselected
+ if (!allowCustomMimeType){
+ customMimeTypeInput = ""
+ }
+
+ val errorMessage = docsUIViewModel.validateAndLaunchPicker(
+ isActionGetContentSelected = isActionGetContentSelected,
+ isOpenDocumentSelected = isOpenDocumentSelected,
+ allowMultiple = allowMultiple,
+ selectedMimeType = selectedMimeType,
+ allowCustomMimeType = allowCustomMimeType,
+ customMimeTypeInput = customMimeTypeInput,
+ launcher = launcher::launch
+ )
+ if (errorMessage != null) {
+ Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
+ }
+ }
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column {
+ resultMedia.forEach { uri ->
+ if (isImage(context, uri)) {
+ // To display image
+ GlideImage(
+ model = uri,
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxSize()
+ .padding(top = 8.dp)
+ )
+ } else {
+ AndroidView(
+ // To display video
+ factory = { ctx ->
+ VideoView(ctx).apply {
+ setVideoURI(uri)
+ start()
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp)
+ .padding(top = 8.dp)
+ )
+ }
+ Spacer(modifier = Modifier.height(20.dp))
+ HorizontalDivider(thickness = 6.dp)
+ Spacer(modifier = Modifier.height(17.dp))
+ }
+ }
}
} \ No newline at end of file
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
index 4449d227c..adc52e777 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
@@ -15,12 +15,67 @@
*/
package com.android.providers.media.tools.photopickerv2.docsui
-import androidx.lifecycle.ViewModel
+import android.app.Application
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.widget.Toast
+import androidx.lifecycle.AndroidViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
/**
* DocsUIViewModel is responsible for managing the state and logic
- * of the PhotoPicker feature.
+ * of the DocsUI feature.
*/
-class DocsUIViewModel() : ViewModel() {
- // Working on it
+class DocsUIViewModel(
+ application: Application,
+) : AndroidViewModel(application) {
+
+ private val _selectedMedia = MutableStateFlow<List<Uri>>(emptyList())
+ val selectedMedia: StateFlow<List<Uri>> = _selectedMedia
+
+ fun updateSelectedMediaList(uris: List<Uri>) {
+ _selectedMedia.value = uris
+ }
+
+ fun validateAndLaunchPicker(
+ isActionGetContentSelected: Boolean,
+ isOpenDocumentSelected: Boolean,
+ allowMultiple: Boolean,
+ selectedMimeType: String,
+ allowCustomMimeType: Boolean,
+ customMimeTypeInput: String,
+ launcher: (Intent) -> Unit
+ ): String? {
+
+ var finalMimeType = ""
+ if (allowCustomMimeType) finalMimeType = customMimeTypeInput
+ else if (selectedMimeType != "") finalMimeType = selectedMimeType
+ else finalMimeType = "*/*"
+
+ val intent = if (isActionGetContentSelected) {
+ Intent(Intent.ACTION_GET_CONTENT).apply {
+ type = finalMimeType
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
+ addCategory(Intent.CATEGORY_OPENABLE)
+ }
+ } else if (isOpenDocumentSelected) {
+ Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ type = finalMimeType
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
+ addCategory(Intent.CATEGORY_OPENABLE)
+ }
+ } else {
+ Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ }
+ try {
+ launcher(intent)
+ } catch (e: ActivityNotFoundException) {
+ val errorMessage =
+ "No Activity found to handle Intent with type \"" + intent.type + "\""
+ Toast.makeText(getApplication(), errorMessage, Toast.LENGTH_SHORT).show()
+ }
+ return null
+ }
} \ No newline at end of file
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
index 678edf60b..f72a110c3 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
@@ -267,17 +267,10 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
onValueChange = { customMimeType ->
customMimeTypeInput = customMimeType
},
- label = stringResource(id = R.string.select_mime_type)
+ label = stringResource(id = R.string.enter_mime_type)
)
}
- // To toggle show images only and show videos only switches
- if (showImagesOnly) {
- showVideosOnly = false
- } else if (showVideosOnly) {
- showImagesOnly = false
- }
-
Spacer(modifier = Modifier.height(16.dp))
// Launch Tab
@@ -328,7 +321,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
ButtonComponent(
label = stringResource(R.string.pick_media),
onClick = {
- // Resetting the maxSelection input box when allowMultiple is deselected
+ // Resetting the maxSelection input box when allowMultiple is unselected
if (!allowMultiple) {
maxSelectionLimitError = ""
selectionErrorMessage = ""
@@ -336,7 +329,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
maxMediaItemsDisplayed = 10
}
- // Resetting the custom Mime Type Box when allowCustomMimeType is deselected
+ // Resetting the custom Mime Type Box when allowCustomMimeType is unselected
if (!allowCustomMimeType) {
customMimeTypeInput = ""
}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
index a86198224..451ff6189 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
@@ -18,6 +18,7 @@ package com.android.providers.media.tools.photopickerv2.utils
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
+import com.android.providers.media.tools.photopickerv2.docsui.DocsUIViewModel
import com.android.providers.media.tools.photopickerv2.photopicker.PhotoPickerViewModel
/**
@@ -42,5 +43,14 @@ fun resetMedia(photoPickerViewModel: PhotoPickerViewModel) {
photoPickerViewModel.updateSelectedMediaList(emptyList())
}
+/**
+ * Resets the selected media in the provided DocsUIViewModel.
+ *
+ * @param docsUIViewModel The DocsUIViewModel instance to reset.
+ */
+fun resetMedia(docsUIViewModel: DocsUIViewModel) {
+ docsUIViewModel.updateSelectedMediaList(emptyList())
+}
+