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 
17 package androidx.recyclerview.selection;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20 import static androidx.core.util.Preconditions.checkArgument;
21 import static androidx.recyclerview.selection.Shared.VERBOSE;
22 
23 import android.util.Log;
24 
25 import androidx.annotation.RestrictTo;
26 import androidx.core.util.Consumer;
27 import androidx.recyclerview.widget.RecyclerView;
28 
29 import org.jspecify.annotations.NonNull;
30 
31 /**
32  * Provides the necessary glue to notify RecyclerView when selection data changes,
33  * and to notify SelectionTracker when the underlying RecyclerView.Adapter data changes.
34  *
35  * This strict decoupling is necessary to permit a single SelectionTracker to work
36  * with multiple RecyclerView instances. This may be necessary when multiple
37  * different views of data are presented to the user.
38  *
39  */
40 @RestrictTo(LIBRARY)
41 public class EventBridge {
42 
43     private static final String TAG = "EventsRelays";
44 
45     /**
46      * Installs the event bridge for on the supplied adapter/helper.
47      *
48      * @param adapter
49      * @param selectionTracker
50      * @param keyProvider
51      * @param runner Callback allowing operation to be run at next opportune time.
52      *                   Implementation could be {@link RecyclerView#postOnAnimation(Runnable)}.
53      *
54      * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
55      */
install( RecyclerView.@onNull Adapter<?> adapter, @NonNull SelectionTracker<K> selectionTracker, @NonNull ItemKeyProvider<K> keyProvider, @NonNull Consumer<Runnable> runner)56     public static <K> void install(
57             RecyclerView.@NonNull Adapter<?> adapter,
58             @NonNull SelectionTracker<K> selectionTracker,
59             @NonNull ItemKeyProvider<K> keyProvider,
60             @NonNull Consumer<Runnable> runner) {
61 
62         // setup bridges to relay selection and adapter events
63         new TrackerToAdapterBridge<>(selectionTracker, keyProvider, adapter, runner);
64         adapter.registerAdapterDataObserver(selectionTracker.getAdapterDataObserver());
65     }
66 
67     private static final class TrackerToAdapterBridge<K>
68             extends SelectionTracker.SelectionObserver<K> {
69 
70         // Non-private as necessary to avoid synthetic accessors for inner classes.
71         final RecyclerView.Adapter<?> mAdapter;
72         private final ItemKeyProvider<K> mKeyProvider;
73         private final Consumer<Runnable> mRunner;
74 
TrackerToAdapterBridge( @onNull SelectionTracker<K> selectionTracker, @NonNull ItemKeyProvider<K> keyProvider, RecyclerView.@NonNull Adapter<?> adapter, Consumer<Runnable> runner)75         TrackerToAdapterBridge(
76                 @NonNull SelectionTracker<K> selectionTracker,
77                 @NonNull ItemKeyProvider<K> keyProvider,
78                 RecyclerView.@NonNull Adapter<?> adapter,
79                 Consumer<Runnable> runner) {
80 
81             selectionTracker.addObserver(this);
82 
83             checkArgument(keyProvider != null);
84             checkArgument(adapter != null);
85             checkArgument(runner != null);
86 
87             mKeyProvider = keyProvider;
88             mAdapter = adapter;
89             mRunner = runner;
90         }
91 
92         /**
93          * Called when state of an item has been changed.
94          */
95         @Override
onItemStateChanged(@onNull K key, boolean selected)96         public void onItemStateChanged(@NonNull K key, boolean selected) {
97             int position = mKeyProvider.getPosition(key);
98             if (VERBOSE) Log.v(TAG, "ITEM " + key + " CHANGED at pos: " + position);
99 
100             if (position < 0) {
101                 Log.w(TAG, "Item change notification received for unknown item: " + key);
102                 return;
103             }
104 
105             mRunner.accept(new Runnable() {
106                 @Override
107                 public void run() {
108                     mAdapter.notifyItemChanged(position, SelectionTracker.SELECTION_CHANGED_MARKER);
109                 }
110             });
111         }
112     }
113 
EventBridge()114     private EventBridge() {
115     }
116 }
117