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.NonNull; 25 import androidx.annotation.Nullable; 26 import androidx.annotation.VisibleForTesting; 27 28 import java.util.ArrayList; 29 30 /** 31 * Strategy for storing keys in saved state. Extend this class when using custom 32 * key types that aren't supported by default. Prefer use of builtin storage strategies: 33 * {@link #createStringStorage()}, {@link #createLongStorage()}, 34 * {@link #createParcelableStorage(Class)}. 35 * 36 * <p> 37 * See 38 * {@link androidx.recyclerview.selection.SelectionTracker.Builder SelectionTracker.Builder} 39 * for more detailed advice on which key type to use for your selection keys. 40 * 41 * @param <K> Selection key type. Built in support is provided for String, Long, and Parcelable 42 * types. Use the respective factory method to create a StorageStrategy instance 43 * appropriate to the desired type. 44 * {@link #createStringStorage()}, 45 * {@link #createParcelableStorage(Class)}, 46 * {@link #createLongStorage()} 47 */ 48 public abstract class StorageStrategy<K> { 49 50 @VisibleForTesting 51 static final String SELECTION_ENTRIES = "androidx.recyclerview.selection.entries"; 52 53 @VisibleForTesting 54 static final String SELECTION_KEY_TYPE = "androidx.recyclerview.selection.type"; 55 56 private final Class<K> mType; 57 58 /** 59 * Creates a new instance. 60 * 61 * @param type the key type class that is being used. 62 */ 63 public StorageStrategy(@NonNull Class<K> type) { 64 checkArgument(type != null); 65 mType = type; 66 } 67 68 /** 69 * Create a {@link Selection} from supplied {@link Bundle}. 70 * 71 * @param state Bundle instance that may contain parceled Selection instance. 72 * @return 73 */ 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 * @return 81 */ 82 public abstract @NonNull Bundle asBundle(@NonNull Selection<K> selection); 83 84 String getKeyTypeName() { 85 return mType.getCanonicalName(); 86 } 87 88 /** 89 * @return StorageStrategy suitable for use with {@link Parcelable} keys 90 * (like {@link android.net.Uri}). 91 */ 92 public static <K extends Parcelable> StorageStrategy<K> createParcelableStorage(Class<K> type) { 93 return new ParcelableStorageStrategy(type); 94 } 95 96 /** 97 * @return StorageStrategy suitable for use with {@link String} keys. 98 */ 99 public static StorageStrategy<String> createStringStorage() { 100 return new StringStorageStrategy(); 101 } 102 103 /** 104 * @return StorageStrategy suitable for use with {@link Long} keys. 105 */ 106 public static StorageStrategy<Long> createLongStorage() { 107 return new LongStorageStrategy(); 108 } 109 110 private static class StringStorageStrategy extends StorageStrategy<String> { 111 112 StringStorageStrategy() { 113 super(String.class); 114 } 115 116 @Override 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 @Nullable 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 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 151 LongStorageStrategy() { 152 super(Long.class); 153 } 154 155 @Override 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 @Nullable 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 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 194 ParcelableStorageStrategy(Class<K> type) { 195 super(type); 196 checkArgument(Parcelable.class.isAssignableFrom(type)); 197 } 198 199 @Override 200 public @Nullable Selection<K> asSelection(@NonNull Bundle state) { 201 202 String keyType = state.getString(SELECTION_KEY_TYPE, null); 203 if (keyType == null || !keyType.equals(getKeyTypeName())) { 204 return null; 205 } 206 207 @Nullable ArrayList<K> stored = state.getParcelableArrayList(SELECTION_ENTRIES); 208 if (stored == null) { 209 return null; 210 } 211 212 Selection<K> selection = new Selection<>(); 213 selection.mSelection.addAll(stored); 214 return selection; 215 } 216 217 @Override 218 public @NonNull Bundle asBundle(@NonNull Selection<K> selection) { 219 220 Bundle bundle = new Bundle(); 221 bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName()); 222 223 ArrayList<K> value = new ArrayList<>(selection.size()); 224 value.addAll(selection.mSelection); 225 bundle.putParcelableArrayList(SELECTION_ENTRIES, value); 226 227 return bundle; 228 } 229 } 230 } 231