1 /* 2 * Copyright 2018 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 androidx.recyclerview.selection; 18 19 import static androidx.core.util.Preconditions.checkArgument; 20 21 import android.os.Bundle; 22 import android.os.Parcelable; 23 24 import androidx.annotation.VisibleForTesting; 25 26 import org.jspecify.annotations.NonNull; 27 import org.jspecify.annotations.Nullable; 28 29 import java.util.ArrayList; 30 31 /** 32 * Strategy for storing keys in saved state. Extend this class when using custom 33 * key types that aren't supported by default. Prefer use of builtin storage strategies: 34 * {@link #createStringStorage()}, {@link #createLongStorage()}, 35 * {@link #createParcelableStorage(Class)}. 36 * 37 * <p> 38 * See 39 * {@link androidx.recyclerview.selection.SelectionTracker.Builder SelectionTracker.Builder} 40 * for more detailed advice on which key type to use for your selection keys. 41 * 42 * @param <K> Selection key type. Built in support is provided for String, Long, and Parcelable 43 * types. Use the respective factory method to create a StorageStrategy instance 44 * appropriate to the desired type. 45 * {@link #createStringStorage()}, 46 * {@link #createParcelableStorage(Class)}, 47 * {@link #createLongStorage()} 48 */ 49 public abstract class StorageStrategy<K> { 50 51 @VisibleForTesting 52 static final String SELECTION_ENTRIES = "androidx.recyclerview.selection.entries"; 53 54 @VisibleForTesting 55 static final String SELECTION_KEY_TYPE = "androidx.recyclerview.selection.type"; 56 57 private final Class<K> mType; 58 59 /** 60 * Creates a new instance. 61 * 62 * @param type the key type class that is being used. 63 */ StorageStrategy(@onNull Class<K> type)64 public StorageStrategy(@NonNull Class<K> type) { 65 checkArgument(type != null); 66 mType = type; 67 } 68 69 /** 70 * Create a {@link Selection} from supplied {@link Bundle}. 71 * 72 * @param state Bundle instance that may contain parceled Selection instance. 73 */ asSelection(@onNull Bundle state)74 public abstract @Nullable Selection<K> asSelection(@NonNull Bundle state); 75 76 /** 77 * Creates a {@link Bundle} from supplied {@link Selection}. 78 * 79 * @param selection The selection to asBundle. 80 */ asBundle(@onNull Selection<K> selection)81 public abstract @NonNull Bundle asBundle(@NonNull Selection<K> selection); 82 getKeyTypeName()83 String getKeyTypeName() { 84 return mType.getCanonicalName(); 85 } 86 87 /** 88 * @return StorageStrategy suitable for use with {@link Parcelable} keys 89 * (like {@link android.net.Uri}). 90 */ createParcelableStorage( @onNull Class<K> type)91 public static <K extends Parcelable> @NonNull StorageStrategy<K> createParcelableStorage( 92 @NonNull Class<K> type) { 93 return new ParcelableStorageStrategy<>(type); 94 } 95 96 /** 97 * @return StorageStrategy suitable for use with {@link String} keys. 98 */ createStringStorage()99 public static @NonNull StorageStrategy<String> createStringStorage() { 100 return new StringStorageStrategy(); 101 } 102 103 /** 104 * @return StorageStrategy suitable for use with {@link Long} keys. 105 */ createLongStorage()106 public static @NonNull StorageStrategy<Long> createLongStorage() { 107 return new LongStorageStrategy(); 108 } 109 110 private static class StringStorageStrategy extends StorageStrategy<String> { 111 StringStorageStrategy()112 StringStorageStrategy() { 113 super(String.class); 114 } 115 116 @Override asSelection(@onNull Bundle state)117 public @Nullable Selection<String> asSelection(@NonNull Bundle state) { 118 119 String keyType = state.getString(SELECTION_KEY_TYPE, null); 120 if (keyType == null || !keyType.equals(getKeyTypeName())) { 121 return null; 122 } 123 124 ArrayList<String> stored = state.getStringArrayList(SELECTION_ENTRIES); 125 if (stored == null) { 126 return null; 127 } 128 129 Selection<String> selection = new Selection<>(); 130 selection.mSelection.addAll(stored); 131 return selection; 132 } 133 134 @Override asBundle(@onNull Selection<String> selection)135 public @NonNull Bundle asBundle(@NonNull Selection<String> selection) { 136 137 Bundle bundle = new Bundle(); 138 139 bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName()); 140 141 ArrayList<String> value = new ArrayList<>(selection.size()); 142 value.addAll(selection.mSelection); 143 bundle.putStringArrayList(SELECTION_ENTRIES, value); 144 145 return bundle; 146 } 147 } 148 149 private static class LongStorageStrategy extends StorageStrategy<Long> { 150 LongStorageStrategy()151 LongStorageStrategy() { 152 super(Long.class); 153 } 154 155 @Override asSelection(@onNull Bundle state)156 public @Nullable Selection<Long> asSelection(@NonNull Bundle state) { 157 String keyType = state.getString(SELECTION_KEY_TYPE, null); 158 if (keyType == null || !keyType.equals(getKeyTypeName())) { 159 return null; 160 } 161 162 long[] stored = state.getLongArray(SELECTION_ENTRIES); 163 if (stored == null) { 164 return null; 165 } 166 167 Selection<Long> selection = new Selection<>(); 168 for (long key : stored) { 169 selection.mSelection.add(key); 170 } 171 return selection; 172 } 173 174 @Override asBundle(@onNull Selection<Long> selection)175 public @NonNull Bundle asBundle(@NonNull Selection<Long> selection) { 176 177 Bundle bundle = new Bundle(); 178 bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName()); 179 180 long[] value = new long[selection.size()]; 181 int i = 0; 182 for (Long key : selection) { 183 value[i++] = key; 184 } 185 bundle.putLongArray(SELECTION_ENTRIES, value); 186 187 return bundle; 188 } 189 } 190 191 private static class ParcelableStorageStrategy<K extends Parcelable> 192 extends StorageStrategy<K> { 193 ParcelableStorageStrategy(@onNull Class<K> type)194 ParcelableStorageStrategy(@NonNull Class<K> type) { 195 super(type); 196 checkArgument(Parcelable.class.isAssignableFrom(type)); 197 } 198 199 @Override 200 @SuppressWarnings("deprecation") asSelection(@onNull Bundle state)201 public @Nullable Selection<K> asSelection(@NonNull Bundle state) { 202 203 String keyType = state.getString(SELECTION_KEY_TYPE, null); 204 if (keyType == null || !keyType.equals(getKeyTypeName())) { 205 return null; 206 } 207 208 ArrayList<K> stored = state.getParcelableArrayList(SELECTION_ENTRIES); 209 if (stored == null) { 210 return null; 211 } 212 213 Selection<K> selection = new Selection<>(); 214 selection.mSelection.addAll(stored); 215 return selection; 216 } 217 218 @Override asBundle(@onNull Selection<K> selection)219 public @NonNull Bundle asBundle(@NonNull Selection<K> selection) { 220 221 Bundle bundle = new Bundle(); 222 bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName()); 223 224 ArrayList<K> value = new ArrayList<>(selection.size()); 225 value.addAll(selection.mSelection); 226 bundle.putParcelableArrayList(SELECTION_ENTRIES, value); 227 228 return bundle; 229 } 230 } 231 } 232