1 /*
2  * Copyright 2020 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 android.view.MotionEvent;
20 
21 import androidx.recyclerview.widget.RecyclerView;
22 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
23 
24 import org.jspecify.annotations.NonNull;
25 
26 /**
27  * OnItemTouchListener that claims all ACTION_UP events in streams that have otherwise gone
28  * unclaimed after a LongPress has been detected by GestureDetector.
29  * This addresses issue described in b/166836317, where child view
30  * OnClickListeners were being called unexpectedly.
31  */
32 class EventBackstop implements OnItemTouchListener, Resettable {
33 
34     private boolean mLongPressFired;
35 
36     @Override
onInterceptTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)37     public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
38         // We only claim ACTION_UP events after a LongPress event. Were we to claim
39         // all ACTION_UP events we'd deprive RecyclerView of the signal it needs to
40         // initiate fling scrolling.
41         if (MotionEvents.isActionUp(e) && mLongPressFired) {
42             mLongPressFired = false;
43             return true;
44         }
45 
46         // Recover from disallow state.
47         if (MotionEvents.isActionDown(e) && isResetRequired()) {
48             reset();
49         }
50         return false;
51     }
52 
53     @Override
onTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)54     public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
55         // We should never receive any events, but were we to...we want to ignore them.
56     }
57 
58     @Override
onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)59     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
60         throw new UnsupportedOperationException("Wrap me in an InterceptFilter.");
61     }
62 
63     @Override
isResetRequired()64     public boolean isResetRequired() {
65         return  mLongPressFired;
66     }
67 
68     @Override
reset()69     public void reset() {
70         mLongPressFired = false;
71     }
72 
onLongPress()73     void onLongPress() {
74         mLongPressFired = true;
75     }
76 }
77