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 static androidx.core.util.Preconditions.checkArgument;
20
21import android.content.Context;
22import android.os.Bundle;
23import android.os.Parcelable;
24import android.view.GestureDetector;
25import android.view.HapticFeedbackConstants;
26import android.view.MotionEvent;
27
28import androidx.annotation.DrawableRes;
29import androidx.annotation.NonNull;
30import androidx.annotation.Nullable;
31import androidx.recyclerview.widget.RecyclerView;
32import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
33import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
34
35import java.util.Set;
36
37/**
38 * SelectionTracker provides support for managing a selection of items in a RecyclerView instance.
39 *
40 * <p>
41 * This class provides support for managing a "primary" set of selected items,
42 * in addition to a "provisional" set of selected items using conventional
43 * {@link java.util.Collections}-like methods.
44 *
45 * <p>
46 * Create an instance of SelectionTracker using {@link Builder SelectionTracker.Builder}.
47 *
48 * <p>
49 * <b>Inspecting the current selection</b>
50 *
51 * <p>
52 * The underlying selection is described by the {@link Selection} class.
53 *
54 * <p>
55 * A live view of the current selection can be obtained using {@link #getSelection}. Changes made
56 * to the selection using SelectionTracker will be immediately reflected in this instance.
57 *
58 * <p>
59 * To obtain a stable snapshot of the selection use {@link #copySelection(MutableSelection)}.
60 *
61 * <p>
62 * Selection state for an individual item can be obtained using {@link #isSelected(Object)}.
63 *
64 * <p>
65 * <b>Provisional Selection</b>
66 *
67 * <p>
68 * Provisional selection exists to address issues where a transitory selection might
69 * momentarily intersect with a previously established selection resulting in a some
70 * or all of the established selection being erased. Such situations may arise
71 * when band selection is being performed in "additive" mode (e.g. SHIFT or CTRL is pressed
72 * on the keyboard prior to mouse down), or when there's an active gesture selection
73 * (which can be initiated by long pressing an unselected item while there is an
74 * existing selection).
75 *
76 * <p>
77 * A provisional selection can be abandoned, or merged into the primary selection.
78 *
79 * <p>
80 * <b>Enforcing selection policies</b>
81 *
82 * <p>
83 * Which items can be selected by the user is a matter of policy in an Application.
84 * Developers supply these policies by way of {@link SelectionPredicate}.
85 *
86 * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
87 */
88public abstract class SelectionTracker<K> {
89
90    /**
91     * This value is included in the payload when SelectionTracker notifies RecyclerView
92     * of changes to selection. Look for this value in the {@code payload}
93     * Object argument supplied to
94     * {@link RecyclerView.Adapter#onBindViewHolder
95     *     Adapter#onBindViewHolder}.
96     * If present the call is occurring in response to a selection state change.
97     * This would be a good opportunity to animate changes between unselected and selected state.
98     * When state is being restored, this argument will not be present.
99     */
100    public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
101
102    /**
103     * Adds {@code observer} to be notified when changes to selection occur.
104     *
105     * <p>
106     * Use an observer to track attributes about the selection and
107     * update the UI to reflect the state of the selection. For example, an author
108     * may use an observer to control the enabled status of menu items,
109     * or to initiate {@link android.view.ActionMode}.
110     */
111    public abstract void addObserver(SelectionObserver observer);
112
113    /** @return true if has a selection */
114    public abstract boolean hasSelection();
115
116    /**
117     * Returns a Selection object that provides a live view on the current selection.
118     *
119     * @return The current selection.
120     * @see #copySelection(MutableSelection) on how to get a snapshot
121     * of the selection that will not reflect future changes
122     * to selection.
123     */
124    public abstract Selection<K> getSelection();
125
126    /**
127     * Updates {@code dest} to reflect the current selection.
128     */
129    public abstract void copySelection(@NonNull MutableSelection<K> dest);
130
131    /**
132     * @return true if the item specified by its id is selected. Shorthand for
133     * {@code getSelection().contains(K)}.
134     */
135    public abstract boolean isSelected(@Nullable K key);
136
137    /**
138     * Restores the selected state of specified items. Used in cases such as restore the selection
139     * after rotation etc. Provisional selection is not restored.
140     *
141     * <p>
142     * This affords clients the ability to restore selection from selection saved
143     * in Activity state.
144     *
145     * @see StorageStrategy details on selection state support.
146     *
147     * @param selection selection being restored.
148     */
149    protected abstract void restoreSelection(@NonNull Selection<K> selection);
150
151    /**
152     * Clears both primary and provisional selections.
153     *
154     * @return true if primary selection changed.
155     */
156    public abstract boolean clearSelection();
157
158    /**
159     * Sets the selected state of the specified items if permitted after consulting
160     * SelectionPredicate.
161     */
162    public abstract boolean setItemsSelected(@NonNull Iterable<K> keys, boolean selected);
163
164    /**
165     * Attempts to select an item.
166     *
167     * @return true if the item was selected. False if the item could not be selected, or was
168     * was already selected.
169     */
170    public abstract boolean select(@NonNull K key);
171
172    /**
173     * Attempts to deselect an item.
174     *
175     * @return true if the item was deselected. False if the item could not be deselected, or was
176     * was already un-selected.
177     */
178    public abstract boolean deselect(@NonNull K key);
179
180    abstract AdapterDataObserver getAdapterDataObserver();
181
182    /**
183     * Attempts to establish a range selection at {@code position}, selecting the item
184     * at {@code position} if needed.
185     *
186     * @param position The "anchor" position for the range. Subsequent range operations
187     *                 (primarily keyboard and mouse based operations like SHIFT + click)
188     *                 work with the established anchor point to define selection ranges.
189     */
190    abstract void startRange(int position);
191
192    /**
193     * Sets the end point for the active range selection.
194     *
195     * <p>
196     * This function should only be called when a range selection is active
197     * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
198     * selected after consulting SelectionPredicate.
199     *
200     * @param position  The new end position for the selection range.
201     * @throws IllegalStateException if a range selection is not active. Range selection
202     *         must have been started by a call to {@link #startRange(int)}.
203     */
204    abstract void extendRange(int position);
205
206    /**
207     * Clears an in-progress range selection. Provisional range selection established
208     * using {@link #extendProvisionalRange(int)} will be cleared (unless
209     * {@link #mergeProvisionalSelection()} is called first.)
210     */
211    abstract void endRange();
212
213    /**
214     * @return Whether or not there is a current range selection active.
215     */
216    abstract boolean isRangeActive();
217
218    /**
219     * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted
220     * when determining how to extend, and modify selection ranges. Calling this when a
221     * range selection is active will reset the range selection.
222     *
223     * TODO: Reconcile this with startRange. Maybe just docs need to be updated.
224     *
225     * @param position the anchor position. Must already be selected.
226     */
227    abstract void anchorRange(int position);
228
229    /**
230     * Creates a provisional selection from anchor to {@code position}.
231     *
232     * @param position the end point.
233     */
234    abstract void extendProvisionalRange(int position);
235
236    /**
237     * Sets the provisional selection, replacing any existing selection.
238     * @param newSelection
239     */
240    abstract void setProvisionalSelection(@NonNull Set<K> newSelection);
241
242    /**
243     * Clears any existing provisional selection
244     */
245    abstract void clearProvisionalSelection();
246
247    /**
248     * Converts the provisional selection into primary selection, then clears
249     * provisional selection.
250     */
251    abstract void mergeProvisionalSelection();
252
253    /**
254     * Preserves selection, if any. Call this method from Activity#onSaveInstanceState
255     *
256     * @param state Bundle instance supplied to onSaveInstanceState.
257     */
258    public abstract void onSaveInstanceState(@NonNull Bundle state);
259
260    /**
261     * Restores selection from previously saved state. Call this method from
262     * Activity#onCreate.
263     *
264     * @param state Bundle instance supplied to onCreate.
265     */
266    public abstract void onRestoreInstanceState(@Nullable Bundle state);
267
268    /**
269     * Observer class providing access to information about Selection state changes.
270     *
271     * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
272     */
273    public abstract static class SelectionObserver<K> {
274
275        /**
276         * Called when the state of an item has been changed.
277         */
278        public void onItemStateChanged(@NonNull K key, boolean selected) {
279        }
280
281        /**
282         * Called when the underlying data set has changed. After this method is called
283         * SelectionTracker will traverse the existing selection,
284         * calling {@link #onItemStateChanged(K, boolean)} for each selected item,
285         * and deselecting any items that cannot be selected given the updated data-set
286         * (and after consulting SelectionPredicate).
287         */
288        public void onSelectionRefresh() {
289        }
290
291        /**
292         * Called immediately after completion of any set of changes, excluding
293         * those resulting in calls to {@link #onSelectionRefresh()} and
294         * {@link #onSelectionRestored()}.
295         */
296        public void onSelectionChanged() {
297        }
298
299        /**
300         * Called immediately after selection is restored.
301         * {@link #onItemStateChanged(K, boolean)} will *not* be called
302         * for individual items in the selection.
303         */
304        public void onSelectionRestored() {
305        }
306    }
307
308    /**
309     * Implement SelectionPredicate to control when items can be selected or unselected.
310     * See {@link Builder#withSelectionPredicate(SelectionPredicate)}.
311     *
312     * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
313     */
314    public abstract static class SelectionPredicate<K> {
315
316        /**
317         * Validates a change to selection for a specific key.
318         *
319         * @param key the item key
320         * @param nextState the next potential selected/unselected state
321         * @return true if the item at {@code id} can be set to {@code nextState}.
322         */
323        public abstract boolean canSetStateForKey(@NonNull K key, boolean nextState);
324
325        /**
326         * Validates a change to selection for a specific position. If necessary
327         * use {@link ItemKeyProvider} to identy associated key.
328         *
329         * @param position the item position
330         * @param nextState the next potential selected/unselected state
331         * @return true if the item at {@code id} can be set to {@code nextState}.
332         */
333        public abstract boolean canSetStateAtPosition(int position, boolean nextState);
334
335        /**
336         * Permits restriction to single selection mode. Single selection mode has
337         * unique behaviors in that it'll deselect an item already selected
338         * in order to select the new item.
339         *
340         * <p>
341         * In order to limit the number of items that can be selected,
342         * use {@link #canSetStateForKey(Object, boolean)} and
343         * {@link #canSetStateAtPosition(int, boolean)}.
344         *
345         * @return true if more than a single item can be selected.
346         */
347        public abstract boolean canSelectMultiple();
348    }
349
350    /**
351     * Builder is the primary mechanism for create a {@link SelectionTracker} that
352     * can be used with your RecyclerView. Once installed, users will be able to create and
353     * manipulate selection using a variety of intuitive techniques like tap, gesture,
354     * and mouse lasso.
355     *
356     * <p>
357     * Example usage:
358     * <pre>SelectionTracker<Uri> tracker = new SelectionTracker.Builder<>(
359     *        "my-uri-selection",
360     *        recyclerView,
361     *        new DemoStableIdProvider(recyclerView.getAdapter()),
362     *        new MyDetailsLookup(recyclerView),
363     *        StorageStrategy.createParcelableStorage(Uri.class))
364     *        .build();
365     *</pre>
366     *
367     * <p>
368     * <b>Restricting which items can be selected and limiting selection size</b>
369     *
370     * <p>
371     * {@link SelectionPredicate} provides a mechanism to restrict which Items can be selected,
372     * to limit the number of items that can be selected, as well as allowing the selection
373     * code to be placed into "single select" mode, which as the name indicates, constrains
374     * the selection size to a single item.
375     *
376     * <p>Configuring the tracker for single single selection support can be done
377     * by supplying {@link SelectionPredicates#createSelectSingleAnything()}.
378     *
379     * SelectionTracker<String> tracker = new SelectionTracker.Builder<>(
380     *        "my-string-selection",
381     *        recyclerView,
382     *        new DemoStableIdProvider(recyclerView.getAdapter()),
383     *        new MyDetailsLookup(recyclerView),
384     *        StorageStrategy.createStringStorage())
385     *        .withSelectionPredicate(SelectionPredicates#createSelectSingleAnything())
386     *        .build();
387     *</pre>
388     * <p>
389     * <b>Retaining state across Android lifecycle events</b>
390     *
391     * <p>
392     * Support for storage/persistence of selection must be configured and invoked manually
393     * owing to its reliance on Activity lifecycle events.
394     * Failure to include support for selection storage will result in the active selection
395     * being lost when the Activity receives a configuration change (e.g. rotation)
396     * or when the application process is destroyed by the OS to reclaim resources.
397     *
398     * <p>
399     * <b>Key Type</b>
400     *
401     * <p>
402     * Developers must decide on the key type used to identify selected items. Support
403     * is provided for three types: {@link Parcelable}, {@link String}, and {@link Long}.
404     *
405     * <p>
406     * {@link Parcelable}: Any Parcelable type can be used as the selection key. This is especially
407     * useful in conjunction with {@link android.net.Uri} as the Android URI implementation is both
408     * parcelable and makes for a natural stable selection key for values represented by
409     * the Android Content Provider framework. If items in your view are associated with
410     * stable {@code content://} uris, you should use Uri for your key type.
411     *
412     * <p>
413     * {@link String}: Use String when a string based stable identifier is available.
414     *
415     * <p>
416     * {@link Long}: Use Long when RecyclerView's long stable ids are
417     * already in use. It comes with some limitations, however, as access to stable ids
418     * at runtime is limited. Band selection support is not available when using the default
419     * long key storage implementation. See {@link StableIdKeyProvider} for details.
420     *
421     * <p>
422     * Usage:
423     *
424     * <pre>
425     * private SelectionTracker<Uri> mTracker;
426     *
427     * public void onCreate(Bundle savedInstanceState) {
428     *   // See above for details on constructing a SelectionTracker instance.
429     *
430     *   if (savedInstanceState != null) {
431     *      mTracker.onRestoreInstanceState(savedInstanceState);
432     *   }
433     * }
434     *
435     * protected void onSaveInstanceState(Bundle outState) {
436     *     super.onSaveInstanceState(outState);
437     *     mTracker.onSaveInstanceState(outState);
438     * }
439     * </pre>
440     *
441     * @param <K> Selection key type. Built in support is provided for {@link String},
442     *           {@link Long}, and {@link Parcelable}. {@link StorageStrategy}
443     *           provides factory methods for each type:
444     *           {@link StorageStrategy#createStringStorage()},
445     *           {@link StorageStrategy#createParcelableStorage(Class)},
446     *           {@link StorageStrategy#createLongStorage()}
447     */
448    public static final class Builder<K> {
449
450        private final RecyclerView mRecyclerView;
451        private final RecyclerView.Adapter<?> mAdapter;
452        private final Context mContext;
453        private final String mSelectionId;
454        private final StorageStrategy<K> mStorage;
455
456        private SelectionPredicate<K> mSelectionPredicate =
457                SelectionPredicates.createSelectAnything();
458        private OperationMonitor mMonitor = new OperationMonitor();
459        private ItemKeyProvider<K> mKeyProvider;
460        private ItemDetailsLookup<K> mDetailsLookup;
461
462        private FocusDelegate<K> mFocusDelegate = FocusDelegate.dummy();
463
464        private OnItemActivatedListener<K> mOnItemActivatedListener;
465        private OnDragInitiatedListener mOnDragInitiatedListener;
466        private OnContextClickListener mOnContextClickListener;
467
468        private BandPredicate mBandPredicate;
469        private int mBandOverlayId = R.drawable.selection_band_overlay;
470
471        private int[] mGestureToolTypes = new int[] {
472                MotionEvent.TOOL_TYPE_FINGER,
473                MotionEvent.TOOL_TYPE_UNKNOWN
474        };
475
476        private int[] mPointerToolTypes = new int[] {
477                MotionEvent.TOOL_TYPE_MOUSE
478        };
479
480        /**
481         * Creates a new SelectionTracker.Builder useful for configuring and creating
482         * a new SelectionTracker for use with your {@link RecyclerView}.
483         *
484         * @param selectionId A unique string identifying this selection in the context
485         *        of the activity or fragment.
486         * @param recyclerView the owning RecyclerView
487         * @param keyProvider the source of selection keys
488         * @param detailsLookup the source of information about RecyclerView items.
489         * @param storage Strategy for type-safe storage of selection state in
490         *        {@link Bundle}.
491         */
492        public Builder(
493                @NonNull String selectionId,
494                @NonNull RecyclerView recyclerView,
495                @NonNull ItemKeyProvider<K> keyProvider,
496                @NonNull ItemDetailsLookup<K> detailsLookup,
497                @NonNull StorageStrategy<K> storage) {
498
499            checkArgument(selectionId != null);
500            checkArgument(!selectionId.trim().isEmpty());
501            checkArgument(recyclerView != null);
502
503            mSelectionId = selectionId;
504            mRecyclerView = recyclerView;
505            mContext = recyclerView.getContext();
506            mAdapter = recyclerView.getAdapter();
507
508            checkArgument(mAdapter != null);
509            checkArgument(keyProvider != null);
510            checkArgument(detailsLookup != null);
511            checkArgument(storage != null);
512
513            mDetailsLookup = detailsLookup;
514            mKeyProvider = keyProvider;
515            mStorage = storage;
516
517            mBandPredicate = new BandPredicate.NonDraggableArea(mRecyclerView, detailsLookup);
518        }
519
520        /**
521         * Install selection predicate.
522         *
523         * @param predicate the predicate to be used.
524         * @return this
525         */
526        public Builder<K> withSelectionPredicate(
527                @NonNull SelectionPredicate<K> predicate) {
528
529            checkArgument(predicate != null);
530            mSelectionPredicate = predicate;
531            return this;
532        }
533
534        /**
535         * Add operation monitor allowing access to information about active
536         * operations (like band selection and gesture selection).
537         *
538         * @param monitor the monitor to be used
539         * @return this
540         */
541        public Builder<K> withOperationMonitor(
542                @NonNull OperationMonitor monitor) {
543
544            checkArgument(monitor != null);
545            mMonitor = monitor;
546            return this;
547        }
548
549        /**
550         * Add focus delegate to interact with selection related focus changes.
551         *
552         * @param delegate the delegate to be used
553         * @return this
554         */
555        public Builder<K> withFocusDelegate(@NonNull FocusDelegate<K> delegate) {
556            checkArgument(delegate != null);
557            mFocusDelegate = delegate;
558            return this;
559        }
560
561        /**
562         * Adds an item activation listener. Respond to taps/enter/double-click on items.
563         *
564         * @param listener the listener to be used
565         * @return this
566         */
567        public Builder<K> withOnItemActivatedListener(
568                @NonNull OnItemActivatedListener<K> listener) {
569
570            checkArgument(listener != null);
571
572            mOnItemActivatedListener = listener;
573            return this;
574        }
575
576        /**
577         * Adds a context click listener. Respond to right-click.
578         *
579         * @param listener the listener to be used
580         * @return this
581         */
582        public Builder<K> withOnContextClickListener(
583                @NonNull OnContextClickListener listener) {
584
585            checkArgument(listener != null);
586
587            mOnContextClickListener = listener;
588            return this;
589        }
590
591        /**
592         * Adds a drag initiated listener. Add support for drag and drop.
593         *
594         * @param listener the listener to be used
595         * @return this
596         */
597        public Builder<K> withOnDragInitiatedListener(
598                @NonNull OnDragInitiatedListener listener) {
599
600            checkArgument(listener != null);
601
602            mOnDragInitiatedListener = listener;
603            return this;
604        }
605
606        /**
607         * Replaces default tap and gesture tool-types. Defaults are:
608         * {@link MotionEvent#TOOL_TYPE_FINGER} and {@link MotionEvent#TOOL_TYPE_UNKNOWN}.
609         *
610         * @param toolTypes the tool types to be used
611         * @return this
612         */
613        public Builder<K> withGestureTooltypes(int... toolTypes) {
614            mGestureToolTypes = toolTypes;
615            return this;
616        }
617
618        /**
619         * Replaces default band overlay.
620         *
621         * @param bandOverlayId
622         * @return this
623         */
624        public Builder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
625            mBandOverlayId = bandOverlayId;
626            return this;
627        }
628
629        /**
630         * Replaces default band predicate.
631         * @param bandPredicate
632         * @return this
633         */
634        public Builder<K> withBandPredicate(@NonNull BandPredicate bandPredicate) {
635            checkArgument(bandPredicate != null);
636
637            mBandPredicate = bandPredicate;
638            return this;
639        }
640
641        /**
642         * Replaces default pointer tool-types. Pointer tools
643         * are associated with band selection, and certain
644         * drag and drop behaviors. Defaults are:
645         * {@link MotionEvent#TOOL_TYPE_MOUSE}.
646         *
647         * @param toolTypes the tool types to be used
648         * @return this
649         */
650        public Builder<K> withPointerTooltypes(int... toolTypes) {
651            mPointerToolTypes = toolTypes;
652            return this;
653        }
654
655        /**
656         * Prepares and returns a SelectionTracker.
657         *
658         * @return this
659         */
660        public SelectionTracker<K> build() {
661
662            SelectionTracker<K> tracker = new DefaultSelectionTracker<>(
663                    mSelectionId, mKeyProvider, mSelectionPredicate, mStorage);
664
665            // Event glue between RecyclerView and SelectionTracker keeps the classes separate
666            // so that a SelectionTracker can be shared across RecyclerView instances that
667            // represent the same data in different ways.
668            EventBridge.install(mAdapter, tracker, mKeyProvider);
669
670            AutoScroller scroller =
671                    new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecyclerView));
672
673            // Setup basic input handling, with the touch handler as the default consumer
674            // of events. If mouse handling is configured as well, the mouse input
675            // related handlers will intercept mouse input events.
676
677            // GestureRouter is responsible for routing GestureDetector events
678            // to tool-type specific handlers.
679            GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
680            GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
681
682            // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
683            // Despite "Touch" being in the name, it receives events for all types of tools.
684            // This class is responsible for routing events to tool-type specific handlers,
685            // and if not handled by a handler, on to a GestureDetector for analysis.
686            TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
687
688            // GestureSelectionHelper provides logic that interprets a combination
689            // of motions and gestures in order to provide gesture driven selection support
690            // when used in conjunction with RecyclerView.
691            final GestureSelectionHelper gestureHelper =
692                    GestureSelectionHelper.create(tracker, mRecyclerView, scroller, mMonitor);
693
694            // Finally hook the framework up to listening to recycle view events.
695            mRecyclerView.addOnItemTouchListener(eventRouter);
696
697            // But before you move on, there's more work to do. Event plumbing has been
698            // installed, but we haven't registered any of our helpers or callbacks.
699            // Helpers contain predefined logic converting events into selection related events.
700            // Callbacks provide developers the ability to reponspond to other types of
701            // events (like "activate" a tapped item). This is broken up into two main
702            // suites, one for "touch" and one for "mouse", though both can and should (usually)
703            // be configured to handle other types of input (to satisfy user expectation).);
704
705            // Internally, the code doesn't permit nullable listeners, so we lazily
706            // initialize dummy instances if the developer didn't supply a real listener.
707            mOnDragInitiatedListener = (mOnDragInitiatedListener != null)
708                    ? mOnDragInitiatedListener
709                    : new OnDragInitiatedListener() {
710                        @Override
711                        public boolean onDragInitiated(@NonNull MotionEvent e) {
712                            return false;
713                        }
714                    };
715
716            mOnItemActivatedListener = (mOnItemActivatedListener != null)
717                    ? mOnItemActivatedListener
718                    : new OnItemActivatedListener<K>() {
719                        @Override
720                        public boolean onItemActivated(
721                                @NonNull ItemDetailsLookup.ItemDetails<K> item,
722                                @NonNull MotionEvent e) {
723                            return false;
724                        }
725                    };
726
727            mOnContextClickListener = (mOnContextClickListener != null)
728                    ? mOnContextClickListener
729                    : new OnContextClickListener() {
730                        @Override
731                        public boolean onContextClick(@NonNull MotionEvent e) {
732                            return false;
733                        }
734                    };
735
736            // Provides high level glue for binding touch events
737            // and gestures to selection framework.
738            TouchInputHandler<K> touchHandler = new TouchInputHandler<K>(
739                    tracker,
740                    mKeyProvider,
741                    mDetailsLookup,
742                    mSelectionPredicate,
743                    new Runnable() {
744                        @Override
745                        public void run() {
746                            if (mSelectionPredicate.canSelectMultiple()) {
747                                gestureHelper.start();
748                            }
749                        }
750                    },
751                    mOnDragInitiatedListener,
752                    mOnItemActivatedListener,
753                    mFocusDelegate,
754                    new Runnable() {
755                        @Override
756                        public void run() {
757                            mRecyclerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
758                        }
759                    });
760
761            for (int toolType : mGestureToolTypes) {
762                gestureRouter.register(toolType, touchHandler);
763                eventRouter.register(toolType, gestureHelper);
764            }
765
766            // Provides high level glue for binding mouse events and gestures
767            // to selection framework.
768            MouseInputHandler<K> mouseHandler = new MouseInputHandler<>(
769                    tracker,
770                    mKeyProvider,
771                    mDetailsLookup,
772                    mOnContextClickListener,
773                    mOnItemActivatedListener,
774                    mFocusDelegate);
775
776            for (int toolType : mPointerToolTypes) {
777                gestureRouter.register(toolType, mouseHandler);
778            }
779
780            @Nullable BandSelectionHelper bandHelper = null;
781
782            // Band selection not supported in single select mode, or when key access
783            // is limited to anything less than the entire corpus.
784            if (mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED)
785                    && mSelectionPredicate.canSelectMultiple()) {
786                // BandSelectionHelper provides support for band selection on-top of a RecyclerView
787                // instance. Given the recycling nature of RecyclerView BandSelectionController
788                // necessarily models and caches list/grid information as the user's pointer
789                // interacts with the item in the RecyclerView. Selectable items that intersect
790                // with the band, both on and off screen, are selected.
791                bandHelper = BandSelectionHelper.create(
792                        mRecyclerView,
793                        scroller,
794                        mBandOverlayId,
795                        mKeyProvider,
796                        tracker,
797                        mSelectionPredicate,
798                        mBandPredicate,
799                        mFocusDelegate,
800                        mMonitor);
801            }
802
803            OnItemTouchListener pointerEventHandler = new PointerDragEventInterceptor(
804                    mDetailsLookup, mOnDragInitiatedListener, bandHelper);
805
806            for (int toolType : mPointerToolTypes) {
807                eventRouter.register(toolType, pointerEventHandler);
808            }
809
810            return tracker;
811        }
812    }
813}
814