1 /*
2  * Copyright 2019 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.recyclerview.selection.Shared.DEBUG;
20 
21 import android.util.Log;
22 import android.view.MotionEvent;
23 
24 import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
25 import androidx.recyclerview.widget.RecyclerView;
26 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
27 
28 import org.jspecify.annotations.NonNull;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * Class managing resetting of library state in response to specific
35  * events like clearing of selection and MotionEvent.ACTION_CANCEL
36  * events.
37  *
38  * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
39  */
40 final class ResetManager<K> {
41 
42     private static final String TAG = "ResetManager";
43 
44     private final List<Resettable> mResetHandlers = new ArrayList<>();
45 
46     private final OnItemTouchListener mInputListener = new OnItemTouchListener() {
47         @Override
48         public boolean onInterceptTouchEvent(@NonNull RecyclerView unused,
49                 @NonNull MotionEvent e) {
50             if (MotionEvents.isActionCancel(e)) {
51                 if (DEBUG) Log.d(TAG, "Received CANCEL event.");
52                 callResetHandlers();
53             }
54             return false;
55         }
56 
57         @Override
58         public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
59         }
60 
61         @Override
62         public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
63         }
64     };
65 
66     // Resettable interface has a #requiresReset method because DefaultSelectionTracker
67     // (owner of the state we observe with our SelectionObserver) is, itself,
68     // a Resettable. Such an arrangement introduces the real possibility of infinite recursion.
69     // When we call reset on DefaultSelectionTracker it'll eventually call back to
70     // notify us of the change via onSelectionCleared. We avoid recursion by
71     // checking #requiresReset before calling reset again.
72     private final SelectionObserver<K> mSelectionObserver = new SelectionObserver<K>() {
73         @Override
74         protected void onSelectionCleared() {
75             if (DEBUG) Log.d(TAG, "Received onSelectionCleared event.");
76             callResetHandlers();
77         }
78     };
79 
getSelectionObserver()80     SelectionObserver<K> getSelectionObserver() {
81         return mSelectionObserver;
82     }
83 
getInputListener()84     OnItemTouchListener getInputListener() {
85         return mInputListener;
86     }
87 
88     /**
89      * Registers a new Resettable.
90      */
addResetHandler(@onNull Resettable handler)91     void addResetHandler(@NonNull Resettable handler) {
92         mResetHandlers.add(handler);
93     }
94 
callResetHandlers()95     void callResetHandlers() {
96         for (Resettable handler : mResetHandlers) {
97             if (handler.isResetRequired()) {
98                 handler.reset();
99             }
100         }
101     }
102 }
103