1/*
2 * Copyright 2017 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
17package androidx.recyclerview.selection;
18
19import android.util.SparseArray;
20import android.view.View;
21
22import androidx.annotation.NonNull;
23import androidx.annotation.Nullable;
24import androidx.recyclerview.widget.RecyclerView;
25import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
26
27import java.util.HashMap;
28import java.util.Map;
29
30/**
31 * An {@link ItemKeyProvider} that provides stable ids by way of cached
32 * {@link RecyclerView.Adapter} stable ids. Items enter the cache as they are laid out by
33 * RecyclerView, and are removed from the cache as they are recycled.
34 *
35 * <p>
36 * There are trade-offs with this implementation as it necessarily auto-boxes {@code long}
37 * stable id values into {@code Long} values for use as selection keys. The core Selection API
38 * uses a parameterized key type to permit other keys (such as Strings or URIs).
39 */
40public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
41
42    private final SparseArray<Long> mPositionToKey = new SparseArray<>();
43    private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>();
44    private final RecyclerView mRecyclerView;
45
46    /**
47     * Creates a new key provider that uses cached {@code long} stable ids associated
48     * with the RecyclerView items.
49     *
50     * @param recyclerView the owner RecyclerView
51     */
52    public StableIdKeyProvider(@NonNull RecyclerView recyclerView) {
53
54        // Since this provide is based on stable ids based on whats laid out in the window
55        // we can only satisfy "window" scope key access.
56        super(SCOPE_CACHED);
57
58        mRecyclerView = recyclerView;
59
60        mRecyclerView.addOnChildAttachStateChangeListener(
61                new OnChildAttachStateChangeListener() {
62                    @Override
63                    public void onChildViewAttachedToWindow(View view) {
64                        onAttached(view);
65                    }
66
67                    @Override
68                    public void onChildViewDetachedFromWindow(View view) {
69                        onDetached(view);
70                    }
71                }
72        );
73
74    }
75
76    private void onAttached(@NonNull View view) {
77        RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view);
78        int position = holder.getAdapterPosition();
79        long id = holder.getItemId();
80        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
81            mPositionToKey.put(position, id);
82            mKeyToPosition.put(id, position);
83        }
84    }
85
86    private void onDetached(@NonNull View view) {
87        RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view);
88        int position = holder.getAdapterPosition();
89        long id = holder.getItemId();
90        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
91            mPositionToKey.delete(position);
92            mKeyToPosition.remove(id);
93        }
94    }
95
96    @Override
97    public @Nullable Long getKey(int position) {
98        return mPositionToKey.get(position, null);
99    }
100
101    @Override
102    public int getPosition(@NonNull Long key) {
103        if (mKeyToPosition.containsKey(key)) {
104            return mKeyToPosition.get(key);
105        }
106        return RecyclerView.NO_POSITION;
107    }
108}
109