diff options
author | 2024-07-25 12:08:56 +0000 | |
---|---|---|
committer | 2024-07-27 18:40:17 +0000 | |
commit | 05f272fdfcf0a64d506800ee8bd7d67f87761028 (patch) | |
tree | 5a8b5d09db3b5390a6a94aa572b215ed251ec86e /tools | |
parent | b9f06bdddb4813ca12a016f664c017434744674a (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
Diffstat (limited to 'tools')
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()) +} + |