1 /* 2 * Copyright (C) 2021 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 17 package com.android.providers.media.photopicker.data; 18 19 import android.content.Intent; 20 import android.net.Uri; 21 import android.os.Bundle; 22 import android.provider.MediaStore; 23 24 import androidx.lifecycle.LiveData; 25 import androidx.lifecycle.MutableLiveData; 26 27 import com.android.providers.media.photopicker.data.model.Item; 28 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 35 /** 36 * A class that tracks Selection 37 */ 38 public class Selection { 39 // The list of selected items. 40 private Map<Uri, Item> mSelectedItems = new HashMap<>(); 41 private MutableLiveData<Integer> mSelectedItemSize = new MutableLiveData<>(); 42 // The list of selected items for preview. This needs to be saved separately so that if activity 43 // gets killed, we will still have deselected items for preview. 44 private List<Item> mSelectedItemsForPreview = new ArrayList<>(); 45 private boolean mSelectMultiple = false; 46 private int mMaxSelectionLimit = 1; 47 // This is set to false when max selection limit is reached. 48 private boolean mIsSelectionAllowed = true; 49 50 /** 51 * @return {@link #mSelectedItems} - A {@link List} of selected {@link Item} 52 */ getSelectedItems()53 public List<Item> getSelectedItems() { 54 return Collections.unmodifiableList(new ArrayList<>(mSelectedItems.values())); 55 } 56 57 /** 58 * @return {@link LiveData} of count of selected items in {@link #mSelectedItems} 59 */ getSelectedItemCount()60 public LiveData<Integer> getSelectedItemCount() { 61 if (mSelectedItemSize.getValue() == null) { 62 mSelectedItemSize.setValue(mSelectedItems.size()); 63 } 64 return mSelectedItemSize; 65 } 66 67 /** 68 * Add the selected {@code item} into {@link #mSelectedItems}. 69 */ addSelectedItem(Item item)70 public void addSelectedItem(Item item) { 71 mSelectedItems.put(item.getContentUri(), item); 72 mSelectedItemSize.postValue(mSelectedItems.size()); 73 updateSelectionAllowed(); 74 } 75 76 /** 77 * Clears {@link #mSelectedItems} and sets the selected item as given {@code item} 78 */ setSelectedItem(Item item)79 public void setSelectedItem(Item item) { 80 mSelectedItems.clear(); 81 mSelectedItems.put(item.getContentUri(), item); 82 mSelectedItemSize.postValue(mSelectedItems.size()); 83 updateSelectionAllowed(); 84 } 85 86 /** 87 * Remove the {@code item} from the selected item list {@link #mSelectedItems}. 88 * 89 * @param item the item to be removed from the selected item list 90 */ removeSelectedItem(Item item)91 public void removeSelectedItem(Item item) { 92 mSelectedItems.remove(item.getContentUri()); 93 mSelectedItemSize.postValue(mSelectedItems.size()); 94 updateSelectionAllowed(); 95 } 96 97 /** 98 * Clear all selected items 99 */ clearSelectedItems()100 public void clearSelectedItems() { 101 mSelectedItems.clear(); 102 mSelectedItemSize.postValue(mSelectedItems.size()); 103 updateSelectionAllowed(); 104 } 105 106 /** 107 * @return {@code true} if give {@code item} is present in selected items 108 * {@link #mSelectedItems}, {@code false} otherwise 109 */ isItemSelected(Item item)110 public boolean isItemSelected(Item item) { 111 return mSelectedItems.containsKey(item.getContentUri()); 112 } 113 updateSelectionAllowed()114 private void updateSelectionAllowed() { 115 final int size = mSelectedItems.size(); 116 if (size >= mMaxSelectionLimit) { 117 if (mIsSelectionAllowed) { 118 mIsSelectionAllowed = false; 119 } 120 } else { 121 // size < mMaxSelectionLimit 122 if (!mIsSelectionAllowed) { 123 mIsSelectionAllowed = true; 124 } 125 } 126 } 127 128 /** 129 * @return returns whether more items can be selected or not. {@code true} if the number of 130 * selected items is lower than or equal to {@code mMaxLimit}, {@code false} otherwise. 131 */ isSelectionAllowed()132 public boolean isSelectionAllowed() { 133 return mIsSelectionAllowed; 134 } 135 136 /** 137 * Prepares current selected items for previewing all selected items in multi-select preview. 138 * The method also sorts the selected items by {@link Item#compareTo} method which sorts based 139 * on dateTaken values. 140 */ prepareSelectedItemsForPreviewAll()141 public void prepareSelectedItemsForPreviewAll() { 142 mSelectedItemsForPreview = new ArrayList<>(mSelectedItems.values()); 143 mSelectedItemsForPreview.sort(Collections.reverseOrder(Item::compareTo)); 144 } 145 146 /** 147 * Sets the given {@code item} as the item for previewing. This method will be used while 148 * previewing on long press. 149 */ prepareItemForPreviewOnLongPress(Item item)150 public void prepareItemForPreviewOnLongPress(Item item) { 151 mSelectedItemsForPreview = Collections.singletonList(item); 152 } 153 154 /** 155 * @return {@link #mSelectedItemsForPreview} - selected items for preview. 156 */ getSelectedItemsForPreview()157 public List<Item> getSelectedItemsForPreview() { 158 return Collections.unmodifiableList(mSelectedItemsForPreview); 159 } 160 161 /** 162 * Parse values from {@code intent} and set corresponding fields 163 */ parseSelectionValuesFromIntent(Intent intent)164 public void parseSelectionValuesFromIntent(Intent intent) { 165 final Bundle extras = intent.getExtras(); 166 final boolean isExtraPickImagesMaxSet = 167 extras != null && extras.containsKey(MediaStore.EXTRA_PICK_IMAGES_MAX); 168 169 // Support Intent.EXTRA_ALLOW_MULTIPLE flag only for ACTION_GET_CONTENT 170 if (intent.getAction() != null && intent.getAction().equals( 171 Intent.ACTION_GET_CONTENT)) { 172 if (isExtraPickImagesMaxSet) { 173 throw new IllegalArgumentException("EXTRA_PICK_IMAGES_MAX is not supported for " 174 + "ACTION_GET_CONTENT"); 175 } 176 177 mSelectMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); 178 if (mSelectMultiple) { 179 mMaxSelectionLimit = MediaStore.getPickImagesMaxLimit(); 180 } 181 return; 182 } 183 184 // Check EXTRA_PICK_IMAGES_MAX value only if the flag is set. 185 if (isExtraPickImagesMaxSet) { 186 final int extraMax = intent.getIntExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 187 /* defaultValue */ -1); 188 // Multi selection max limit should always be greater than 1 and less than or equal 189 // to PICK_IMAGES_MAX_LIMIT. 190 if (extraMax <= 1 || extraMax > MediaStore.getPickImagesMaxLimit()) { 191 throw new IllegalArgumentException("Invalid EXTRA_PICK_IMAGES_MAX value"); 192 } 193 mSelectMultiple = true; 194 mMaxSelectionLimit = extraMax; 195 } 196 } 197 198 /** 199 * Return whether supports multiple select {@link #mSelectMultiple} or not 200 */ canSelectMultiple()201 public boolean canSelectMultiple() { 202 return mSelectMultiple; 203 } 204 205 /** 206 * Return maximum limit of items that can be selected 207 */ getMaxSelectionLimit()208 public int getMaxSelectionLimit() { 209 return mMaxSelectionLimit; 210 } 211 }