mirror of https://github.com/M66B/FairEmail.git
Updated AndroidX
This commit is contained in:
parent
2c46f3d9ff
commit
8c555c684d
|
@ -181,17 +181,18 @@ dependencies {
|
|||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
def core_version = "1.2.0-rc01"
|
||||
def appcompat_version = "1.1.0"
|
||||
def fragment_version = "1.2.0-rc02"
|
||||
def appcompat_version = "1.2.0-alpha01"
|
||||
def fragment_version = "1.2.0-rc03"
|
||||
def recyclerview_version = "1.1.0"
|
||||
def coordinatorlayout_version = "1.1.0-rc01"
|
||||
def coordinatorlayout_version = "1.1.0"
|
||||
def constraintlayout_version = "2.0.0-beta3"
|
||||
def material_version = "1.2.0-alpha02"
|
||||
def browser_version = "1.2.0-beta01"
|
||||
def browser_version = "1.2.0-rc01"
|
||||
def lbm_version = "1.0.0"
|
||||
def swiperefresh_version = "1.0.0"
|
||||
def documentfile_version = "1.0.1"
|
||||
def lifecycle_version = "2.2.0-rc02"
|
||||
def lifecycle_version = "2.2.0-rc03"
|
||||
def sqlite_version = "2.1.0-beta01"
|
||||
def room_version = "2.2.2"
|
||||
def paging_version = "2.1.0"
|
||||
def preference_version = "1.1.0"
|
||||
|
@ -226,7 +227,7 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview
|
||||
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview-selection
|
||||
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
|
||||
//implementation "androidx.recyclerview:recyclerview-selection:1.1.0-alpha06"
|
||||
//implementation "androidx.recyclerview:recyclerview-selection:1.1.0-beta01"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.coordinatorlayout/coordinatorlayout
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayout_version"
|
||||
|
@ -260,7 +261,7 @@ dependencies {
|
|||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-common:$room_version" // because of exclude
|
||||
// https://mvnrepository.com/artifact/androidx.sqlite/sqlite-framework
|
||||
implementation "androidx.sqlite:sqlite-framework:2.1.0-alpha01" // because of exclude
|
||||
implementation "androidx.sqlite:sqlite-framework:$sqlite_version" // because of exclude
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
|
||||
|
|
|
@ -40,7 +40,7 @@ public abstract class BandPredicate {
|
|||
/**
|
||||
* @return true if band selection can be initiated in response to the {@link MotionEvent}.
|
||||
*/
|
||||
public abstract boolean canInitiate(MotionEvent e);
|
||||
public abstract boolean canInitiate(@NonNull MotionEvent e);
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
static boolean hasSupportedLayoutManager(@NonNull RecyclerView recyclerView) {
|
||||
|
@ -107,7 +107,7 @@ public abstract class BandPredicate {
|
|||
public static final class NonDraggableArea extends BandPredicate {
|
||||
|
||||
private final RecyclerView mRecyclerView;
|
||||
private final ItemDetailsLookup mDetailsLookup;
|
||||
private final ItemDetailsLookup<?> mDetailsLookup;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
|
@ -116,7 +116,7 @@ public abstract class BandPredicate {
|
|||
* @param detailsLookup provides access to item details.
|
||||
*/
|
||||
public NonDraggableArea(
|
||||
@NonNull RecyclerView recyclerView, @NonNull ItemDetailsLookup detailsLookup) {
|
||||
@NonNull RecyclerView recyclerView, @NonNull ItemDetailsLookup<?> detailsLookup) {
|
||||
|
||||
checkArgument(recyclerView != null);
|
||||
checkArgument(detailsLookup != null);
|
||||
|
@ -132,7 +132,8 @@ public abstract class BandPredicate {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e);
|
||||
@Nullable ItemDetailsLookup.ItemDetails<?> details =
|
||||
mDetailsLookup.getItemDetails(e);
|
||||
return (details == null) || !details.inDragRegion(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import android.view.MotionEvent;
|
|||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
|
@ -48,17 +47,14 @@ import java.util.Set;
|
|||
* the user interacts with items using their pointer (and the band). Selectable items that intersect
|
||||
* with the band, both on and off screen, are selected on pointer up.
|
||||
*
|
||||
* @see SelectionTracker.Builder#withPointerTooltypes(int...) for details on the specific
|
||||
* tooltypes routed to this helper.
|
||||
*
|
||||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
*/
|
||||
class BandSelectionHelper<K> implements OnItemTouchListener {
|
||||
class BandSelectionHelper<K> implements OnItemTouchListener, Resettable {
|
||||
|
||||
static final String TAG = "BandSelectionHelper";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private final BandHost mHost;
|
||||
private final BandHost<K> mHost;
|
||||
private final ItemKeyProvider<K> mKeyProvider;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final SelectionTracker<K> mSelectionTracker;
|
||||
|
@ -66,17 +62,17 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
private final FocusDelegate<K> mFocusDelegate;
|
||||
private final OperationMonitor mLock;
|
||||
private final AutoScroller mScroller;
|
||||
private final GridModel.SelectionObserver mGridObserver;
|
||||
private final GridModel.SelectionObserver<K> mGridObserver;
|
||||
|
||||
private @Nullable Point mCurrentPosition;
|
||||
private @Nullable Point mOrigin;
|
||||
private @Nullable GridModel mModel;
|
||||
private @Nullable GridModel<K> mModel;
|
||||
|
||||
/**
|
||||
* See {@link BandSelectionHelper#create}.
|
||||
*/
|
||||
BandSelectionHelper(
|
||||
@NonNull BandHost host,
|
||||
@NonNull BandHost<K> host,
|
||||
@NonNull AutoScroller scroller,
|
||||
@NonNull ItemKeyProvider<K> keyProvider,
|
||||
@NonNull SelectionTracker<K> selectionTracker,
|
||||
|
@ -122,7 +118,7 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
*
|
||||
* @return new BandSelectionHelper instance.
|
||||
*/
|
||||
static <K> BandSelectionHelper create(
|
||||
static <K> BandSelectionHelper<K> create(
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull AutoScroller scroller,
|
||||
@DrawableRes int bandOverlayId,
|
||||
|
@ -143,24 +139,23 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
lock);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isActive() {
|
||||
boolean active = mModel != null;
|
||||
if (DEBUG && active) {
|
||||
mLock.checkStarted();
|
||||
}
|
||||
return active;
|
||||
private boolean isActive() {
|
||||
boolean started = mModel != null;
|
||||
if (DEBUG) mLock.checkStarted(started);
|
||||
return started;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clients must call reset when there are any material changes to the layout of items
|
||||
* in RecyclerView.
|
||||
*/
|
||||
void reset() {
|
||||
@Override
|
||||
public void reset() {
|
||||
if (!isActive()) {
|
||||
if (DEBUG) Log.d(TAG, "Ignoring reset request, not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Handling reset request.");
|
||||
mHost.hideBand();
|
||||
if (mModel != null) {
|
||||
mModel.stopCapturing();
|
||||
|
@ -171,11 +166,15 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
mOrigin = null;
|
||||
|
||||
mScroller.reset();
|
||||
mLock.stop();
|
||||
// mLock is reset by reset manager.
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean shouldStart(@NonNull MotionEvent e) {
|
||||
@Override
|
||||
public boolean isResetRequired() {
|
||||
return isActive();
|
||||
}
|
||||
|
||||
private boolean shouldStart(@NonNull MotionEvent e) {
|
||||
// b/30146357 && b/23793622. onInterceptTouchEvent does not dispatch events to onTouchEvent
|
||||
// unless the event is != ACTION_DOWN. Thus, we need to actually start band selection when
|
||||
// mouse moves.
|
||||
|
@ -185,12 +184,8 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
&& !isActive();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean shouldStop(@NonNull MotionEvent e) {
|
||||
return isActive()
|
||||
&& (MotionEvents.isActionUp(e)
|
||||
|| MotionEvents.isActionPointerUp(e)
|
||||
|| MotionEvents.isActionCancel(e));
|
||||
private boolean shouldStop(@NonNull MotionEvent e) {
|
||||
return isActive() && MotionEvents.isActionUp(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -242,7 +237,9 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
* Starts band select by adding the drawable to the RecyclerView's overlay.
|
||||
*/
|
||||
private void startBandSelect(@NonNull MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
checkState(!isActive());
|
||||
}
|
||||
|
||||
if (!MotionEvents.isCtrlKeyPressed(e)) {
|
||||
mSelectionTracker.clearSelection();
|
||||
|
@ -303,7 +300,18 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
}
|
||||
|
||||
mSelectionTracker.mergeProvisionalSelection();
|
||||
reset();
|
||||
mLock.stop();
|
||||
|
||||
mHost.hideBand();
|
||||
if (mModel != null) {
|
||||
mModel.stopCapturing();
|
||||
mModel.onDestroy();
|
||||
}
|
||||
|
||||
mModel = null;
|
||||
mOrigin = null;
|
||||
|
||||
mScroller.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -348,8 +356,6 @@ class BandSelectionHelper<K> implements OnItemTouchListener {
|
|||
|
||||
/**
|
||||
* Add a listener to be notified on scroll events.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
abstract void addOnScrollListener(@NonNull OnScrollListener listener);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.view.View;
|
|||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -53,7 +54,7 @@ final class DefaultBandHost<K> extends GridModel.GridHost<K> {
|
|||
checkArgument(recyclerView != null);
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
mBand = mRecyclerView.getContext().getResources().getDrawable(bandOverlayId);
|
||||
mBand = ContextCompat.getDrawable(mRecyclerView.getContext(), bandOverlayId);
|
||||
|
||||
checkArgument(mBand != null);
|
||||
checkArgument(keyProvider != null);
|
||||
|
|
|
@ -46,17 +46,17 @@ import java.util.Set;
|
|||
* {@link SelectionPredicate#canSelectMultiple()}.
|
||||
*
|
||||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DefaultSelectionTracker<K> extends SelectionTracker<K> implements Resettable {
|
||||
|
||||
private static final String TAG = "DefaultSelectionTracker";
|
||||
private static final String EXTRA_SELECTION_PREFIX = "androidx.recyclerview.selection";
|
||||
|
||||
private final Selection<K> mSelection = new Selection<>();
|
||||
private final List<SelectionObserver> mObservers = new ArrayList<>(1);
|
||||
private final List<SelectionObserver<K>> mObservers = new ArrayList<>(1);
|
||||
private final ItemKeyProvider<K> mKeyProvider;
|
||||
private final SelectionPredicate<K> mSelectionPredicate;
|
||||
private final StorageStrategy<K> mStorage;
|
||||
|
@ -78,8 +78,8 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
*/
|
||||
public DefaultSelectionTracker(
|
||||
@NonNull String selectionId,
|
||||
@NonNull ItemKeyProvider keyProvider,
|
||||
@NonNull SelectionPredicate selectionPredicate,
|
||||
@NonNull ItemKeyProvider<K> keyProvider,
|
||||
@NonNull SelectionPredicate<K> selectionPredicate,
|
||||
@NonNull StorageStrategy<K> storage) {
|
||||
|
||||
checkArgument(selectionId != null);
|
||||
|
@ -101,23 +101,26 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void addObserver(@NonNull SelectionObserver callback) {
|
||||
public void addObserver(@NonNull SelectionObserver<K> callback) {
|
||||
checkArgument(callback != null);
|
||||
mObservers.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is a primary or previsional selection.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSelection() {
|
||||
return !mSelection.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection getSelection() {
|
||||
public @NonNull Selection<K> getSelection() {
|
||||
return mSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copySelection(@NonNull MutableSelection dest) {
|
||||
public void copySelection(@NonNull MutableSelection<K> dest) {
|
||||
dest.copyFrom(mSelection);
|
||||
}
|
||||
|
||||
|
@ -127,7 +130,7 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void restoreSelection(@NonNull Selection other) {
|
||||
protected void restoreSelection(@NonNull Selection<K> other) {
|
||||
checkArgument(other != null);
|
||||
setItemsSelectedQuietly(other.mSelection, true);
|
||||
// NOTE: We intentionally don't restore provisional selection. It's provisional.
|
||||
|
@ -158,11 +161,15 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
@Override
|
||||
public boolean clearSelection() {
|
||||
if (!hasSelection()) {
|
||||
if (DEBUG) Log.d(TAG, "Ignoring clearSelection request. No selection.");
|
||||
return false;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "Handling clearSelection request.");
|
||||
|
||||
clearProvisionalSelection();
|
||||
clearPrimarySelection();
|
||||
notifySelectionCleared();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -171,7 +178,7 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
return;
|
||||
}
|
||||
|
||||
Selection prev = clearSelectionQuietly();
|
||||
Selection<K> prev = clearSelectionQuietly();
|
||||
notifySelectionCleared(prev);
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
@ -181,10 +188,10 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
* Returns items in previous selection. Callers are responsible for notifying
|
||||
* listeners about changes.
|
||||
*/
|
||||
private Selection clearSelectionQuietly() {
|
||||
private Selection<K> clearSelectionQuietly() {
|
||||
mRange = null;
|
||||
|
||||
MutableSelection prevSelection = new MutableSelection();
|
||||
MutableSelection<K> prevSelection = new MutableSelection();
|
||||
if (hasSelection()) {
|
||||
copySelection(prevSelection);
|
||||
mSelection.clear();
|
||||
|
@ -193,6 +200,18 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
return prevSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (DEBUG) Log.d(TAG, "Received reset request.");
|
||||
clearSelection();
|
||||
mRange = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResetRequired() {
|
||||
return hasSelection() || isRangeActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean select(@NonNull K key) {
|
||||
checkArgument(key != null);
|
||||
|
@ -208,7 +227,7 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
|
||||
// Enforce single selection policy.
|
||||
if (mSingleSelect && hasSelection()) {
|
||||
Selection prev = clearSelectionQuietly();
|
||||
Selection<K> prev = clearSelectionQuietly();
|
||||
notifySelectionCleared(prev);
|
||||
}
|
||||
|
||||
|
@ -277,8 +296,10 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) Log.i(TAG, "Extending provision range to position: " + position);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Extending provision range to position: " + position);
|
||||
checkState(isRangeActive(), "Range start point not set.");
|
||||
}
|
||||
extendRange(position, Range.TYPE_PROVISIONAL);
|
||||
}
|
||||
|
||||
|
@ -294,10 +315,20 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
* @param type The type of selection the range should utilize.
|
||||
*/
|
||||
private void extendRange(int position, @RangeType int type) {
|
||||
checkState(isRangeActive(), "Range start point not set.");
|
||||
if (!isRangeActive()) {
|
||||
Log.e(TAG, "Ignoring attempt to extend unestablished range. Ignoring.");
|
||||
if (DEBUG) {
|
||||
throw new IllegalStateException("Attempted to extend unestablished range.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.w(TAG, "Invalid position: Cannot extend selection to: " + position);
|
||||
Log.w(TAG, "Ignoring attempt to extend range to invalid position: " + position);
|
||||
if (DEBUG) {
|
||||
throw new IllegalStateException(
|
||||
"Attempting to extend range to invalid position: " + position);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -348,20 +379,16 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
return mRange != null;
|
||||
}
|
||||
|
||||
boolean isOverlapping(int position, int count) {
|
||||
return (mRange != null && mRange.isOverlapping(position, count));
|
||||
}
|
||||
|
||||
private boolean canSetState(@NonNull K key, boolean nextState) {
|
||||
return mSelectionPredicate.canSetStateForKey(key, nextState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdapterDataObserver getAdapterDataObserver() {
|
||||
protected @NonNull AdapterDataObserver getAdapterDataObserver() {
|
||||
return mAdapterObserver;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
@SuppressWarnings({"WeakerAccess", "unchecked"}) /* synthetic access */
|
||||
void onDataSetChanged() {
|
||||
mSelection.clearProvisionalSelection();
|
||||
|
||||
|
@ -409,6 +436,12 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
}
|
||||
}
|
||||
|
||||
private void notifySelectionCleared() {
|
||||
for (SelectionObserver<K> observer : mObservers) {
|
||||
observer.onSelectionCleared();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifySelectionCleared(@NonNull Selection<K> selection) {
|
||||
for (K key : selection.mSelection) {
|
||||
notifyItemStateChanged(key, false);
|
||||
|
@ -445,19 +478,6 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
|
||||
switch (type) {
|
||||
case Range.TYPE_PRIMARY:
|
||||
updateForRegularRange(begin, end, selected);
|
||||
break;
|
||||
case Range.TYPE_PROVISIONAL:
|
||||
updateForProvisionalRange(begin, end, selected);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid range type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
void updateForRegularRange(int begin, int end, boolean selected) {
|
||||
checkArgument(end >= begin);
|
||||
|
@ -514,7 +534,6 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final void onSaveInstanceState(@NonNull Bundle state) {
|
||||
if (mSelection.isEmpty()) {
|
||||
return;
|
||||
|
@ -582,20 +601,16 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
|||
|
||||
@Override
|
||||
public void onItemRangeInserted(int startPosition, int itemCount) {
|
||||
if (mSelectionTracker.isOverlapping(startPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int startPosition, int itemCount) {
|
||||
if (mSelectionTracker.isOverlapping(startPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
if (mSelectionTracker.isOverlapping(fromPosition, itemCount) ||
|
||||
mSelectionTracker.isOverlapping(toPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* No-op implementation of OnItemTouchListener suitable for use as a default
|
||||
* handler w/ ToolHandlerRegistery, or in tests.
|
||||
*/
|
||||
final class DummyOnItemTouchListener implements RecyclerView.OnItemTouchListener {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(
|
||||
@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(
|
||||
@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
|
||||
/**
|
||||
* A class responsible for routing MotionEvents to tool-type specific handlers.
|
||||
* Individual tool-type specific handlers are added after the class is constructed.
|
||||
*
|
||||
* <p>
|
||||
* EventRouter takes its name from
|
||||
* {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
|
||||
* being in the name, it receives MotionEvents for all types of tools.
|
||||
*/
|
||||
final class EventRouter implements OnItemTouchListener {
|
||||
|
||||
private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
|
||||
|
||||
EventRouter() {
|
||||
mDelegates = new ToolHandlerRegistry<>(new DummyOnItemTouchListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param toolType See MotionEvent for details on available types.
|
||||
* @param delegate An {@link OnItemTouchListener} to receive events
|
||||
* of {@code toolType}.
|
||||
*/
|
||||
void set(int toolType, @NonNull OnItemTouchListener delegate) {
|
||||
checkArgument(delegate != null);
|
||||
|
||||
mDelegates.set(toolType, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
return mDelegates.get(e).onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
mDelegates.get(e).onTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
// TODO(b/139141511): Handle onRequestDisallowInterceptTouchEvent.
|
||||
}
|
||||
}
|
|
@ -61,7 +61,22 @@ public abstract class FocusDelegate<K> {
|
|||
public abstract boolean hasFocusedItem();
|
||||
|
||||
/**
|
||||
* @return the position of the currently focused item, if any.
|
||||
* Returns the position of the currently focused item, or
|
||||
* {@link RecyclerView#NO_POSITION} if nothing is focused.
|
||||
*
|
||||
* <p>You must implement this feature if you intend your app
|
||||
* to work well with mouse and keyboard. Selection
|
||||
* ranges are inferred from focused item when there is
|
||||
* no explicit last-selected item.
|
||||
*
|
||||
* <p>You can manage and advance focus using keyboard arrows,
|
||||
* reflecting this state visibly in the view item.
|
||||
* Use can then press shift, then click another item with
|
||||
* their mouse to select all items between the focused
|
||||
* item and the clicked item.
|
||||
*
|
||||
* @return the position of the currently focused item,
|
||||
* or {@code RecyclerView#NO_POSITION} if none.
|
||||
*/
|
||||
public abstract int getFocusedPosition();
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Class allowing GestureDetector to listen directly to RecyclerView touch events.
|
||||
*/
|
||||
final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.OnItemTouchListener {
|
||||
|
||||
private final GestureDetector mDetector;
|
||||
|
||||
GestureDetectorOnItemTouchListenerAdapter(@NonNull GestureDetector detector) {
|
||||
checkArgument(detector != null);
|
||||
|
||||
mDetector = detector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
// While the idea of "intercepting" an event stream isn't consistent
|
||||
// with the world-view of GestureDetector, failure to return true here
|
||||
// resulted in a bug where a context menu shown on an item view was not
|
||||
// visible...despite returning reporting that the menu was shown.
|
||||
// See b/143494310 for further details.
|
||||
return mDetector.onTouchEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener>
|
|||
mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
GestureRouter() {
|
||||
this((T) new SimpleOnGestureListener());
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.core.util.Preconditions.checkState;
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
import static androidx.recyclerview.selection.Shared.VERBOSE;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
@ -36,7 +37,7 @@ import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
|||
* when used in conjunction with RecyclerView and other classes in the ReyclerView
|
||||
* selection support package.
|
||||
*/
|
||||
final class GestureSelectionHelper implements OnItemTouchListener {
|
||||
final class GestureSelectionHelper implements OnItemTouchListener, Resettable {
|
||||
|
||||
private static final String TAG = "GestureSelectionHelper";
|
||||
|
||||
|
@ -76,16 +77,19 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
* Explicitly kicks off a gesture multi-select.
|
||||
*/
|
||||
void start() {
|
||||
checkState(!mStarted);
|
||||
|
||||
// Partner code in MotionInputHandler ensures items
|
||||
// are selected and range anchor initialized prior to
|
||||
// start being called.
|
||||
// Verify the truth of that statement here
|
||||
// to make the implicit coupling less of a time bomb.
|
||||
checkState(mSelectionMgr.isRangeActive());
|
||||
|
||||
mLock.checkStopped();
|
||||
if (mStarted) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "Attempting to start, but state is already=started.");
|
||||
throw new IllegalStateException(
|
||||
"Attempting to start, but state is already=started.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mStarted = true;
|
||||
mLock.start();
|
||||
|
@ -94,15 +98,20 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
@Override
|
||||
/** @hide */
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
// MotionEvents that aren't ACTION_DOWN are only ever passed to either onInterceptTouchEvent
|
||||
// or onTouchEvent; never to both, so events delivered to this method are effectively
|
||||
// lost if we don't act on them in this method.
|
||||
//
|
||||
// TODO(b/132447183): For some reason we're not receiving an ACTION_UP
|
||||
// event after a > long-press NOT followed by a ACTION_MOVE < event.
|
||||
if (mStarted) {
|
||||
handleTouch(e);
|
||||
onTouchEvent(unused, e);
|
||||
}
|
||||
|
||||
// ACTION_CANCEL is associated with "TOOL_TYPE_UNKNOWN" and
|
||||
// is handled in ResetManager.
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
return mStarted;
|
||||
default:
|
||||
|
@ -113,27 +122,11 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
@Override
|
||||
/** @hide */
|
||||
public void onTouchEvent(@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
// See handleTouch(MotionEvent) javadoc for explanation as to why this is correct.
|
||||
handleTouch(e);
|
||||
if (!mStarted) {
|
||||
if (VERBOSE) Log.i(TAG, "Ignoring input event. Not started.");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If selection has started, will handle all appropriate types of MotionEvents and will return
|
||||
* true if this OnItemTouchListener should start intercepting the rest of the MotionEvents.
|
||||
*
|
||||
* <p>This code, and the fact that this method is used by both OnInterceptTouchEvent and
|
||||
* OnTouchEvent, is correct and valid because:
|
||||
* <ol>
|
||||
* <li>MotionEvents that aren't ACTION_DOWN are only ever passed to either onInterceptTouchEvent
|
||||
* or onTouchEvent; never to both. The MotionEvents we are handling in this method are not
|
||||
* ACTION_DOWN, and therefore, its appropriate that both the onInterceptTouchEvent and
|
||||
* onTouchEvent code paths cross this method.
|
||||
* <li>This method returns true when we want to intercept MotionEvents. OnInterceptTouchEvent
|
||||
* uses that information to determine its own return, and OnMotionEvent doesn't have a return
|
||||
* so this methods return value is irrelevant to it.
|
||||
* </ol>
|
||||
*/
|
||||
private void handleTouch(MotionEvent e) {
|
||||
if (!mSelectionMgr.isRangeActive()) {
|
||||
Log.e(TAG,
|
||||
"Internal state of GestureSelectionHelper out of sync w/ SelectionTracker "
|
||||
|
@ -141,6 +134,8 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
endSelection();
|
||||
}
|
||||
|
||||
// ACTION_CANCEL is associated with "TOOL_TYPE_UNKNOWN" and
|
||||
// is handled in ResetManager.
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
handleMoveEvent(e);
|
||||
|
@ -148,9 +143,6 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
case MotionEvent.ACTION_UP:
|
||||
handleUpEvent();
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handleCancelEvent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,17 +159,22 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
endSelection();
|
||||
}
|
||||
|
||||
// Called when ACTION_CANCEL event is to be handled.
|
||||
// This means this gesture selection is aborted, so reset everything and abandon provisional
|
||||
// selection.
|
||||
private void handleCancelEvent() {
|
||||
mSelectionMgr.clearProvisionalSelection();
|
||||
endSelection();
|
||||
/**
|
||||
* Immediately "Stops" active gesture selection, and resets all related state.
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
if (DEBUG) Log.d(TAG, "Received reset request.");
|
||||
mStarted = false;
|
||||
mScroller.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResetRequired() {
|
||||
return mStarted;
|
||||
}
|
||||
|
||||
private void endSelection() {
|
||||
checkState(mStarted);
|
||||
|
||||
mStarted = false;
|
||||
mScroller.reset();
|
||||
mLock.stop();
|
||||
|
@ -186,6 +183,11 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
// Call when an intercepted ACTION_MOVE event is passed down.
|
||||
// At this point, we are sure user wants to gesture multi-select.
|
||||
private void handleMoveEvent(@NonNull MotionEvent e) {
|
||||
if (!mStarted) {
|
||||
Log.e(TAG, "Received event while not started.");
|
||||
if (DEBUG) throw new IllegalStateException("Received event while not started.");
|
||||
}
|
||||
|
||||
int lastGlidedItemPos = mView.getLastGlidedItemPosition(e);
|
||||
if (mSelectionPredicate.canSetStateAtPosition(lastGlidedItemPos, true)) {
|
||||
extendSelection(lastGlidedItemPos);
|
||||
|
@ -283,7 +285,7 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
|||
// of items in the adapter. Using the adapter is the for sure way to get the actual last
|
||||
// item position.
|
||||
final float inboundY = getInboundY(mRecyclerView.getHeight(), e.getY());
|
||||
return (pastLastItem) ? mRecyclerView.getAdapter().getItemCount() - 1
|
||||
return pastLastItem ? mRecyclerView.getAdapter().getItemCount() - 1
|
||||
: mRecyclerView.getChildAdapterPosition(
|
||||
mRecyclerView.findChildViewUnder(e.getX(), inboundY));
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ final class GridModel<K> {
|
|||
private final ItemKeyProvider<K> mKeyProvider;
|
||||
private final SelectionPredicate<K> mSelectionPredicate;
|
||||
|
||||
private final List<SelectionObserver> mOnSelectionChangedListeners = new ArrayList<>();
|
||||
private final List<SelectionObserver<K>> mOnSelectionChangedListeners = new ArrayList<>();
|
||||
|
||||
// Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed
|
||||
// by their y-offset. For example, if the first column of the view starts at an x-value of 5,
|
||||
|
@ -101,8 +101,9 @@ final class GridModel<K> {
|
|||
|
||||
private final OnScrollListener mScrollListener;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
GridModel(
|
||||
GridHost host,
|
||||
GridHost<K> host,
|
||||
ItemKeyProvider<K> keyProvider,
|
||||
SelectionPredicate<K> selectionPredicate) {
|
||||
|
||||
|
@ -284,8 +285,9 @@ final class GridModel<K> {
|
|||
* mSelection, so computeCurrentSelection() should be called before this
|
||||
* function.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void notifySelectionChanged() {
|
||||
for (SelectionObserver listener : mOnSelectionChangedListeners) {
|
||||
for (SelectionObserver<K> listener : mOnSelectionChangedListeners) {
|
||||
listener.onSelectionChanged(mSelection);
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +403,7 @@ final class GridModel<K> {
|
|||
abstract void onSelectionChanged(Set<K> updatedSelection);
|
||||
}
|
||||
|
||||
void addOnSelectionChangedListener(SelectionObserver listener) {
|
||||
void addOnSelectionChangedListener(SelectionObserver<K> listener) {
|
||||
mOnSelectionChangedListeners.add(listener);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,10 +44,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
* mRecyclerView = recyclerView;
|
||||
* }
|
||||
*
|
||||
* public ItemDetails<Uri> getItemDetails(MotionEvent e) {
|
||||
* View view = mRecView.findChildViewUnder(e.getX(), e.getY());
|
||||
* public @Nullable ItemDetails<Uri> getItemDetails(@NonNull MotionEvent e) {
|
||||
* View view = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
|
||||
* if (view != null) {
|
||||
* ViewHolder holder = mRecView.getChildViewHolder(view);
|
||||
* ViewHolder holder = mRecyclerView.getChildViewHolder(view);
|
||||
* if (holder instanceof MyHolder) {
|
||||
* return ((MyHolder) holder).getItemDetails();
|
||||
* }
|
||||
|
@ -110,10 +110,6 @@ public abstract class ItemDetailsLookup<K> {
|
|||
return item != null && item.getSelectionKey() != null;
|
||||
}
|
||||
|
||||
private static boolean hasPosition(@Nullable ItemDetails<?> item) {
|
||||
return item != null && item.getPosition() != RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ItemDetails for the item under the event, or null.
|
||||
*/
|
||||
|
@ -241,10 +237,10 @@ public abstract class ItemDetailsLookup<K> {
|
|||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (obj instanceof ItemDetails)
|
||||
&& isEqualTo((ItemDetails) obj);
|
||||
&& isEqualTo((ItemDetails<?>) obj);
|
||||
}
|
||||
|
||||
private boolean isEqualTo(@NonNull ItemDetails other) {
|
||||
private boolean isEqualTo(@NonNull ItemDetails<?> other) {
|
||||
K key = getSelectionKey();
|
||||
boolean sameKeys = false;
|
||||
if (key == null) {
|
||||
|
|
|
@ -33,6 +33,14 @@ final class MotionEvents {
|
|||
return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
|
||||
}
|
||||
|
||||
static boolean isFingerEvent(@NonNull MotionEvent e) {
|
||||
return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
|
||||
}
|
||||
|
||||
static boolean isActionDown(@NonNull MotionEvent e) {
|
||||
return e.getActionMasked() == MotionEvent.ACTION_DOWN;
|
||||
}
|
||||
|
||||
static boolean isActionMove(@NonNull MotionEvent e) {
|
||||
return e.getActionMasked() == MotionEvent.ACTION_MOVE;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.core.util.Preconditions.checkState;
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
import static androidx.recyclerview.selection.Shared.VERBOSE;
|
||||
|
||||
|
@ -123,7 +122,11 @@ final class MouseInputHandler<K> extends MotionInputHandler<K> {
|
|||
// tap on an item when there is an existing selection. We could extend
|
||||
// a selection, we could clear selection (then launch)
|
||||
private void onItemClick(@NonNull MotionEvent e, @NonNull ItemDetails<K> item) {
|
||||
checkState(mSelectionTracker.hasSelection());
|
||||
if (!mSelectionTracker.hasSelection()) {
|
||||
Log.e(TAG, "Call to onItemClick w/o selection.");
|
||||
if (DEBUG) throw new IllegalStateException("Call to onItemClick w/o selection.");
|
||||
return;
|
||||
}
|
||||
checkArgument(item != null);
|
||||
|
||||
if (shouldExtendRange(e)) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.core.util.Preconditions.checkState;
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
|
@ -24,7 +25,7 @@ import android.util.Log;
|
|||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -36,7 +37,7 @@ import java.util.List;
|
|||
*
|
||||
* <p>
|
||||
* The host {@link android.app.Activity} or {@link android.app.Fragment} should avoid changing
|
||||
* {@link RecyclerView.Adapter Adapter} data while there
|
||||
* {@link androidx.recyclerview.widget.RecyclerView.Adapter Adapter} data while there
|
||||
* are active selection operations, as this can result in a poor user experience.
|
||||
*
|
||||
* <p>
|
||||
|
@ -46,17 +47,33 @@ public final class OperationMonitor {
|
|||
|
||||
private static final String TAG = "OperationMonitor";
|
||||
|
||||
private final List<OnChangeListener> mListeners = new ArrayList<>();
|
||||
|
||||
// Ideally OperationMonitor would implement Resettable
|
||||
// directly, but Metalava couldn't understand that
|
||||
// `OperationMonitor` was public API while `Resettable` was
|
||||
// not. This is our klunkuy workaround.
|
||||
private final Resettable mResettable = new Resettable() {
|
||||
|
||||
@Override
|
||||
public boolean isResetRequired() {
|
||||
return OperationMonitor.this.isResetRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
OperationMonitor.this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
private int mNumOps = 0;
|
||||
private List<OnChangeListener> mListeners = new ArrayList<>();
|
||||
|
||||
@MainThread
|
||||
synchronized void start() {
|
||||
mNumOps++;
|
||||
|
||||
if (mNumOps == 1) {
|
||||
for (OnChangeListener l : mListeners) {
|
||||
l.onChanged();
|
||||
}
|
||||
notifyStateChanged();
|
||||
}
|
||||
|
||||
if (DEBUG) Log.v(TAG, "Incremented content lock count to " + mNumOps + ".");
|
||||
|
@ -64,29 +81,46 @@ public final class OperationMonitor {
|
|||
|
||||
@MainThread
|
||||
synchronized void stop() {
|
||||
checkState(mNumOps > 0);
|
||||
if (mNumOps == 0) {
|
||||
if (DEBUG) Log.w(TAG, "Stop called whith opt count of 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
mNumOps--;
|
||||
if (DEBUG) Log.v(TAG, "Decremented content lock count to " + mNumOps + ".");
|
||||
|
||||
if (mNumOps == 0) {
|
||||
for (OnChangeListener l : mListeners) {
|
||||
l.onChanged();
|
||||
notifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY)
|
||||
@MainThread
|
||||
synchronized void reset() {
|
||||
if (DEBUG) Log.d(TAG, "Received reset request.");
|
||||
if (mNumOps > 0) {
|
||||
Log.w(TAG, "Resetting OperationMonitor with " + mNumOps + " active operations.");
|
||||
}
|
||||
mNumOps = 0;
|
||||
notifyStateChanged();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY)
|
||||
synchronized boolean isResetRequired() {
|
||||
return isStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there are any running operations.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public synchronized boolean isStarted() {
|
||||
return mNumOps > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers supplied listener to be notified when operation status changes.
|
||||
* @param listener
|
||||
*/
|
||||
public void addListener(@NonNull OnChangeListener listener) {
|
||||
checkArgument(listener != null);
|
||||
|
@ -95,7 +129,6 @@ public final class OperationMonitor {
|
|||
|
||||
/**
|
||||
* Unregisters listener for further notifications.
|
||||
* @param listener
|
||||
*/
|
||||
public void removeListener(@NonNull OnChangeListener listener) {
|
||||
checkArgument(listener != null);
|
||||
|
@ -105,15 +138,27 @@ public final class OperationMonitor {
|
|||
/**
|
||||
* Allows other selection code to perform a precondition check asserting the state is locked.
|
||||
*/
|
||||
void checkStarted() {
|
||||
void checkStarted(boolean started) {
|
||||
if (started) {
|
||||
checkState(mNumOps > 0);
|
||||
} else {
|
||||
checkState(mNumOps == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStateChanged() {
|
||||
for (OnChangeListener l : mListeners) {
|
||||
l.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows other selection code to perform a precondition check asserting the state is unlocked.
|
||||
* Work around b/139109223.
|
||||
* @hide
|
||||
*/
|
||||
void checkStopped() {
|
||||
checkState(mNumOps == 0);
|
||||
@RestrictTo(LIBRARY)
|
||||
@NonNull Resettable asResettable() {
|
||||
return mResettable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,19 +25,19 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
|
||||
/**
|
||||
* OnItemTouchListener that delegates drag events to a drag listener,
|
||||
* OnItemTouchListener that detects and delegates drag events to a drag listener,
|
||||
* else sends event to fallback {@link OnItemTouchListener}.
|
||||
*
|
||||
* <p>See {@link OnDragInitiatedListener} for details on implementing drag and drop.
|
||||
*/
|
||||
final class PointerDragEventInterceptor implements OnItemTouchListener {
|
||||
|
||||
private final ItemDetailsLookup mEventDetailsLookup;
|
||||
private final ItemDetailsLookup<?> mEventDetailsLookup;
|
||||
private final OnDragInitiatedListener mDragListener;
|
||||
private @Nullable OnItemTouchListener mDelegate;
|
||||
private OnItemTouchListener mDelegate;
|
||||
|
||||
PointerDragEventInterceptor(
|
||||
ItemDetailsLookup eventDetailsLookup,
|
||||
ItemDetailsLookup<?> eventDetailsLookup,
|
||||
OnDragInitiatedListener dragListener,
|
||||
@Nullable OnItemTouchListener delegate) {
|
||||
|
||||
|
@ -46,30 +46,29 @@ final class PointerDragEventInterceptor implements OnItemTouchListener {
|
|||
|
||||
mEventDetailsLookup = eventDetailsLookup;
|
||||
mDragListener = dragListener;
|
||||
|
||||
if (delegate != null) {
|
||||
mDelegate = delegate;
|
||||
} else {
|
||||
mDelegate = new DummyOnItemTouchListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
if (MotionEvents.isPointerDragEvent(e) && mEventDetailsLookup.inItemDragRegion(e)) {
|
||||
return mDragListener.onDragInitiated(e);
|
||||
} else if (mDelegate != null) {
|
||||
return mDelegate.onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
return false;
|
||||
return mDelegate.onInterceptTouchEvent(rv, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onTouchEvent(rv, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onRequestDisallowInterceptTouchEvent(disallowIntercept);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,12 +54,14 @@ final class Range {
|
|||
* provisional selection will not affect the primary selection where the two may intersect.
|
||||
*/
|
||||
static final int TYPE_PROVISIONAL = 1;
|
||||
|
||||
@IntDef({
|
||||
TYPE_PRIMARY,
|
||||
TYPE_PROVISIONAL
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface RangeType {}
|
||||
@interface RangeType {
|
||||
}
|
||||
|
||||
private static final String TAG = "Range";
|
||||
|
||||
|
@ -69,9 +71,6 @@ final class Range {
|
|||
|
||||
/**
|
||||
* Creates a new range anchored at {@code position}.
|
||||
*
|
||||
* @param position
|
||||
* @param callbacks
|
||||
*/
|
||||
Range(int position, @NonNull Callbacks callbacks) {
|
||||
mBegin = position;
|
||||
|
@ -118,7 +117,7 @@ final class Range {
|
|||
} else if (mEnd < mBegin) {
|
||||
reviseDescending(position, type);
|
||||
}
|
||||
// the "else" case is covered by checkState at beginning of method.
|
||||
// the "else" case is covered by checkArgument at beginning of method.
|
||||
|
||||
mEnd = position;
|
||||
}
|
||||
|
@ -170,11 +169,6 @@ final class Range {
|
|||
mCallbacks.updateForRange(begin, end, selected, type);
|
||||
}
|
||||
|
||||
boolean isOverlapping(int position, int count) {
|
||||
return (position >= mBegin && position <= mEnd) ||
|
||||
(position + count >= mBegin && position + count <= mEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class managing resetting of library state in response to specific
|
||||
* events like clearing of selection and MotionEvent.ACTION_CANCEL
|
||||
* events.
|
||||
*
|
||||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
*/
|
||||
final class ResetManager<K> {
|
||||
|
||||
private static final String TAG = "ResetManager";
|
||||
|
||||
private final List<Resettable> mResetHandlers = new ArrayList<>();
|
||||
|
||||
private final OnItemTouchListener mInputListener = new OnItemTouchListener() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView unused,
|
||||
@NonNull MotionEvent e) {
|
||||
if (MotionEvents.isActionCancel(e)) {
|
||||
if (DEBUG) Log.d(TAG, "Received CANCEL event.");
|
||||
callResetHandlers();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
};
|
||||
|
||||
// Resettable interface has a #requiresReset method because DefaultSelectionTracker
|
||||
// (owner of the state we observe with our SelectionObserver) is, itself,
|
||||
// a Resettable. Such an arrangement introduces the real possibility of infinite recursion.
|
||||
// When we call reset on DefaultSelectionTracker it'll eventually call back to
|
||||
// notify us of the change via onSelectionCleared. We avoid recursion by
|
||||
// checking #requiresReset before calling reset again.
|
||||
private final SelectionObserver<K> mSelectionObserver = new SelectionObserver<K>() {
|
||||
@Override
|
||||
protected void onSelectionCleared() {
|
||||
if (DEBUG) Log.d(TAG, "Received onSelectionCleared event.");
|
||||
callResetHandlers();
|
||||
}
|
||||
};
|
||||
|
||||
SelectionObserver<K> getSelectionObserver() {
|
||||
return mSelectionObserver;
|
||||
}
|
||||
|
||||
OnItemTouchListener getInputListener() {
|
||||
return mInputListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new Resettable.
|
||||
*/
|
||||
void addResetHandler(@NonNull Resettable handler) {
|
||||
mResetHandlers.add(handler);
|
||||
}
|
||||
|
||||
void callResetHandlers() {
|
||||
for (Resettable handler : mResetHandlers) {
|
||||
if (handler.isResetRequired()) {
|
||||
handler.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
/**
|
||||
* Represents an object that can be reset and can advise on it's
|
||||
* need to be reset.
|
||||
*
|
||||
* <p>Calling {@link #isResetRequired()} on an instance of {@link Resettable}
|
||||
* should always return false when called immediately after {@link #reset()}
|
||||
* has been called.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
public interface Resettable {
|
||||
|
||||
/**
|
||||
* @return true if the object requires reset.
|
||||
*/
|
||||
boolean isResetRequired();
|
||||
|
||||
/**
|
||||
* Resets the object state.
|
||||
*/
|
||||
void reset();
|
||||
}
|
|
@ -54,9 +54,8 @@ import java.util.Set;
|
|||
* (which can be initiated by long pressing an unselected item while there is an
|
||||
* existing selection).
|
||||
*
|
||||
* @see MutableSelection
|
||||
*
|
||||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
* @see MutableSelection
|
||||
*/
|
||||
public class Selection<K> implements Iterable<K> {
|
||||
|
||||
|
@ -78,7 +77,6 @@ public class Selection<K> implements Iterable<K> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return true if the position is currently selected.
|
||||
*/
|
||||
public boolean contains(@Nullable K key) {
|
||||
|
@ -92,7 +90,7 @@ public class Selection<K> implements Iterable<K> {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
public @NonNull Iterator<K> iterator() {
|
||||
return mSelection.iterator();
|
||||
}
|
||||
|
||||
|
@ -114,6 +112,7 @@ public class Selection<K> implements Iterable<K> {
|
|||
* Sets the provisional selection, which is a temporary selection that can be saved,
|
||||
* canceled, or adjusted at a later time. When a new provision selection is applied, the old
|
||||
* one (if it exists) is abandoned.
|
||||
*
|
||||
* @return Map of ids added or removed. Added ids have a value of true, removed are false.
|
||||
*/
|
||||
Map<K, Boolean> setProvisionalSelection(@NonNull Set<K> newSelection) {
|
||||
|
@ -237,10 +236,10 @@ public class Selection<K> implements Iterable<K> {
|
|||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other)
|
||||
|| (other instanceof Selection && isEqualTo((Selection) other));
|
||||
|| (other instanceof Selection && isEqualTo((Selection<?>) other));
|
||||
}
|
||||
|
||||
private boolean isEqualTo(Selection other) {
|
||||
private boolean isEqualTo(Selection<?> other) {
|
||||
return mSelection.equals(other.mSelection)
|
||||
&& mProvisionalSelection.equals(other.mProvisionalSelection);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public final class SelectionPredicates {
|
|||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
* @return
|
||||
*/
|
||||
public static <K> SelectionPredicate<K> createSelectAnything() {
|
||||
public static @NonNull <K> SelectionPredicate<K> createSelectAnything() {
|
||||
return new SelectionPredicate<K>() {
|
||||
@Override
|
||||
public boolean canSetStateForKey(@NonNull K key, boolean nextState) {
|
||||
|
@ -60,7 +60,7 @@ public final class SelectionPredicates {
|
|||
* @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
|
||||
* @return
|
||||
*/
|
||||
public static <K> SelectionPredicate<K> createSelectSingleAnything() {
|
||||
public static @NonNull <K> SelectionPredicate<K> createSelectSingleAnything() {
|
||||
return new SelectionPredicate<K>() {
|
||||
@Override
|
||||
public boolean canSetStateForKey(@NonNull K key, boolean nextState) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import static androidx.core.util.Preconditions.checkArgument;
|
|||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
|
@ -89,6 +90,8 @@ import java.util.Set;
|
|||
*/
|
||||
public abstract class SelectionTracker<K> {
|
||||
|
||||
private static final String TAG = "SelectionTracker";
|
||||
|
||||
/**
|
||||
* This value is included in the payload when SelectionTracker notifies RecyclerView
|
||||
* of changes to selection. Look for this value in the {@code payload}
|
||||
|
@ -110,7 +113,7 @@ public abstract class SelectionTracker<K> {
|
|||
* may use an observer to control the enabled status of menu items,
|
||||
* or to initiate {@link android.view.ActionMode}.
|
||||
*/
|
||||
public abstract void addObserver(SelectionObserver observer);
|
||||
public abstract void addObserver(@NonNull SelectionObserver<K> observer);
|
||||
|
||||
/** @return true if has a selection */
|
||||
public abstract boolean hasSelection();
|
||||
|
@ -123,7 +126,7 @@ public abstract class SelectionTracker<K> {
|
|||
* of the selection that will not reflect future changes
|
||||
* to selection.
|
||||
*/
|
||||
public abstract Selection<K> getSelection();
|
||||
public abstract @NonNull Selection<K> getSelection();
|
||||
|
||||
/**
|
||||
* Updates {@code dest} to reflect the current selection.
|
||||
|
@ -144,9 +147,8 @@ public abstract class SelectionTracker<K> {
|
|||
* This affords clients the ability to restore selection from selection saved
|
||||
* in Activity state.
|
||||
*
|
||||
* @see StorageStrategy details on selection state support.
|
||||
*
|
||||
* @param selection selection being restored.
|
||||
* @see StorageStrategy details on selection state support.
|
||||
*/
|
||||
protected abstract void restoreSelection(@NonNull Selection<K> selection);
|
||||
|
||||
|
@ -181,7 +183,7 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract AdapterDataObserver getAdapterDataObserver();
|
||||
protected abstract @NonNull AdapterDataObserver getAdapterDataObserver();
|
||||
|
||||
/**
|
||||
* Attempts to establish a range selection at {@code position}, selecting the item
|
||||
|
@ -215,6 +217,7 @@ public abstract class SelectionTracker<K> {
|
|||
* Clears an in-progress range selection. Provisional range selection established
|
||||
* using {@link #extendProvisionalRange(int)} will be cleared (unless
|
||||
* {@link #mergeProvisionalSelection()} is called first.)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
|
@ -251,7 +254,7 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
/**
|
||||
* Sets the provisional selection, replacing any existing selection.
|
||||
* @param newSelection
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
|
@ -259,6 +262,7 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
/**
|
||||
* Clears any existing provisional selection
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
|
@ -267,6 +271,7 @@ public abstract class SelectionTracker<K> {
|
|||
/**
|
||||
* Converts the provisional selection into primary selection, then clears
|
||||
* provisional selection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
|
@ -300,6 +305,16 @@ public abstract class SelectionTracker<K> {
|
|||
public void onItemStateChanged(@NonNull K key, boolean selected) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Selection is cleared.
|
||||
* TODO(smckay): Make public in a future public API.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY)
|
||||
protected void onSelectionCleared() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the underlying data set has changed. After this method is called
|
||||
* SelectionTracker will traverse the existing selection,
|
||||
|
@ -490,9 +505,9 @@ public abstract class SelectionTracker<K> {
|
|||
private BandPredicate mBandPredicate;
|
||||
private int mBandOverlayId = eu.faircode.email.R.drawable.selection_band_overlay;
|
||||
|
||||
// TODO(b/144500333): Remove support for overriding gesture and pointer tooltypes.
|
||||
private int[] mGestureToolTypes = new int[]{
|
||||
MotionEvent.TOOL_TYPE_FINGER,
|
||||
MotionEvent.TOOL_TYPE_UNKNOWN
|
||||
MotionEvent.TOOL_TYPE_FINGER
|
||||
};
|
||||
|
||||
private int[] mPointerToolTypes = new int[]{
|
||||
|
@ -545,7 +560,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param predicate the predicate to be used.
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withSelectionPredicate(
|
||||
public @NonNull Builder<K> withSelectionPredicate(
|
||||
@NonNull SelectionPredicate<K> predicate) {
|
||||
|
||||
checkArgument(predicate != null);
|
||||
|
@ -560,7 +575,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param monitor the monitor to be used
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withOperationMonitor(
|
||||
public @NonNull Builder<K> withOperationMonitor(
|
||||
@NonNull OperationMonitor monitor) {
|
||||
|
||||
checkArgument(monitor != null);
|
||||
|
@ -574,7 +589,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param delegate the delegate to be used
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withFocusDelegate(@NonNull FocusDelegate<K> delegate) {
|
||||
public @NonNull Builder<K> withFocusDelegate(@NonNull FocusDelegate<K> delegate) {
|
||||
checkArgument(delegate != null);
|
||||
mFocusDelegate = delegate;
|
||||
return this;
|
||||
|
@ -586,7 +601,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param listener the listener to be used
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withOnItemActivatedListener(
|
||||
public @NonNull Builder<K> withOnItemActivatedListener(
|
||||
@NonNull OnItemActivatedListener<K> listener) {
|
||||
|
||||
checkArgument(listener != null);
|
||||
|
@ -601,7 +616,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param listener the listener to be used
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withOnContextClickListener(
|
||||
public @NonNull Builder<K> withOnContextClickListener(
|
||||
@NonNull OnContextClickListener listener) {
|
||||
|
||||
checkArgument(listener != null);
|
||||
|
@ -616,7 +631,7 @@ public abstract class SelectionTracker<K> {
|
|||
* @param listener the listener to be used
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withOnDragInitiatedListener(
|
||||
public @NonNull Builder<K> withOnDragInitiatedListener(
|
||||
@NonNull OnDragInitiatedListener listener) {
|
||||
|
||||
checkArgument(listener != null);
|
||||
|
@ -627,12 +642,17 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
/**
|
||||
* Replaces default tap and gesture tool-types. Defaults are:
|
||||
* {@link MotionEvent#TOOL_TYPE_FINGER} and {@link MotionEvent#TOOL_TYPE_UNKNOWN}.
|
||||
* {@link MotionEvent#TOOL_TYPE_FINGER}.
|
||||
*
|
||||
* @param toolTypes the tool types to be used
|
||||
* @return this
|
||||
*
|
||||
* @deprecated GestureSelection is best bound to {@link MotionEvent#TOOL_TYPE_FINGER},
|
||||
* and only that tool type. This method will be removed in a future release.
|
||||
*/
|
||||
public Builder<K> withGestureTooltypes(int... toolTypes) {
|
||||
@Deprecated
|
||||
public @NonNull Builder<K> withGestureTooltypes(@NonNull int... toolTypes) {
|
||||
Log.w(TAG, "Setting gestureTooltypes is likely to result in unexpected behavior.");
|
||||
mGestureToolTypes = toolTypes;
|
||||
return this;
|
||||
}
|
||||
|
@ -640,22 +660,19 @@ public abstract class SelectionTracker<K> {
|
|||
/**
|
||||
* Replaces default band overlay.
|
||||
*
|
||||
* @param bandOverlayId
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
|
||||
public @NonNull Builder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
|
||||
mBandOverlayId = bandOverlayId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces default band predicate.
|
||||
* @param bandPredicate
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public Builder<K> withBandPredicate(@NonNull BandPredicate bandPredicate) {
|
||||
checkArgument(bandPredicate != null);
|
||||
|
||||
public @NonNull Builder<K> withBandPredicate(@NonNull BandPredicate bandPredicate) {
|
||||
mBandPredicate = bandPredicate;
|
||||
return this;
|
||||
}
|
||||
|
@ -668,8 +685,13 @@ public abstract class SelectionTracker<K> {
|
|||
*
|
||||
* @param toolTypes the tool types to be used
|
||||
* @return this
|
||||
*
|
||||
* @deprecated PointerSelection is best bound to {@link MotionEvent#TOOL_TYPE_MOUSE},
|
||||
* and only that tool type. This method will be removed in a future release.
|
||||
*/
|
||||
public Builder<K> withPointerTooltypes(int... toolTypes) {
|
||||
@Deprecated
|
||||
public @NonNull Builder<K> withPointerTooltypes(@NonNull int... toolTypes) {
|
||||
Log.w(TAG, "Setting pointerTooltypes is likely to result in unexpected behavior.");
|
||||
mPointerToolTypes = toolTypes;
|
||||
return this;
|
||||
}
|
||||
|
@ -679,9 +701,9 @@ public abstract class SelectionTracker<K> {
|
|||
*
|
||||
* @return this
|
||||
*/
|
||||
public SelectionTracker<K> build() {
|
||||
public @NonNull SelectionTracker<K> build() {
|
||||
|
||||
SelectionTracker<K> tracker = new DefaultSelectionTracker<>(
|
||||
DefaultSelectionTracker<K> tracker = new DefaultSelectionTracker<>(
|
||||
mSelectionId, mKeyProvider, mSelectionPredicate, mStorage);
|
||||
|
||||
// Event glue between RecyclerView and SelectionTracker keeps the classes separate
|
||||
|
@ -689,6 +711,8 @@ public abstract class SelectionTracker<K> {
|
|||
// represent the same data in different ways.
|
||||
EventBridge.install(mAdapter, tracker, mKeyProvider);
|
||||
|
||||
// Scroller is stateful and can be reset, but we don't manage it directly.
|
||||
// GestureSelectionHelper will reset scroller when it is reset.
|
||||
AutoScroller scroller =
|
||||
new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecyclerView));
|
||||
|
||||
|
@ -698,14 +722,10 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
// GestureRouter is responsible for routing GestureDetector events
|
||||
// to tool-type specific handlers.
|
||||
GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
|
||||
GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
|
||||
GestureRouter<MotionInputHandler<K>> gestureRouter = new GestureRouter<>();
|
||||
|
||||
// TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
|
||||
// Despite "Touch" being in the name, it receives events for all types of tools.
|
||||
// This class is responsible for routing events to tool-type specific handlers,
|
||||
// and if not handled by a handler, on to a GestureDetector for analysis.
|
||||
TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
|
||||
// GestureDetector cancels itself in response to ACTION_CANCEL events.
|
||||
GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
|
||||
|
||||
// GestureSelectionHelper provides logic that interprets a combination
|
||||
// of motions and gestures in order to provide gesture driven selection support
|
||||
|
@ -713,8 +733,37 @@ public abstract class SelectionTracker<K> {
|
|||
final GestureSelectionHelper gestureHelper = GestureSelectionHelper.create(
|
||||
tracker, mSelectionPredicate, mRecyclerView, scroller, mMonitor);
|
||||
|
||||
// Finally hook the framework up to listening to recycle view events.
|
||||
// EventRouter receives events for RecyclerView, dispatching to handlers
|
||||
// registered by tool-type.
|
||||
EventRouter eventRouter = new EventRouter();
|
||||
|
||||
// Finally hook the framework up to listening to RecycleView events.
|
||||
mRecyclerView.addOnItemTouchListener(eventRouter);
|
||||
mRecyclerView.addOnItemTouchListener(
|
||||
new GestureDetectorOnItemTouchListenerAdapter(gestureDetector));
|
||||
|
||||
// Reset manager listens for cancel events from RecyclerView. In response to that it
|
||||
// advises other classes it is time to reset state.
|
||||
ResetManager<K> resetMgr = new ResetManager<>();
|
||||
|
||||
// Register ResetManager to:
|
||||
//
|
||||
// 1. Monitor selection reset which can be invoked by clients in response
|
||||
// to back key press and some application lifecycle events.
|
||||
//
|
||||
// 2. Monitor ACTION_CANCEL events (which arrive exclusively
|
||||
// via TOOL_TYPE_UNKNOWN).
|
||||
tracker.addObserver(resetMgr.getSelectionObserver());
|
||||
|
||||
// CAUTION! Registering resetMgr directly with RecyclerView#addOnItemTouchListener
|
||||
// will not work as expected. Once EventRouter returns true, RecyclerView will
|
||||
// no longer dispatch any events to other listeners for the duration of the
|
||||
// stream, not even ACTION_CANCEL events.
|
||||
eventRouter.set(MotionEvent.TOOL_TYPE_UNKNOWN, resetMgr.getInputListener());
|
||||
|
||||
resetMgr.addResetHandler(tracker);
|
||||
resetMgr.addResetHandler(mMonitor.asResettable());
|
||||
resetMgr.addResetHandler(gestureHelper);
|
||||
|
||||
// But before you move on, there's more work to do. Event plumbing has been
|
||||
// installed, but we haven't registered any of our helpers or callbacks.
|
||||
|
@ -757,7 +806,7 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
// Provides high level glue for binding touch events
|
||||
// and gestures to selection framework.
|
||||
TouchInputHandler<K> touchHandler = new TouchInputHandler<K>(
|
||||
TouchInputHandler<K> touchHandler = new TouchInputHandler<>(
|
||||
tracker,
|
||||
mKeyProvider,
|
||||
mDetailsLookup,
|
||||
|
@ -768,8 +817,8 @@ public abstract class SelectionTracker<K> {
|
|||
if (mSelectionPredicate.canSelectMultiple()) {
|
||||
try {
|
||||
gestureHelper.start();
|
||||
} catch (IllegalStateException ex) {
|
||||
eu.faircode.email.Log.w(ex);
|
||||
} catch (Throwable ex) {
|
||||
eu.faircode.email.Log.e(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -786,7 +835,7 @@ public abstract class SelectionTracker<K> {
|
|||
|
||||
for (int toolType : mGestureToolTypes) {
|
||||
gestureRouter.register(toolType, touchHandler);
|
||||
eventRouter.register(toolType, gestureHelper);
|
||||
eventRouter.set(toolType, gestureHelper);
|
||||
}
|
||||
|
||||
// Provides high level glue for binding mouse events and gestures
|
||||
|
@ -803,7 +852,7 @@ public abstract class SelectionTracker<K> {
|
|||
gestureRouter.register(toolType, mouseHandler);
|
||||
}
|
||||
|
||||
@Nullable BandSelectionHelper bandHelper = null;
|
||||
@Nullable BandSelectionHelper<K> bandHelper = null;
|
||||
|
||||
// Band selection not supported in single select mode, or when key access
|
||||
// is limited to anything less than the entire corpus.
|
||||
|
@ -824,14 +873,14 @@ public abstract class SelectionTracker<K> {
|
|||
mBandPredicate,
|
||||
mFocusDelegate,
|
||||
mMonitor);
|
||||
|
||||
resetMgr.addResetHandler(bandHelper);
|
||||
}
|
||||
|
||||
OnItemTouchListener pointerEventHandler = new PointerDragEventInterceptor(
|
||||
mDetailsLookup, mOnDragInitiatedListener, bandHelper);
|
||||
|
||||
for (int toolType : mPointerToolTypes) {
|
||||
eventRouter.register(toolType, pointerEventHandler);
|
||||
}
|
||||
eventRouter.set(MotionEvent.TOOL_TYPE_MOUSE, pointerEventHandler);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ public abstract class StorageStrategy<K> {
|
|||
* Create a {@link Selection} from supplied {@link Bundle}.
|
||||
*
|
||||
* @param state Bundle instance that may contain parceled Selection instance.
|
||||
* @return
|
||||
*/
|
||||
public abstract @Nullable Selection<K> asSelection(@NonNull Bundle state);
|
||||
|
||||
|
@ -77,7 +76,6 @@ public abstract class StorageStrategy<K> {
|
|||
* Creates a {@link Bundle} from supplied {@link Selection}.
|
||||
*
|
||||
* @param selection The selection to asBundle.
|
||||
* @return
|
||||
*/
|
||||
public abstract @NonNull Bundle asBundle(@NonNull Selection<K> selection);
|
||||
|
||||
|
@ -89,21 +87,22 @@ public abstract class StorageStrategy<K> {
|
|||
* @return StorageStrategy suitable for use with {@link Parcelable} keys
|
||||
* (like {@link android.net.Uri}).
|
||||
*/
|
||||
public static <K extends Parcelable> StorageStrategy<K> createParcelableStorage(Class<K> type) {
|
||||
return new ParcelableStorageStrategy(type);
|
||||
public static @NonNull <K extends Parcelable> StorageStrategy<K> createParcelableStorage(
|
||||
@NonNull Class<K> type) {
|
||||
return new ParcelableStorageStrategy<>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StorageStrategy suitable for use with {@link String} keys.
|
||||
*/
|
||||
public static StorageStrategy<String> createStringStorage() {
|
||||
public static @NonNull StorageStrategy<String> createStringStorage() {
|
||||
return new StringStorageStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StorageStrategy suitable for use with {@link Long} keys.
|
||||
*/
|
||||
public static StorageStrategy<Long> createLongStorage() {
|
||||
public static @NonNull StorageStrategy<Long> createLongStorage() {
|
||||
return new LongStorageStrategy();
|
||||
}
|
||||
|
||||
|
@ -191,7 +190,7 @@ public abstract class StorageStrategy<K> {
|
|||
private static class ParcelableStorageStrategy<K extends Parcelable>
|
||||
extends StorageStrategy<K> {
|
||||
|
||||
ParcelableStorageStrategy(Class<K> type) {
|
||||
ParcelableStorageStrategy(@NonNull Class<K> type) {
|
||||
super(type);
|
||||
checkArgument(Parcelable.class.isAssignableFrom(type));
|
||||
}
|
||||
|
|
|
@ -30,29 +30,27 @@ import java.util.List;
|
|||
/**
|
||||
* Registry for tool specific event handler. This provides map like functionality,
|
||||
* along with fallback to a default handler, while avoiding auto-boxing of tool
|
||||
* type values that would be necessitated where a Map used.
|
||||
* type values that would be necessitated were a Map used.k
|
||||
*
|
||||
* <p>ToolHandlerRegistry guarantees that it will never return a null handler ensuring
|
||||
* client code isn't peppered with null checks. To that end a default handler
|
||||
* is required. This default handler will be returned when a handler matching
|
||||
* the event tooltype has not be registered using {@link #set(int, T)}.
|
||||
*
|
||||
* @param <T> type of item being registered.
|
||||
*/
|
||||
final class ToolHandlerRegistry<T> {
|
||||
|
||||
// Currently there are four known input types. ERASER is the last one, so has the
|
||||
// highest value. UNKNOWN is zero, so we add one. This allows delegates to be
|
||||
// registered by type, and avoid the auto-boxing that would be necessary were we
|
||||
// to store delegates in a Map<Integer, Delegate>.
|
||||
private static final int NUM_INPUT_TYPES = MotionEvent.TOOL_TYPE_ERASER + 1;
|
||||
|
||||
// list with one null entry for each known tooltype (0-4).
|
||||
// See MotionEvent.TOOL_TYPE_ERASER for details. We're using a list here because
|
||||
// it is parameterized type friendly, and a natural container given that
|
||||
// the index values are 0-based ints.
|
||||
private final List<T> mHandlers = Arrays.asList(null, null, null, null, null);
|
||||
private final T mDefault;
|
||||
|
||||
ToolHandlerRegistry(@NonNull T defaultDelegate) {
|
||||
checkArgument(defaultDelegate != null);
|
||||
mDefault = defaultDelegate;
|
||||
|
||||
// Initialize all values to null.
|
||||
for (int i = 0; i < NUM_INPUT_TYPES; i++) {
|
||||
mHandlers.set(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
|
||||
/**
|
||||
* A class responsible for routing MotionEvents to tool-type specific handlers,
|
||||
* and if not handled by a handler, on to a {@link GestureDetector} for further
|
||||
* processing.
|
||||
*
|
||||
* <p>
|
||||
* TouchEventRouter takes its name from
|
||||
* {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
|
||||
* being in the name, it receives MotionEvents for all types of tools.
|
||||
*/
|
||||
final class TouchEventRouter implements OnItemTouchListener {
|
||||
|
||||
private static final String TAG = "TouchEventRouter";
|
||||
|
||||
private final GestureDetector mDetector;
|
||||
private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
|
||||
|
||||
TouchEventRouter(
|
||||
@NonNull GestureDetector detector, @NonNull OnItemTouchListener defaultDelegate) {
|
||||
|
||||
checkArgument(detector != null);
|
||||
checkArgument(defaultDelegate != null);
|
||||
|
||||
mDetector = detector;
|
||||
mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
|
||||
}
|
||||
|
||||
TouchEventRouter(@NonNull GestureDetector detector) {
|
||||
this(
|
||||
detector,
|
||||
// Supply a fallback listener does nothing...because the caller
|
||||
// didn't supply a fallback.
|
||||
new OnItemTouchListener() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(
|
||||
@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(
|
||||
@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param toolType See MotionEvent for details on available types.
|
||||
* @param delegate An {@link OnItemTouchListener} to receive events
|
||||
* of {@code toolType}.
|
||||
*/
|
||||
void register(int toolType, @NonNull OnItemTouchListener delegate) {
|
||||
checkArgument(delegate != null);
|
||||
mDelegates.set(toolType, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
boolean handled = mDelegates.get(e).onInterceptTouchEvent(rv, e);
|
||||
|
||||
// Forward all events to UserInputHandler.
|
||||
// This is necessary since UserInputHandler needs to always see the first DOWN event. Or
|
||||
// else all future UP events will be tossed.
|
||||
handled |= mDetector.onTouchEvent(e);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
mDelegates.get(e).onTouchEvent(rv, e);
|
||||
|
||||
// Note: even though this event is being handled as part of gestures such as drag and band,
|
||||
// continue forwarding to the GestureDetector. The detector needs to see the entire cluster
|
||||
// of events in order to properly interpret other gestures, such as long press.
|
||||
mDetector.onTouchEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
@ -36,7 +37,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
||||
|
||||
private static final String TAG = "TouchInputDelegate";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final ItemDetailsLookup<K> mDetailsLookup;
|
||||
private final SelectionPredicate<K> mSelectionPredicate;
|
||||
|
@ -75,6 +75,11 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
|||
|
||||
@Override
|
||||
public boolean onSingleTapUp(@NonNull MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
checkArgument(MotionEvents.isFingerEvent(e));
|
||||
checkArgument(MotionEvents.isActionUp(e));
|
||||
}
|
||||
|
||||
if (!mDetailsLookup.overItemWithSelectionKey(e)) {
|
||||
if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
|
||||
mSelectionTracker.clearSelection();
|
||||
|
@ -113,6 +118,11 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
|||
|
||||
@Override
|
||||
public void onLongPress(@NonNull MotionEvent e) {
|
||||
if (DEBUG) {
|
||||
checkArgument(MotionEvents.isFingerEvent(e));
|
||||
checkArgument(MotionEvents.isActionDown(e));
|
||||
}
|
||||
|
||||
if (!mDetailsLookup.overItemWithSelectionKey(e)) {
|
||||
if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item.");
|
||||
return;
|
||||
|
@ -124,8 +134,6 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
|||
return;
|
||||
}
|
||||
|
||||
boolean handled = false;
|
||||
|
||||
if (shouldExtendRange(e)) {
|
||||
extendSelectionRange(item);
|
||||
mHapticPerformer.run();
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
* </b>
|
||||
*
|
||||
* <p>
|
||||
* Example usage (with {@code Long} selection keys:
|
||||
* Example usage (with {@code Long} selection keys):
|
||||
* <pre>SelectionTracker<Long> tracker = new SelectionTracker.Builder<>(
|
||||
* "my-selection-id",
|
||||
* recyclerView,
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
diff --git a/app/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/app/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
|
||||
index 76087ea4..121fbd14 100644
|
||||
index 50cc4ceb7..910935a0a 100644
|
||||
--- a/app/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
|
||||
+++ b/app/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
|
||||
@@ -488,7 +488,7 @@ public abstract class SelectionTracker<K> {
|
||||
@@ -503,7 +503,7 @@ public abstract class SelectionTracker<K> {
|
||||
private OnContextClickListener mOnContextClickListener;
|
||||
|
||||
private BandPredicate mBandPredicate;
|
||||
- private int mBandOverlayId = R.drawable.selection_band_overlay;
|
||||
+ private int mBandOverlayId = eu.faircode.email.R.drawable.selection_band_overlay;
|
||||
|
||||
// TODO(b/144500333): Remove support for overriding gesture and pointer tooltypes.
|
||||
private int[] mGestureToolTypes = new int[]{
|
||||
MotionEvent.TOOL_TYPE_FINGER,
|
||||
@@ -766,7 +766,11 @@ public abstract class SelectionTracker<K> {
|
||||
@@ -815,7 +815,11 @@ public abstract class SelectionTracker<K> {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mSelectionPredicate.canSelectMultiple()) {
|
||||
- gestureHelper.start();
|
||||
+ try {
|
||||
+ gestureHelper.start();
|
||||
+ } catch (IllegalStateException ex) {
|
||||
+ ex.printStackTrace();
|
||||
+ } catch (Throwable ex) {
|
||||
+ eu.faircode.email.Log.e(ex);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
},
|
||||
diff --git a/app/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java b/app/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java
|
||||
index d82812cc..48db78ec 100644
|
||||
index be4c3fa0b..c434ced07 100644
|
||||
--- a/app/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java
|
||||
+++ b/app/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java
|
||||
@@ -107,6 +107,11 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
||||
@@ -112,6 +112,11 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,5 +38,5 @@ index d82812cc..48db78ec 100644
|
|||
+
|
||||
+ @Override
|
||||
public void onLongPress(@NonNull MotionEvent e) {
|
||||
if (!mDetailsLookup.overItemWithSelectionKey(e)) {
|
||||
if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item.");
|
||||
if (DEBUG) {
|
||||
checkArgument(MotionEvents.isFingerEvent(e));
|
||||
|
|
Loading…
Reference in New Issue