StorageStrategy.java

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.recyclerview.selection;

import static androidx.core.util.Preconditions.checkArgument;

import android.os.Bundle;
import android.os.Parcelable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import java.util.ArrayList;

/**
 * Strategy for storing keys in saved state. Extend this class when using custom
 * key types that aren't supported by default. Prefer use of builtin storage strategies:
 * {@link #createStringStorage()}, {@link #createLongStorage()},
 * {@link #createParcelableStorage(Class)}.
 *
 * <p>
 * See
 * {@link androidx.recyclerview.selection.SelectionTracker.Builder SelectionTracker.Builder}
 * for more detailed advice on which key type to use for your selection keys.
 *
 * @param <K> Selection key type. Built in support is provided for String, Long, and Parcelable
 *            types. Use the respective factory method to create a StorageStrategy instance
 *            appropriate to the desired type.
 *            {@link #createStringStorage()},
 *            {@link #createParcelableStorage(Class)},
 *            {@link #createLongStorage()}
 */
public abstract class StorageStrategy<K> {

    @VisibleForTesting
    static final String SELECTION_ENTRIES = "androidx.recyclerview.selection.entries";

    @VisibleForTesting
    static final String SELECTION_KEY_TYPE = "androidx.recyclerview.selection.type";

    private final Class<K> mType;

    /**
     * Creates a new instance.
     *
     * @param type the key type class that is being used.
     */
    public StorageStrategy(@NonNull Class<K> type) {
        checkArgument(type != null);
        mType = type;
    }

    /**
     * Create a {@link Selection} from supplied {@link Bundle}.
     *
     * @param state Bundle instance that may contain parceled Selection instance.
     */
    public abstract @Nullable Selection<K> asSelection(@NonNull Bundle state);

    /**
     * Creates a {@link Bundle} from supplied {@link Selection}.
     *
     * @param selection The selection to asBundle.
     */
    public abstract @NonNull Bundle asBundle(@NonNull Selection<K> selection);

    String getKeyTypeName() {
        return mType.getCanonicalName();
    }

    /**
     * @return StorageStrategy suitable for use with {@link Parcelable} keys
     * (like {@link android.net.Uri}).
     */
    public static @NonNull <K extends Parcelable> StorageStrategy<K> createParcelableStorage(
            @NonNull Class<K> type) {
        return new ParcelableStorageStrategy<>(type);
    }

    /**
     * @return StorageStrategy suitable for use with {@link String} keys.
     */
    public static @NonNull StorageStrategy<String> createStringStorage() {
        return new StringStorageStrategy();
    }

    /**
     * @return StorageStrategy suitable for use with {@link Long} keys.
     */
    public static @NonNull StorageStrategy<Long> createLongStorage() {
        return new LongStorageStrategy();
    }

    private static class StringStorageStrategy extends StorageStrategy<String> {

        StringStorageStrategy() {
            super(String.class);
        }

        @Override
        public @Nullable Selection<String> asSelection(@NonNull Bundle state) {

            String keyType = state.getString(SELECTION_KEY_TYPE, null);
            if (keyType == null || !keyType.equals(getKeyTypeName())) {
                return null;
            }

            @Nullable ArrayList<String> stored = state.getStringArrayList(SELECTION_ENTRIES);
            if (stored == null) {
                return null;
            }

            Selection<String> selection = new Selection<>();
            selection.mSelection.addAll(stored);
            return selection;
        }

        @Override
        public @NonNull Bundle asBundle(@NonNull Selection<String> selection) {

            Bundle bundle = new Bundle();

            bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName());

            ArrayList<String> value = new ArrayList<>(selection.size());
            value.addAll(selection.mSelection);
            bundle.putStringArrayList(SELECTION_ENTRIES, value);

            return bundle;
        }
    }

    private static class LongStorageStrategy extends StorageStrategy<Long> {

        LongStorageStrategy() {
            super(Long.class);
        }

        @Override
        public @Nullable Selection<Long> asSelection(@NonNull Bundle state) {
            String keyType = state.getString(SELECTION_KEY_TYPE, null);
            if (keyType == null || !keyType.equals(getKeyTypeName())) {
                return null;
            }

            @Nullable long[] stored = state.getLongArray(SELECTION_ENTRIES);
            if (stored == null) {
                return null;
            }

            Selection<Long> selection = new Selection<>();
            for (long key : stored) {
                selection.mSelection.add(key);
            }
            return selection;
        }

        @Override
        public @NonNull Bundle asBundle(@NonNull Selection<Long> selection) {

            Bundle bundle = new Bundle();
            bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName());

            long[] value = new long[selection.size()];
            int i = 0;
            for (Long key : selection) {
                value[i++] = key;
            }
            bundle.putLongArray(SELECTION_ENTRIES, value);

            return bundle;
        }
    }

    private static class ParcelableStorageStrategy<K extends Parcelable>
            extends StorageStrategy<K> {

        ParcelableStorageStrategy(@NonNull Class<K> type) {
            super(type);
            checkArgument(Parcelable.class.isAssignableFrom(type));
        }

        @Override
        public @Nullable Selection<K> asSelection(@NonNull Bundle state) {

            String keyType = state.getString(SELECTION_KEY_TYPE, null);
            if (keyType == null || !keyType.equals(getKeyTypeName())) {
                return null;
            }

            @Nullable ArrayList<K> stored = state.getParcelableArrayList(SELECTION_ENTRIES);
            if (stored == null) {
                return null;
            }

            Selection<K> selection = new Selection<>();
            selection.mSelection.addAll(stored);
            return selection;
        }

        @Override
        public @NonNull Bundle asBundle(@NonNull Selection<K> selection) {

            Bundle bundle = new Bundle();
            bundle.putString(SELECTION_KEY_TYPE, getKeyTypeName());

            ArrayList<K> value = new ArrayList<>(selection.size());
            value.addAll(selection.mSelection);
            bundle.putParcelableArrayList(SELECTION_ENTRIES, value);

            return bundle;
        }
    }
}