1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.providers.media.tools.photopickerv2.photopicker 17 18 import android.annotation.SuppressLint 19 import android.app.Application 20 import android.content.ActivityNotFoundException 21 import android.content.Intent 22 import android.net.Uri 23 import android.provider.MediaStore 24 import android.widget.Toast 25 import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.isPhotoPickerAvailable 26 import androidx.lifecycle.AndroidViewModel 27 import com.android.providers.media.tools.photopickerv2.utils.LaunchLocation 28 import kotlinx.coroutines.flow.MutableStateFlow 29 import kotlinx.coroutines.flow.StateFlow 30 31 /** 32 * PhotoPickerViewModel is responsible for managing the state and logic 33 * of the PhotoPicker feature. 34 */ 35 @SuppressLint("NewApi") 36 class PhotoPickerViewModel( 37 application: Application, 38 ) : AndroidViewModel(application) { 39 40 private val _selectedMedia = MutableStateFlow<List<Uri>>(emptyList()) 41 val selectedMedia: StateFlow<List<Uri>> = _selectedMedia 42 43 private val _pickImagesMaxSelectionLimit: Int 44 45 init{ 46 // If the PhotoPicker is available on the device, getPickImagesMaxLimit is there but not 47 // always visible on the SDK (only from Android 13+) 48 _pickImagesMaxSelectionLimit = if (isPhotoPickerAvailable(application)){ 49 val maxLimit = MediaStore.getPickImagesMaxLimit() 50 if (maxLimit > 0) maxLimit else Int.MAX_VALUE 51 } else { 52 Int.MAX_VALUE 53 } 54 } 55 updateSelectedMediaListnull56 fun updateSelectedMediaList(uris: List<Uri>) { 57 _selectedMedia.value = uris 58 } 59 validateAndLaunchPickernull60 fun validateAndLaunchPicker( 61 isActionGetContentSelected: Boolean, 62 allowMultiple: Boolean, 63 maxMediaItemsDisplayed: Int, 64 selectedMimeType: String, 65 allowCustomMimeType: Boolean, 66 customMimeTypeInput: String, 67 isOrderSelectionEnabled: Boolean, 68 selectedLaunchTab: LaunchLocation, 69 accentColor: String, 70 isPreSelectionEnabled: Boolean, 71 launcher: (Intent) -> Unit 72 ): String? { 73 if (!isActionGetContentSelected && allowMultiple){ 74 if (maxMediaItemsDisplayed <= 1) { 75 return "Enter a valid count greater than one" 76 } 77 78 if (maxMediaItemsDisplayed > _pickImagesMaxSelectionLimit) { 79 return "Set media item limit within $_pickImagesMaxSelectionLimit items" 80 } 81 } 82 83 if (accentColor == "") { 84 return "Enter an accent color" 85 } 86 87 val accentColorLong: Long = try { 88 android.graphics.Color.parseColor(accentColor).toLong() 89 } catch (e: IllegalArgumentException) { 90 android.graphics.Color.parseColor("#FF6200EE").toLong() // Default color 91 } 92 93 val intent = if (isActionGetContentSelected) { 94 // ACTION_GET_CONTENT supports only images and videos in the PhotoPicker tab 95 Intent(Intent.ACTION_GET_CONTENT).apply { 96 if (selectedMimeType == "image/*") type = "image/*" 97 else if (selectedMimeType == "video/*") type = "video/*" 98 else { 99 type = "image/*,video/*" 100 putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*")) 101 } 102 putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple) 103 addCategory(Intent.CATEGORY_OPENABLE) 104 } 105 } else { 106 Intent(MediaStore.ACTION_PICK_IMAGES).apply { 107 if (allowCustomMimeType) type = customMimeTypeInput 108 else if (selectedMimeType != "") type = selectedMimeType 109 110 putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple) 111 if (allowMultiple) { 112 putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxMediaItemsDisplayed) 113 } 114 putExtra( 115 MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, 116 if (selectedLaunchTab == LaunchLocation.ALBUMS_TAB) 0 else 1 117 ) 118 putExtra(MediaStore.EXTRA_PICK_IMAGES_IN_ORDER, isOrderSelectionEnabled) 119 putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColorLong) 120 if (isPreSelectionEnabled){ 121 Intent(putParcelableArrayListExtra( 122 "android.provider.extra.PICKER_PRE_SELECTION_URIS", 123 ArrayList(_selectedMedia.value) 124 )) 125 } 126 } 127 } 128 129 try { 130 launcher(intent) 131 } catch (e: ActivityNotFoundException) { 132 val errorMessage = 133 "No Activity found to handle Intent with type \"" + intent.type + "\"" 134 Toast.makeText(getApplication(), errorMessage, Toast.LENGTH_SHORT).show() 135 } 136 return null 137 } 138 }