diff options
author | 2018-06-21 22:06:43 +0800 | |
---|---|---|
committer | 2018-08-22 00:03:13 +0800 | |
commit | 0c3759895067adbca6b7b49bbe6628639f5d2ca1 (patch) | |
tree | ef052b4b59e39d0efd560b87d9551f59c2f7de58 | |
parent | d0193072513ec0f88983407081eca541e8654b68 (diff) |
Migrate to use RecyclerView-Selection library
Test: atest DocumentsUITests
Bug: 110482929
Change-Id: Icb99520ac48d120b8bed0ce461c5e4f2ff885c18
112 files changed, 798 insertions, 9662 deletions
diff --git a/build_apk.mk b/build_apk.mk index c7955bc02..e8252f64f 100644 --- a/build_apk.mk +++ b/build_apk.mk @@ -10,7 +10,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ androidx.legacy_legacy-support-v13 \ androidx.design_design \ androidx.transition_transition \ - androidx.recyclerview_recyclerview + androidx.recyclerview_recyclerview \ + androidx.recyclerview_recyclerview-selection LOCAL_USE_AAPT2 := true diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java index 0bb327b0c..0eb308f8c 100644 --- a/src/com/android/documentsui/AbstractActionHandler.java +++ b/src/com/android/documentsui/AbstractActionHandler.java @@ -34,11 +34,15 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.provider.DocumentsContract; -import androidx.annotation.VisibleForTesting; import android.util.Log; import android.util.Pair; import android.view.DragEvent; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.SelectionTracker; + import com.android.documentsui.AbstractActionHandler.CommonAddons; import com.android.documentsui.LoadDocStackTask.LoadDocStackCallback; import com.android.documentsui.base.BooleanConsumer; @@ -57,10 +61,6 @@ import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.GetRootDocumentTask; import com.android.documentsui.roots.LoadRootTask; import com.android.documentsui.roots.ProvidersAccess; -import com.android.documentsui.selection.ContentLock; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import com.android.documentsui.sidebar.EjectRootTask; import com.android.documentsui.ui.Snackbars; @@ -93,7 +93,7 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> protected final ProvidersAccess mProviders; protected final DocumentsAccess mDocs; protected final FocusHandler mFocusHandler; - protected final SelectionHelper mSelectionMgr; + protected final SelectionTracker<String> mSelectionMgr; protected final SearchViewManager mSearchMgr; protected final Lookup<String, Executor> mExecutors; protected final Injector<?> mInjector; @@ -227,7 +227,7 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> } @Override - public boolean openItem(ItemDetails doc, @ViewType int type, @ViewType int fallback) { + public boolean openItem(ItemDetails<String> doc, @ViewType int type, @ViewType int fallback) { throw new UnsupportedOperationException("Can't open document."); } @@ -533,8 +533,8 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> loadRoot(Shared.getDefaultRootUri(mActivity)); } - protected MutableSelection getStableSelection() { - MutableSelection selection = new MutableSelection(); + protected MutableSelection<String> getStableSelection() { + MutableSelection<String> selection = new MutableSelection<>(); mSelectionMgr.copySelection(selection); return selection; } diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java index 53a746a4b..6f25f053b 100644 --- a/src/com/android/documentsui/ActionHandler.java +++ b/src/com/android/documentsui/ActionHandler.java @@ -24,12 +24,12 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.view.DragEvent; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + import com.android.documentsui.base.BooleanConsumer; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.RootInfo; -import com.android.documentsui.selection.ContentLock; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,6 +37,9 @@ import java.util.function.Consumer; import javax.annotation.Nullable; +/** + * Interface to handle action for document. + */ public interface ActionHandler { @IntDef({ @@ -110,7 +113,7 @@ public interface ActionHandler { * If container, then opens the container, otherwise views using the specified type of view. * If the primary view type is unavailable, then fallback to the alternative type of view. */ - boolean openItem(ItemDetails doc, @ViewType int type, @ViewType int fallback); + boolean openItem(ItemDetails<String> doc, @ViewType int type, @ViewType int fallback); /** * This is called when user hovers over a doc for enough time during a drag n' drop, to open a diff --git a/src/com/android/documentsui/ActionModeController.java b/src/com/android/documentsui/ActionModeController.java index 4e0baef45..8b74dbf5a 100644 --- a/src/com/android/documentsui/ActionModeController.java +++ b/src/com/android/documentsui/ActionModeController.java @@ -31,33 +31,34 @@ import android.view.View; import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.base.EventHandler; import com.android.documentsui.base.Menus; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.SelectionHelper.SelectionObserver; import com.android.documentsui.ui.MessageBuilder; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.selection.SelectionTracker.SelectionObserver; + /** * A controller that listens to selection changes and manages life cycles of action modes. */ -public class ActionModeController extends SelectionObserver +public class ActionModeController extends SelectionObserver<String> implements ActionMode.Callback, ActionModeAddons { private static final String TAG = "ActionModeController"; private final Activity mActivity; - private final SelectionHelper mSelectionMgr; + private final SelectionTracker<String> mSelectionMgr; private final MenuManager mMenuManager; private final MessageBuilder mMessages; private final ContentScope mScope = new ContentScope(); - private final Selection mSelected = new Selection(); + private final MutableSelection<String> mSelected = new MutableSelection<>(); private @Nullable ActionMode mActionMode; private @Nullable Menu mMenu; public ActionModeController( Activity activity, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, MenuManager menuManager, MessageBuilder messages) { diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index 8e4ea867e..7343fa888 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -16,8 +16,8 @@ package com.android.documentsui; -import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.Shared.EXTRA_BENCHMARK; +import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.State.MODE_GRID; import android.app.Activity; @@ -31,9 +31,6 @@ import android.os.Bundle; import android.os.MessageQueue.IdleHandler; import android.preference.PreferenceManager; import android.provider.DocumentsContract; -import androidx.annotation.CallSuper; -import androidx.annotation.LayoutRes; -import androidx.annotation.VisibleForTesting; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -41,6 +38,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.Toolbar; +import androidx.annotation.CallSuper; +import androidx.annotation.LayoutRes; +import androidx.annotation.VisibleForTesting; + import com.android.documentsui.AbstractActionHandler.CommonAddons; import com.android.documentsui.Injector.Injected; import com.android.documentsui.NavigationViewManager.Breadcrumb; @@ -60,7 +61,6 @@ import com.android.documentsui.queries.CommandInterceptor; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.queries.SearchViewManager.SearchManagerListener; import com.android.documentsui.roots.ProvidersCache; -import com.android.documentsui.selection.Selection; import com.android.documentsui.sidebar.RootsFragment; import com.android.documentsui.sorting.SortController; import com.android.documentsui.sorting.SortModel; @@ -82,7 +82,6 @@ public abstract class BaseActivity @Injected protected Injector<?> mInjector; - protected @Nullable RetainedState mRetainedState; protected ProvidersCache mProviders; protected DocumentsAccess mDocs; protected DrawerController mDrawer; @@ -133,10 +132,6 @@ public abstract class BaseActivity mDrawer = DrawerController.create(this, mInjector.config); Metrics.logActivityLaunch(this, mState, intent); - // we're really interested in retainining state in our very complex - // DirectoryFragment. So we do a little code yoga to extend - // support to that fragment. - mRetainedState = (RetainedState) getLastNonConfigurationInstance(); mProviders = DocumentsApplication.getProvidersCache(this); mDocs = DocumentsAccess.create(this); @@ -524,29 +519,6 @@ public abstract class BaseActivity } @Override - protected void onRestoreInstanceState(Bundle state) { - super.onRestoreInstanceState(state); - } - - /** - * Delegate ths call to the current fragment so it can save selection. - * Feel free to expand on this with other useful state. - */ - @Override - public RetainedState onRetainNonConfigurationInstance() { - RetainedState retained = new RetainedState(); - DirectoryFragment fragment = DirectoryFragment.get(getFragmentManager()); - if (fragment != null) { - fragment.retainState(retained); - } - return retained; - } - - public @Nullable RetainedState getRetainedState() { - return mRetainedState; - } - - @Override public boolean isSearchExpanded() { return mSearchManager.isExpanded(); } @@ -662,14 +634,6 @@ public abstract class BaseActivity }); } - public static final class RetainedState { - public @Nullable Selection selection; - - public boolean hasSelection() { - return selection != null; - } - } - @VisibleForTesting protected interface EventListener { /** diff --git a/src/com/android/documentsui/ContentLock.java b/src/com/android/documentsui/ContentLock.java new file mode 100644 index 000000000..d9f0346ca --- /dev/null +++ b/src/com/android/documentsui/ContentLock.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 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 com.android.documentsui; + +import android.annotation.Nullable; + +import androidx.annotation.GuardedBy; +import androidx.recyclerview.selection.OperationMonitor; + +/** + * ContentLock provides a mechanism to block content from reloading while selection + * activities like gesture and band selection are active. Clients using live data + * (data loaded, for example by a {@link Loader}), should route calls to load + * content through this lock using {@link ContentLock#runWhenUnlocked(Runnable)}. + */ +public final class ContentLock { + + private final OperationMonitor mMonitor = new OperationMonitor(); + + @GuardedBy("this") + private @Nullable Runnable mCallback; + + public ContentLock() { + mMonitor.addListener(() -> { + if (!isLocked()) { + synchronized (this) { + final Runnable callback = mCallback; + if (callback != null) { + callback.run(); + mCallback = null; + } + } + } + }); + } + + public OperationMonitor getMonitor() { + return mMonitor; + } + + /** + * Returns true if locked. + */ + private boolean isLocked() { + return mMonitor.isStarted(); + } + + /** + * Attempts to run the given Runnable if not-locked, or else the Runnable is set to be ran next + * (replacing any previous set Runnables). + */ + public synchronized void runWhenUnlocked(Runnable runnable) { + if (!isLocked()) { + runnable.run(); + } else { + mCallback = runnable; + } + } +} diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java index eb3ab32c3..0a749fb65 100644 --- a/src/com/android/documentsui/DirectoryLoader.java +++ b/src/com/android/documentsui/DirectoryLoader.java @@ -43,7 +43,6 @@ import com.android.documentsui.base.FilteringCursorWrapper; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.RootInfo; import com.android.documentsui.roots.RootCursorWrapper; -import com.android.documentsui.selection.ContentLock; import com.android.documentsui.sorting.SortModel; import android.os.FileUtils; diff --git a/src/com/android/documentsui/DocsSelectionHelper.java b/src/com/android/documentsui/DocsSelectionHelper.java index c788119a7..b7a720c8b 100644 --- a/src/com/android/documentsui/DocsSelectionHelper.java +++ b/src/com/android/documentsui/DocsSelectionHelper.java @@ -16,50 +16,42 @@ package com.android.documentsui; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; +import android.os.Bundle; +import android.view.MotionEvent; -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.DefaultSelectionHelper.SelectionMode; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.selection.ItemDetailsLookup; +import androidx.recyclerview.selection.ItemKeyProvider; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import java.util.Set; -import javax.annotation.Nullable; - /** * DocumentsUI SelectManager implementation that creates delegate instances * each time reset is called. */ -public final class DocsSelectionHelper extends SelectionHelper { +public final class DocsSelectionHelper extends SelectionTracker<String> { private final DelegateFactory mFactory; - private final @SelectionMode int mSelectionMode; // initialize to a dummy object incase we get some input // event drive calls before we're properly initialized. // See: b/69306667. - private SelectionHelper mDelegate = new DummySelectionHelper(); + private SelectionTracker<String> mDelegate = new DummySelectionTracker<>(); @VisibleForTesting - DocsSelectionHelper(DelegateFactory factory, @SelectionMode int mode) { + DocsSelectionHelper(DelegateFactory factory) { mFactory = factory; - mSelectionMode = mode; } - public SelectionHelper reset( - RecyclerView.Adapter<?> adapter, - StableIdProvider stableIds, - SelectionPredicate canSetState) { - + public void reset(SelectionTracker<String> selectionTracker) { if (mDelegate != null) { mDelegate.clearSelection(); } - - mDelegate = mFactory.create(mSelectionMode, adapter, stableIds, canSetState); - return this; + mDelegate = mFactory.create(selectionTracker); } @Override @@ -73,12 +65,12 @@ public final class DocsSelectionHelper extends SelectionHelper { } @Override - public Selection getSelection() { + public Selection<String> getSelection() { return mDelegate.getSelection(); } @Override - public void copySelection(Selection dest) { + public void copySelection(MutableSelection<String> dest) { mDelegate.copySelection(dest); } @@ -94,18 +86,13 @@ public final class DocsSelectionHelper extends SelectionHelper { } @Override - public void restoreSelection(Selection other) { - mDelegate.restoreSelection(other); - } - - @Override public boolean setItemsSelected(Iterable<String> ids, boolean selected) { return mDelegate.setItemsSelected(ids, selected); } @Override - public void clearSelection() { - mDelegate.clearSelection(); + public boolean clearSelection() { + return mDelegate.clearSelection(); } @Override @@ -129,50 +116,59 @@ public final class DocsSelectionHelper extends SelectionHelper { } @Override - public void extendProvisionalRange(int pos) { - mDelegate.extendProvisionalRange(pos); + public void endRange() { + mDelegate.endRange(); } @Override - public void clearProvisionalSelection() { - mDelegate.clearProvisionalSelection(); + public boolean isRangeActive() { + return mDelegate.isRangeActive(); } @Override - public void setProvisionalSelection(Set<String> newSelection) { - mDelegate.setProvisionalSelection(newSelection); + public void anchorRange(int position) { + mDelegate.anchorRange(position); } @Override - public void mergeProvisionalSelection() { - mDelegate.mergeProvisionalSelection(); + public void onSaveInstanceState(Bundle state) { + mDelegate.onSaveInstanceState(state); } @Override - public void endRange() { - mDelegate.endRange(); + public void onRestoreInstanceState(Bundle state) { + mDelegate.onRestoreInstanceState(state); } + // Below overridden protected methods are not used for delegation. These empty implementations + // are just required by abstract declaration of parent class. @Override - public boolean isRangeActive() { - return mDelegate.isRangeActive(); + protected void restoreSelection(Selection<String> selection) { } @Override - public void anchorRange(int position) { - mDelegate.anchorRange(position); + protected AdapterDataObserver getAdapterDataObserver() { + return null; + } + + @Override + protected void extendProvisionalRange(int position) { } - public static DocsSelectionHelper createMultiSelect() { - return new DocsSelectionHelper( - DelegateFactory.INSTANCE, - DefaultSelectionHelper.MODE_MULTIPLE); + @Override + protected void setProvisionalSelection(Set<String> newSelection) { } - public static DocsSelectionHelper createSingleSelect() { - return new DocsSelectionHelper( - DelegateFactory.INSTANCE, - DefaultSelectionHelper.MODE_SINGLE); + @Override + protected void clearProvisionalSelection() { + } + + @Override + protected void mergeProvisionalSelection() { + } + + public static DocsSelectionHelper create() { + return new DocsSelectionHelper(DelegateFactory.INSTANCE); } /** @@ -183,107 +179,30 @@ public final class DocsSelectionHelper extends SelectionHelper { static class DelegateFactory { static final DelegateFactory INSTANCE = new DelegateFactory(); - SelectionHelper create( - @SelectionMode int mode, - RecyclerView.Adapter<?> adapter, - StableIdProvider stableIds, - SelectionPredicate canSetState) { - - return new DefaultSelectionHelper(mode, adapter, stableIds, canSetState); + SelectionTracker<String> create(SelectionTracker<String> selectionTracker) { + return selectionTracker; } } /** - * A dummy SelectHelper used by DocsSelectionHelper before a real - * SelectionHelper has been initialized by DirectoryFragment. + * Facilitates the use of ItemDetailsLookup. */ - private static final class DummySelectionHelper extends SelectionHelper { - - @Override - public void addObserver(SelectionObserver listener) { - } - - @Override - public boolean hasSelection() { - return false; - } - - @Override - public Selection getSelection() { - return new MutableSelection(); - } - - @Override - public void copySelection(Selection dest) { - } + public static abstract class DocDetailsLookup extends ItemDetailsLookup<String> { + // Override as public for usages in other packages. @Override - public boolean isSelected(String id) { - return false; - } - - @VisibleForTesting - public void replaceSelection(Iterable<String> ids) { - } - - @Override - public void restoreSelection(Selection other) { - } - - @Override - public boolean setItemsSelected(Iterable<String> ids, boolean selected) { - return false; - } - - @Override - public void clearSelection() { - } - - @Override - public boolean select(String modelId) { - return false; - } - - @Override - public boolean deselect(String modelId) { - return false; - } - - @Override - public void startRange(int pos) { - } - - @Override - public void extendRange(int pos) { - } - - @Override - public void extendProvisionalRange(int pos) { - } - - @Override - public void clearProvisionalSelection() { - } - - @Override - public void setProvisionalSelection(Set<String> newSelection) { - } - - @Override - public void mergeProvisionalSelection() { - } - - @Override - public void endRange() { + public boolean overItemWithSelectionKey(MotionEvent e) { + return super.overItemWithSelectionKey(e); } + } - @Override - public boolean isRangeActive() { - return false; - } + /** + * Facilitates the use of stable ids. + */ + public static abstract class StableIdProvider extends ItemKeyProvider<String> { - @Override - public void anchorRange(int position) { + protected StableIdProvider() { + super(ItemKeyProvider.SCOPE_MAPPED); } } } diff --git a/src/com/android/documentsui/DragHoverListener.java b/src/com/android/documentsui/DragHoverListener.java index d26f7caa9..9994de5f3 100644 --- a/src/com/android/documentsui/DragHoverListener.java +++ b/src/com/android/documentsui/DragHoverListener.java @@ -24,9 +24,8 @@ import android.view.View.OnDragListener; import android.widget.AbsListView; import com.android.documentsui.ItemDragListener.DragHost; -import com.android.documentsui.selection.ViewAutoScroller; -import com.android.documentsui.selection.ViewAutoScroller.ScrollHost; -import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks; +import com.android.documentsui.ViewAutoScroller.ScrollHost; +import com.android.documentsui.ViewAutoScroller.ScrollerCallbacks; import java.util.function.BooleanSupplier; import java.util.function.IntConsumer; diff --git a/src/com/android/documentsui/DummySelectionTracker.java b/src/com/android/documentsui/DummySelectionTracker.java new file mode 100644 index 000000000..49b9ad918 --- /dev/null +++ b/src/com/android/documentsui/DummySelectionTracker.java @@ -0,0 +1,131 @@ +/* + * Copyright 2018 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 com.android.documentsui; + +import android.os.Bundle; + +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; + +import java.util.Set; + +/** + * A dummy SelectionTracker used by DocsSelectionHelper before a real SelectionTracker has been + * initialized by DirectoryFragment. + */ +public class DummySelectionTracker<K> extends SelectionTracker<K> { + + @Override + public void addObserver(SelectionObserver observer) { + } + + @Override + public boolean hasSelection() { + return false; + } + + @Override + public Selection<K> getSelection() { + return new MutableSelection<K>(); + } + + @Override + public void copySelection(MutableSelection<K> dest) { + } + + @Override + public boolean isSelected(K key) { + return false; + } + + @Override + public void restoreSelection(Selection<K> selection) { + } + + @Override + public boolean clearSelection() { + return false; + } + + @Override + public boolean setItemsSelected(Iterable<K> keys, boolean selected) { + return false; + } + + @Override + public boolean select(K key) { + return false; + } + + @Override + public boolean deselect(K key) { + return false; + } + + @Override + protected AdapterDataObserver getAdapterDataObserver() { + return null; + } + + @Override + public void startRange(int position) { + } + + @Override + public void extendRange(int position) { + } + + @Override + public void endRange() { + } + + @Override + public boolean isRangeActive() { + return false; + } + + @Override + public void anchorRange(int position) { + } + + @Override + public void extendProvisionalRange(int position) { + } + + @Override + public void setProvisionalSelection(Set<K> newSelection) { + } + + @Override + public void clearProvisionalSelection() { + } + + @Override + public void mergeProvisionalSelection() { + } + + @Override + public void onSaveInstanceState(Bundle state) { + } + + @Override + public void onRestoreInstanceState(Bundle state) { + } + +} diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java index 5fe732684..7a228ffe3 100644 --- a/src/com/android/documentsui/FocusManager.java +++ b/src/com/android/documentsui/FocusManager.java @@ -27,8 +27,6 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.provider.DocumentsContract.Document; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.text.Editable; import android.text.Spannable; import android.text.method.KeyListener; @@ -40,6 +38,13 @@ import android.view.KeyEvent; import android.view.View; import android.widget.TextView; +import androidx.recyclerview.selection.FocusDelegate; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.documentsui.Model.Update; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Events; import com.android.documentsui.base.Features; @@ -47,21 +52,22 @@ import com.android.documentsui.base.Procedure; import com.android.documentsui.dirlist.DocumentHolder; import com.android.documentsui.dirlist.DocumentsAdapter; import com.android.documentsui.dirlist.FocusHandler; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.Model.Update; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; -public final class FocusManager implements FocusHandler { +/** + * The implementation to handle focus and keyboard driven navigation. + */ +public final class FocusManager extends FocusDelegate<String> implements FocusHandler { private static final String TAG = "FocusManager"; private final ContentScope mScope = new ContentScope(); private final Features mFeatures; - private final SelectionHelper mSelectionMgr; + private final SelectionTracker<String> mSelectionMgr; private final DrawerController mDrawer; private final Procedure mRootsFocuser; private final TitleSearchHelper mSearchHelper; @@ -70,7 +76,7 @@ public final class FocusManager implements FocusHandler { public FocusManager( Features features, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, DrawerController drawer, Procedure rootsFocuser, @ColorRes int color) { @@ -205,7 +211,12 @@ public final class FocusManager implements FocusHandler { } @Override - public int getFocusPosition() { + public void focusItem(ItemDetails<String> item) { + focusDocument(item.getSelectionKey()); + } + + @Override + public int getFocusedPosition() { return mScope.lastFocusPosition; } diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java index ea4dd90a7..0fc3670f0 100644 --- a/src/com/android/documentsui/Injector.java +++ b/src/com/android/documentsui/Injector.java @@ -18,9 +18,11 @@ package com.android.documentsui; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.view.MenuItem; + import androidx.annotation.Nullable; +import androidx.recyclerview.selection.SelectionTracker; import androidx.recyclerview.widget.RecyclerView; -import android.view.MenuItem; import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.base.DebugHelper; @@ -28,12 +30,8 @@ import com.android.documentsui.base.EventHandler; import com.android.documentsui.base.Features; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.RootInfo; -import com.android.documentsui.dirlist.DocsStableIdProvider; -import com.android.documentsui.dirlist.DocumentsAdapter; import com.android.documentsui.prefs.ScopedPreferences; import com.android.documentsui.queries.SearchViewManager; -import com.android.documentsui.selection.ContentLock; -import com.android.documentsui.selection.SelectionHelper; import com.android.documentsui.ui.DialogController; import com.android.documentsui.ui.MessageBuilder; import androidx.annotation.VisibleForTesting; @@ -120,9 +118,8 @@ public class Injector<T extends ActionHandler> { return focusManager.reset(view, model); } - public SelectionHelper getSelectionManager( - DocumentsAdapter adapter, SelectionHelper.SelectionPredicate canSetState) { - return selectionMgr.reset(adapter, new DocsStableIdProvider(adapter), canSetState); + public void updateSharedSelectionTracker(SelectionTracker<String> selectionTracker) { + selectionMgr.reset(selectionTracker); } public final ActionModeController getActionModeController( diff --git a/src/com/android/documentsui/Model.java b/src/com/android/documentsui/Model.java index f02c324a1..7141ab90e 100644 --- a/src/com/android/documentsui/Model.java +++ b/src/com/android/documentsui/Model.java @@ -28,16 +28,17 @@ import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; +import android.util.Log; + import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import android.util.Log; +import androidx.recyclerview.selection.Selection; import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Features; import com.android.documentsui.roots.RootCursorWrapper; -import com.android.documentsui.selection.Selection; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -73,7 +74,7 @@ public class Model { private @Nullable Cursor mCursor; private int mCursorCount; private String mIds[] = new String[0]; - private Set<Selection> mDocumentsToBeDeleted = new HashSet<>(); + private Set<Selection<String>> mDocumentsToBeDeleted = new HashSet<>(); public Model(Features features) { mFeatures = features; @@ -142,7 +143,7 @@ public class Model { notifyUpdateListeners(); } - public void markDocumentsToBeDeleted(Selection selection) { + public void markDocumentsToBeDeleted(Selection<String> selection) { if (mDocumentsToBeDeleted.contains(selection)) { return; } @@ -151,7 +152,7 @@ public class Model { notifyUpdateListeners(); } - public void restoreDocumentsToBeDeleted(Selection selection) { + public void restoreDocumentsToBeDeleted(Selection<String> selection) { if (!mDocumentsToBeDeleted.contains(selection)) { return; } @@ -161,7 +162,7 @@ public class Model { } private boolean isDocumentToBeDeleted(String id) { - for (Selection s : mDocumentsToBeDeleted) { + for (Selection<String> s : mDocumentsToBeDeleted) { if (s.contains(id)) { return true; } @@ -170,8 +171,8 @@ public class Model { } private void updateDocumentsToBeDeleted() { - for (Iterator<Selection> i = mDocumentsToBeDeleted.iterator(); i.hasNext();) { - Selection selection = i.next(); + for (Iterator<Selection<String>> i = mDocumentsToBeDeleted.iterator(); i.hasNext();) { + Selection<String> selection = i.next(); for (String id : selection) { if (!mPositions.containsKey(id)) { i.remove(); @@ -183,7 +184,7 @@ public class Model { private int getDocumentsToBeDeletedCount() { int count = 0; - for (Selection s : mDocumentsToBeDeleted) { + for (Selection<String> s : mDocumentsToBeDeleted) { count += s.size(); } return count; @@ -258,7 +259,7 @@ public class Model { return mIsLoading; } - public List<DocumentInfo> getDocuments(Selection selection) { + public List<DocumentInfo> getDocuments(Selection<String> selection) { return loadDocuments(selection, DocumentFilters.ANY); } @@ -269,7 +270,7 @@ public class Model { : DocumentInfo.fromDirectoryCursor(cursor); } - public List<DocumentInfo> loadDocuments(Selection selection, Predicate<Cursor> filter) { + public List<DocumentInfo> loadDocuments(Selection<String> selection, Predicate<Cursor> filter) { final int size = (selection != null) ? selection.size() : 0; final List<DocumentInfo> docs = new ArrayList<>(size); @@ -283,7 +284,7 @@ public class Model { return docs; } - public boolean hasDocuments(Selection selection, Predicate<Cursor> filter) { + public boolean hasDocuments(Selection<String> selection, Predicate<Cursor> filter) { for (String modelId: selection) { if (loadDocument(modelId, filter) != null) { return true; diff --git a/src/com/android/documentsui/SharedInputHandler.java b/src/com/android/documentsui/SharedInputHandler.java index 8d39204cd..b9a5b92c3 100644 --- a/src/com/android/documentsui/SharedInputHandler.java +++ b/src/com/android/documentsui/SharedInputHandler.java @@ -20,12 +20,16 @@ import static com.android.documentsui.base.SharedMinimal.DEBUG; import android.util.Log; import android.view.KeyEvent; +import androidx.recyclerview.selection.SelectionTracker; + import com.android.documentsui.base.Events; import com.android.documentsui.base.Features; import com.android.documentsui.base.Procedure; import com.android.documentsui.dirlist.FocusHandler; -import com.android.documentsui.selection.SelectionHelper; +/** + * Handle common input events. + */ public class SharedInputHandler { private static final String TAG = "SharedInputHandler"; @@ -34,12 +38,12 @@ public class SharedInputHandler { private final Procedure mSearchCanceler; private final Procedure mDirPopper; private final Features mFeatures; - private final SelectionHelper mSelectionMgr; + private final SelectionTracker<String> mSelectionMgr; private final DrawerController mDrawer; public SharedInputHandler( FocusHandler focusHandler, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, Procedure searchCanceler, Procedure dirPopper, Features features, diff --git a/src/com/android/documentsui/selection/ViewAutoScroller.java b/src/com/android/documentsui/ViewAutoScroller.java index b77176c01..f7b062e18 100644 --- a/src/com/android/documentsui/selection/ViewAutoScroller.java +++ b/src/com/android/documentsui/ViewAutoScroller.java @@ -14,16 +14,15 @@ * limitations under the License. */ -package com.android.documentsui.selection; +package com.android.documentsui; import android.graphics.Point; /** * Provides auto-scrolling upon request when user's interaction with the application - * introduces a natural intent to scroll. Used by BandController, GestureSelector, - * and DragHoverListener to allow auto scrolling when user either does band selection, - * attempting to drag and drop files to somewhere off the current screen, or trying to motion select - * past top/bottom of the screen. + * introduces a natural intent to scroll. Used by DragHoverListener to allow auto scrolling + * when user either does band selection, attempting to drag and drop files to somewhere off + * the current screen, or trying to motion select past top/bottom of the screen. */ public final class ViewAutoScroller implements Runnable { diff --git a/src/com/android/documentsui/base/Events.java b/src/com/android/documentsui/base/Events.java index c38f1a4c6..1e6be6c3c 100644 --- a/src/com/android/documentsui/base/Events.java +++ b/src/com/android/documentsui/base/Events.java @@ -16,7 +16,6 @@ package com.android.documentsui.base; -import android.graphics.Point; import android.view.KeyEvent; import android.view.MotionEvent; @@ -29,10 +28,6 @@ public final class Events { return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE; } - public static boolean isActionMove(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_MOVE; - } - public static boolean isActionDown(MotionEvent e) { return e.getActionMasked() == MotionEvent.ACTION_DOWN; } @@ -49,22 +44,6 @@ public final class Events { return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP; } - public static boolean isActionCancel(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_CANCEL; - } - - public static boolean isPrimaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_PRIMARY); - } - - public static boolean isSecondaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_SECONDARY); - } - - public static boolean isTertiaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_TERTIARY); - } - public static boolean isCtrlKeyPressed(MotionEvent e) { return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON); } @@ -77,20 +56,10 @@ public final class Events { return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON); } - public static boolean isTouchpadScroll(MotionEvent e) { - // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons - // returned. - return isMouseEvent(e) && isActionMove(e) && e.getButtonState() == 0; - } - private static boolean hasBit(int metaState, int bit) { return (metaState & bit) != 0; } - public static Point getOrigin(MotionEvent e) { - return new Point((int) e.getX(), (int) e.getY()); - } - /** * @return true if keyCode is a known navigation code (e.g. up, down, home). */ @@ -109,15 +78,4 @@ public final class Events { return false; } } - - /** - * Returns true if the event is a mouse drag event. - * @param e - * @return - */ - public static boolean isMouseDragEvent(MotionEvent e) { - return isMouseEvent(e) - && isActionMove(e) - && isPrimaryButtonPressed(e); - } } diff --git a/src/com/android/documentsui/clipping/DocumentClipper.java b/src/com/android/documentsui/clipping/DocumentClipper.java index dbb84fca3..fe85e27e3 100644 --- a/src/com/android/documentsui/clipping/DocumentClipper.java +++ b/src/com/android/documentsui/clipping/DocumentClipper.java @@ -20,12 +20,11 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.net.Uri; -import androidx.annotation.Nullable; + +import androidx.recyclerview.selection.Selection; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; -import com.android.documentsui.base.RootInfo; -import com.android.documentsui.selection.Selection; import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations; @@ -47,7 +46,7 @@ public interface DocumentClipper { * Returns {@link ClipData} representing the selection, or null if selection is empty, * or cannot be converted. */ - ClipData getClipDataForDocuments(Function<String, Uri> uriBuilder, Selection selection, + ClipData getClipDataForDocuments(Function<String, Uri> uriBuilder, Selection<String> selection, @OpType int opType); /** @@ -63,13 +62,13 @@ public interface DocumentClipper { /** * Puts {@code ClipData} in a primary clipboard, describing a copy operation */ - void clipDocumentsForCopy(Function<String, Uri> uriBuilder, Selection selection); + void clipDocumentsForCopy(Function<String, Uri> uriBuilder, Selection<String> selection); /** * Puts {@Code ClipData} in a primary clipboard, describing a cut operation */ void clipDocumentsForCut( - Function<String, Uri> uriBuilder, Selection selection, DocumentInfo parent); + Function<String, Uri> uriBuilder, Selection<String> selection, DocumentInfo parent); /** * Copies documents from clipboard. It's the same as {@link #copyFromClipData} with clipData diff --git a/src/com/android/documentsui/clipping/RuntimeDocumentClipper.java b/src/com/android/documentsui/clipping/RuntimeDocumentClipper.java index 64311f349..1c5804c57 100644 --- a/src/com/android/documentsui/clipping/RuntimeDocumentClipper.java +++ b/src/com/android/documentsui/clipping/RuntimeDocumentClipper.java @@ -24,15 +24,15 @@ import android.content.Context; import android.net.Uri; import android.os.PersistableBundle; import android.provider.DocumentsContract; -import androidx.annotation.Nullable; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.recyclerview.selection.Selection; + import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Features; -import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.Shared; -import com.android.documentsui.selection.Selection; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; @@ -90,7 +90,7 @@ final class RuntimeDocumentClipper implements DocumentClipper { @Override public ClipData getClipDataForDocuments( - Function<String, Uri> uriBuilder, Selection selection, @OpType int opType) { + Function<String, Uri> uriBuilder, Selection<String> selection, @OpType int opType) { assert(selection != null); @@ -190,7 +190,8 @@ final class RuntimeDocumentClipper implements DocumentClipper { } @Override - public void clipDocumentsForCopy(Function<String, Uri> uriBuilder, Selection selection) { + public void clipDocumentsForCopy( + Function<String, Uri> uriBuilder, Selection<String> selection) { ClipData data = getClipDataForDocuments(uriBuilder, selection, FileOperationService.OPERATION_COPY); assert(data != null); @@ -200,7 +201,7 @@ final class RuntimeDocumentClipper implements DocumentClipper { @Override public void clipDocumentsForCut( - Function<String, Uri> uriBuilder, Selection selection, DocumentInfo parent) { + Function<String, Uri> uriBuilder, Selection<String> selection, DocumentInfo parent) { assert(!selection.isEmpty()); assert(parent.derivedUri != null); diff --git a/src/com/android/documentsui/clipping/UrisSupplier.java b/src/com/android/documentsui/clipping/UrisSupplier.java index fd4d0e653..6679347d6 100644 --- a/src/com/android/documentsui/clipping/UrisSupplier.java +++ b/src/com/android/documentsui/clipping/UrisSupplier.java @@ -25,12 +25,13 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import androidx.annotation.VisibleForTesting; import android.util.Log; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.selection.Selection; + import com.android.documentsui.DocumentsApplication; import com.android.documentsui.base.Shared; -import com.android.documentsui.selection.Selection; import com.android.documentsui.services.FileOperation; import java.io.File; @@ -82,7 +83,7 @@ public abstract class UrisSupplier implements Parcelable { } public static UrisSupplier create( - Selection selection, Function<String, Uri> uriBuilder, ClipStore storage) + Selection<String> selection, Function<String, Uri> uriBuilder, ClipStore storage) throws IOException { List<Uri> uris = new ArrayList<>(selection.size()); diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index 2cd150c37..e3b429b09 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -34,23 +34,14 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; -import androidx.annotation.Nullable; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.RecyclerListener; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.Log; import android.util.SparseArray; import android.view.ContextMenu; -import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; @@ -59,10 +50,25 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import androidx.annotation.Nullable; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.selection.StorageStrategy; +import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.RecyclerListener; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.android.documentsui.ActionHandler; import com.android.documentsui.ActionModeController; import com.android.documentsui.BaseActivity; -import com.android.documentsui.BaseActivity.RetainedState; +import com.android.documentsui.ContentLock; +import com.android.documentsui.DocsSelectionHelper.DocDetailsLookup; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DragHoverListener; import com.android.documentsui.FocusManager; @@ -87,20 +93,6 @@ import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.clipping.UrisSupplier; import com.android.documentsui.dirlist.AnimationView.AnimationType; import com.android.documentsui.picker.PickActivity; -import com.android.documentsui.selection.BandSelectionHelper; -import com.android.documentsui.selection.ContentLock; -import com.android.documentsui.selection.DefaultBandHost; -import com.android.documentsui.selection.DefaultBandPredicate; -import com.android.documentsui.selection.GestureRouter; -import com.android.documentsui.selection.GestureSelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.MotionInputHandler; -import com.android.documentsui.selection.MouseInputHandler; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.TouchEventRouter; -import com.android.documentsui.selection.TouchInputHandler; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; @@ -129,7 +121,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On public static final int REQUEST_COPY_DESTINATION = 1; static final String TAG = "DirectoryFragment"; - private static final int LOADER_ID = 42; private static final int CACHE_EVICT_LIMIT = 100; private static final int REFRESH_SPINNER_TIMEOUT = 500; @@ -147,7 +138,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On @Injected @ContentScoped - private SelectionHelper mSelectionMgr; + private SelectionTracker<String> mSelectionMgr; @Injected @ContentScoped @@ -161,10 +152,9 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On @ContentScoped private ActionModeController mActionModeController; - private ItemDetailsLookup mDetailsLookup; + private DocDetailsLookup mDetailsLookup; private SelectionMetadata mSelectionMetadata; private KeyInputHandler mKeyListener; - private @Nullable BandSelectionHelper mBandSelector; private @Nullable DragHoverListener mDragHoverListener; private IconHelper mIconHelper; private SwipeRefreshLayout mRefreshLayout; @@ -181,14 +171,12 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On private DirectoryState mLocalState; - // Blocks loading/reloading of content while user is actively making selection. - private ContentLock mContentLock = new ContentLock(); - - private Runnable mBandSelectStartedCallback; - // Note, we use !null to indicate that selection was restored (from rotation). // So don't fiddle with this field unless you've got the bigger picture in mind. - private @Nullable Selection mRestoredSelection = null; + private @Nullable Bundle mRestoredState; + + // Blocks loading/reloading of content while user is actively making selection. + private ContentLock mContentLock = new ContentLock(); private SortModel.UpdateListener mSortListener = (model, updateType) -> { // Only when sort order has changed do we need to trigger another loading. @@ -223,6 +211,9 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mRecView.setItemAnimator(new DirectoryItemAnimator(mActivity)); mInjector = mActivity.getInjector(); + // Initially, this selection tracker (delegator) uses a dummy implementation, so it must be + // updated (reset) when necessary things are ready. + mSelectionMgr = mInjector.selectionMgr; mModel = mInjector.getModel(); mModel.reset(); @@ -234,7 +225,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On new DragHost<>( mActivity, DocumentsApplication.getDragAndDropManager(mActivity), - mInjector.selectionMgr, + mSelectionMgr, mInjector.actions, mActivity.getDisplayState(), mInjector.dialogs, @@ -267,10 +258,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mModel.removeUpdateListener(mModelUpdateListener); mModel.removeUpdateListener(mAdapter.getModelUpdateListener()); - if (mBandSelector != null) { - mBandSelector.removeOnBandStartedListener(mBandSelectStartedCallback); - } - super.onDestroyView(); } @@ -283,17 +270,12 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On // Read arguments when object created for the first time. // Restore state if fragment recreated. Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; + mRestoredState = args; mLocalState = new DirectoryState(); mLocalState.restore(args); - - // Restore any selection we may have squirreled away in retained state. - @Nullable RetainedState retained = mActivity.getRetainedState(); - if (retained != null && retained.hasSelection()) { - // We claim the selection for ourselves and null it out once used - // so we don't have a rando selection hanging around in RetainedState. - mRestoredSelection = retained.selection; - retained.selection = null; + if (mLocalState.mSelectionId == null) { + mLocalState.mSelectionId = Integer.toHexString(System.identityHashCode(mRecView)); } mIconHelper = new IconHelper(mActivity, MODE_GRID); @@ -322,10 +304,9 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mModel.addUpdateListener(mAdapter.getModelUpdateListener()); mModel.addUpdateListener(mModelUpdateListener); - SelectionPredicate selectionPredicate = + SelectionPredicate<String> selectionPredicate = new DocsSelectionPredicate(mInjector.config, mState, mModel, mRecView); - mSelectionMgr = mInjector.getSelectionManager(mAdapter, selectionPredicate); mFocusManager = mInjector.getFocusManager(mRecView, mModel); mActions = mInjector.getActionHandler(mContentLock); @@ -333,26 +314,8 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On new AccessibilityEventRouter(mRecView, (View child) -> onAccessibilityClick(child))); mSelectionMetadata = new SelectionMetadata(mModel::getItem); - mSelectionMgr.addObserver(mSelectionMetadata); mDetailsLookup = new DocsItemDetailsLookup(mRecView); - GestureSelectionHelper gestureHelper = GestureSelectionHelper.create( - mSelectionMgr, mRecView, mContentLock, mDetailsLookup); - - if (mState.allowMultiple) { - mBandSelector = new BandSelectionHelper( - new DefaultBandHost(mRecView, R.drawable.band_select_overlay), - mAdapter, - new DocsStableIdProvider(mAdapter), - mSelectionMgr, - selectionPredicate, - new DefaultBandPredicate(mDetailsLookup), - mContentLock); - - mBandSelectStartedCallback = mFocusManager::clearFocus; - mBandSelector.addOnBandStartedListener(mBandSelectStartedCallback); - } - DragStartListener dragStartListener = mInjector.config.dragAndDropEnabled() ? DragStartListener.create( mIconHelper, @@ -360,12 +323,33 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mSelectionMgr, mSelectionMetadata, mState, - mDetailsLookup, this::getModelId, mRecView::findChildViewUnder, DocumentsApplication.getDragAndDropManager(mActivity)) : DragStartListener.DUMMY; + { + // Limiting the scope of the localTracker so nobody uses it. + // This block initializes/updates the global SelectionTracker held in mSelectionMgr. + SelectionTracker<String> localTracker = new SelectionTracker.Builder<>( + mLocalState.mSelectionId, + mRecView, + new DocsStableIdProvider(mAdapter), + mDetailsLookup, + StorageStrategy.createStringStorage()) + .withBandOverlay(R.drawable.band_select_overlay) + .withFocusDelegate(mFocusManager) + .withOnDragInitiatedListener(dragStartListener::onDragEvent) + .withOnContextClickListener(this::onContextMenuClick) + .withOnItemActivatedListener(this::onItemActivated) + .withOperationMonitor(mContentLock.getMonitor()) + .withSelectionPredicate(selectionPredicate) + .build(); + mInjector.updateSharedSelectionTracker(localTracker); + } + + mSelectionMgr.addObserver(mSelectionMetadata); + // Construction of the input handlers is non trivial, so to keep logic clear, // and code flexible, and DirectoryFragment small, the construction has been // moved off into a separate class. @@ -373,19 +357,8 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mActions, mSelectionMgr, selectionPredicate, - mDetailsLookup, mFocusManager, - mRecView, - mState); - - MouseInputHandler mouseHandler = - handlers.createMouseHandler(this::onContextMenuClick); - - TouchInputHandler touchHandler = - handlers.createTouchHandler(gestureHelper, dragStartListener); - - GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>(touchHandler); - gestureRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mouseHandler); + mRecView); // This little guy gets added to each Holder, so that we can be notified of key events // on RecyclerView items. @@ -399,17 +372,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On new RefreshHelper(mRefreshLayout::setEnabled) .attach(mRecView); - GestureDetector gestureDetector = new GestureDetector(getContext(), gestureRouter); - - TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector, gestureHelper); - - eventRouter.register( - MotionEvent.TOOL_TYPE_MOUSE, - new MouseDragEventInterceptor( - mDetailsLookup, dragStartListener::onMouseDragEvent, mBandSelector)); - - mRecView.addOnItemTouchListener(eventRouter); - mActionModeController = mInjector.getActionModeController( mSelectionMetadata, this::handleMenuItemClick); @@ -455,16 +417,12 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mState.dirConfigs.put(mLocalState.getConfigKey(), container); } - public void retainState(RetainedState state) { - state.selection = new Selection(); - mSelectionMgr.copySelection(state.selection); - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mLocalState.save(outState); + mSelectionMgr.onSaveInstanceState(outState); } @Override @@ -515,7 +473,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On // TODO: Move to UserInputHander. protected boolean onContextMenuClick(MotionEvent e) { - if (mDetailsLookup.overStableItem(e)) { + if (mDetailsLookup.overItemWithSelectionKey(e)) { View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); ViewHolder holder = mRecView.getChildViewHolder(childView); @@ -530,6 +488,13 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On return true; } + private boolean onItemActivated(ItemDetails<String> item, MotionEvent e) { + return mActions.openItem( + item, + ActionHandler.VIEW_TYPE_PREVIEW, + ActionHandler.VIEW_TYPE_REGULAR); + } + public void onViewModeChanged() { // Mode change is just visual change; no need to kick loader. onDisplayStateChanged(); @@ -554,9 +519,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On int pad = getDirectoryPadding(mode); mRecView.setPadding(pad, pad, pad, pad); mRecView.requestLayout(); - if (mBandSelector != null) { - mBandSelector.reset(); - } mIconHelper.setViewMode(mode); } @@ -635,7 +597,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On } private boolean handleMenuItemClick(MenuItem item) { - Selection selection = new Selection(); + MutableSelection<String> selection = new MutableSelection<>(); mSelectionMgr.copySelection(selection); switch (item.getItemId()) { @@ -780,7 +742,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On } } - private void showChooserForDoc(final Selection selected) { + private void showChooserForDoc(final Selection<String> selected) { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN); assert selected.size() == 1; @@ -789,7 +751,8 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mActions.showChooserForDoc(doc); } - private void transferDocuments(final Selection selected, @Nullable DocumentStack destination, + private void transferDocuments( + final Selection<String> selected, @Nullable DocumentStack destination, final @OpType int mode) { switch (mode) { case FileOperationService.OPERATION_COPY: @@ -1025,7 +988,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On final Bundle args = new Bundle(); args.putParcelable(Shared.EXTRA_ROOT, root); args.putParcelable(Shared.EXTRA_DOC, doc); - args.putParcelable(Shared.EXTRA_SELECTION, new Selection()); final FragmentTransaction ft = fm.beginTransaction(); AnimationView.setupAnimations(ft, anim, args); @@ -1084,9 +1046,9 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mAdapter.notifyDataSetChanged(); - if (mRestoredSelection != null) { - mSelectionMgr.restoreSelection(mRestoredSelection); - mRestoredSelection = null; + if (mRestoredState != null) { + mSelectionMgr.onRestoreInstanceState(mRestoredState); + mRestoredState = null; } // Restore any previous instance state diff --git a/src/com/android/documentsui/dirlist/DirectoryState.java b/src/com/android/documentsui/dirlist/DirectoryState.java index b27a4b8f4..30d570c2f 100644 --- a/src/com/android/documentsui/dirlist/DirectoryState.java +++ b/src/com/android/documentsui/dirlist/DirectoryState.java @@ -22,8 +22,8 @@ import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.Shared; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperationService; -import com.android.documentsui.sorting.SortModel; import com.android.documentsui.sorting.SortDimension.SortDirection; +import com.android.documentsui.sorting.SortModel; import javax.annotation.Nullable; @@ -31,6 +31,7 @@ final class DirectoryState { private static final String EXTRA_SORT_DIMENSION_ID = "sortDimensionId"; private static final String EXTRA_SORT_DIRECTION = "sortDirection"; + private static final String EXTRA_SELECTION_ID = "selectionId"; // Null when viewing Recents directory. @Nullable DocumentInfo mDocument; @@ -40,6 +41,10 @@ final class DirectoryState { int mLastSortDimensionId = SortModel.SORT_DIMENSION_ID_UNKNOWN; @SortDirection int mLastSortDirection; + // The unique id to identify the selection. It is null when the corresponding + // container (fragment/activity) is the first launch. + @Nullable String mSelectionId; + private RootInfo mRoot; private String mConfigKey; @@ -49,6 +54,7 @@ final class DirectoryState { mPendingOperation = bundle.getParcelable(FileOperationService.EXTRA_OPERATION); mLastSortDimensionId = bundle.getInt(EXTRA_SORT_DIMENSION_ID); mLastSortDirection = bundle.getInt(EXTRA_SORT_DIRECTION); + mSelectionId = bundle.getString(EXTRA_SELECTION_ID); } public void save(Bundle bundle) { @@ -57,6 +63,7 @@ final class DirectoryState { bundle.putParcelable(FileOperationService.EXTRA_OPERATION, mPendingOperation); bundle.putInt(EXTRA_SORT_DIMENSION_ID, mLastSortDimensionId); bundle.putInt(EXTRA_SORT_DIRECTION, mLastSortDirection); + bundle.putString(EXTRA_SELECTION_ID, mSelectionId); } public FileOperation claimPendingOperation() { @@ -65,11 +72,6 @@ final class DirectoryState { return op; } - public void update(RootInfo root, DocumentInfo doc) { - mRoot = root; - mDocument = doc; - } - String getConfigKey() { if (mConfigKey == null) { final StringBuilder builder = new StringBuilder(); diff --git a/src/com/android/documentsui/dirlist/DocsItemDetailsLookup.java b/src/com/android/documentsui/dirlist/DocsItemDetailsLookup.java index bc4494cf8..6f16b7632 100644 --- a/src/com/android/documentsui/dirlist/DocsItemDetailsLookup.java +++ b/src/com/android/documentsui/dirlist/DocsItemDetailsLookup.java @@ -15,18 +15,19 @@ */ package com.android.documentsui.dirlist; +import android.view.MotionEvent; +import android.view.View; + import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.view.MotionEvent; -import android.view.View; -import com.android.documentsui.selection.ItemDetailsLookup; +import com.android.documentsui.DocsSelectionHelper.DocDetailsLookup; /** * Access to details of an item associated with a {@link MotionEvent} instance. */ -final class DocsItemDetailsLookup extends ItemDetailsLookup { +final class DocsItemDetailsLookup extends DocDetailsLookup { private final RecyclerView mRecView; @@ -35,47 +36,7 @@ final class DocsItemDetailsLookup extends ItemDetailsLookup { } @Override - public boolean overItem(MotionEvent e) { - return getItemPosition(e) != RecyclerView.NO_POSITION; - } - - @Override - public boolean overStableItem(MotionEvent e) { - if (!overItem(e)) { - return false; - } - ItemDetails details = getItemDetails(e); - return details != null && details.hasStableId(); - } - - @Override - public boolean inItemDragRegion(MotionEvent e) { - if (!overItem(e)) { - return false; - } - ItemDetails details = getItemDetails(e); - return details != null && details.inDragRegion(e); - } - - @Override - public boolean inItemSelectRegion(MotionEvent e) { - if (!overItem(e)) { - return false; - } - ItemDetails details = getItemDetails(e); - return details != null && details.inSelectionHotspot(e); - } - - @Override - public int getItemPosition(MotionEvent e) { - View child = mRecView.findChildViewUnder(e.getX(), e.getY()); - return (child != null) - ? mRecView.getChildAdapterPosition(child) - : RecyclerView.NO_POSITION; - } - - @Override - public ItemDetails getItemDetails(MotionEvent e) { + public DocumentItemDetails getItemDetails(MotionEvent e) { @Nullable DocumentHolder holder = getDocumentHolder(e); return holder == null ? null : holder.getItemDetails(); } diff --git a/src/com/android/documentsui/dirlist/DocsSelectionPredicate.java b/src/com/android/documentsui/dirlist/DocsSelectionPredicate.java index 5ceb4617a..167cf1dbc 100644 --- a/src/com/android/documentsui/dirlist/DocsSelectionPredicate.java +++ b/src/com/android/documentsui/dirlist/DocsSelectionPredicate.java @@ -21,19 +21,20 @@ import static com.android.documentsui.base.DocumentInfo.getCursorString; import android.database.Cursor; import android.provider.DocumentsContract.Document; + +import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import com.android.documentsui.ActivityConfig; import com.android.documentsui.Model; import com.android.documentsui.base.State; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; /** * Class embodying the logic as to whether an item (specified by id or position) * can be selected (or not). */ -final class DocsSelectionPredicate extends SelectionPredicate { +final class DocsSelectionPredicate extends SelectionPredicate<String> { private ActivityConfig mConfig; private Model mModel; @@ -56,7 +57,7 @@ final class DocsSelectionPredicate extends SelectionPredicate { } @Override - public boolean canSetStateForId(String id, boolean nextState) { + public boolean canSetStateForKey(String id, boolean nextState) { if (nextState) { // Check if an item can be selected final Cursor cursor = mModel.getItem(id); diff --git a/src/com/android/documentsui/dirlist/DocsStableIdProvider.java b/src/com/android/documentsui/dirlist/DocsStableIdProvider.java index 96390df59..6ff73f279 100644 --- a/src/com/android/documentsui/dirlist/DocsStableIdProvider.java +++ b/src/com/android/documentsui/dirlist/DocsStableIdProvider.java @@ -17,9 +17,7 @@ package com.android.documentsui.dirlist; import static androidx.core.util.Preconditions.checkArgument; -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; - -import java.util.List; +import com.android.documentsui.DocsSelectionHelper.StableIdProvider; /** * Provides RecyclerView selection code access to stable ids backed @@ -35,7 +33,7 @@ public final class DocsStableIdProvider extends StableIdProvider { } @Override - public String getStableId(int position) { + public String getKey(int position) { return mAdapter.getStableId(position); } @@ -43,9 +41,4 @@ public final class DocsStableIdProvider extends StableIdProvider { public int getPosition(String id) { return mAdapter.getPosition(id); } - - @Override - public List<String> getStableIds() { - return mAdapter.getStableIds(); - } } diff --git a/src/com/android/documentsui/dirlist/DocumentHolder.java b/src/com/android/documentsui/dirlist/DocumentHolder.java index 7854dc452..c1c5c1ee3 100644 --- a/src/com/android/documentsui/dirlist/DocumentHolder.java +++ b/src/com/android/documentsui/dirlist/DocumentHolder.java @@ -18,7 +18,6 @@ package com.android.documentsui.dirlist; import android.content.Context; import android.database.Cursor; -import androidx.recyclerview.widget.RecyclerView; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -27,11 +26,15 @@ import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.widget.ImageView; +import androidx.recyclerview.widget.RecyclerView; + import com.android.documentsui.base.Shared; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import javax.annotation.Nullable; +/** + * ViewHolder of a document item within a RecyclerView. + */ public abstract class DocumentHolder extends RecyclerView.ViewHolder implements View.OnKeyListener { @@ -42,7 +45,7 @@ public abstract class DocumentHolder protected @Nullable String mModelId; // See #addKeyEventListener for details on the need for this field. - private KeyboardEventListener mKeyEventListener; + private KeyboardEventListener<DocumentItemDetails> mKeyEventListener; private final DocumentItemDetails mDetails; @@ -56,7 +59,7 @@ public abstract class DocumentHolder itemView.setOnKeyListener(this); mContext = context; - mDetails = new DocumentItemDetails(); + mDetails = new DocumentItemDetails(this); } /** @@ -94,10 +97,10 @@ public abstract class DocumentHolder @Override public boolean onKey(View v, int keyCode, KeyEvent event) { assert(mKeyEventListener != null); - ItemDetails details = getItemDetails(); + DocumentItemDetails details = getItemDetails(); return (details == null) - ? false - : mKeyEventListener.onKey(getItemDetails(), keyCode, event); + ? false + : mKeyEventListener.onKey(details, keyCode, event); } /** @@ -108,7 +111,7 @@ public abstract class DocumentHolder * * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. */ - public void addKeyEventListener(KeyboardEventListener listener) { + public void addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener) { assert(mKeyEventListener == null); mKeyEventListener = listener; } @@ -121,7 +124,7 @@ public abstract class DocumentHolder return false; } - public ItemDetails getItemDetails() { + public DocumentItemDetails getItemDetails() { return mDetails; } @@ -148,32 +151,4 @@ public abstract class DocumentHolder static ViewPropertyAnimator fade(ImageView view, float alpha) { return view.animate().setDuration(Shared.CHECK_ANIMATION_DURATION).alpha(alpha); } - - private final class DocumentItemDetails extends ItemDetails { - - @Override - public int getPosition() { - return DocumentHolder.this.getAdapterPosition(); - } - - @Override - public String getStableId() { - return DocumentHolder.this.getModelId(); - } - - @Override - public int getItemViewType() { - return DocumentHolder.this.getItemViewType(); - } - - @Override - public boolean inDragRegion(MotionEvent e) { - return DocumentHolder.this.inDragRegion(e); - } - - @Override - public boolean inSelectionHotspot(MotionEvent e) { - return DocumentHolder.this.inSelectRegion(e); - } - } } diff --git a/src/com/android/documentsui/dirlist/DocumentItemDetails.java b/src/com/android/documentsui/dirlist/DocumentItemDetails.java new file mode 100644 index 000000000..bf6d7ddd5 --- /dev/null +++ b/src/com/android/documentsui/dirlist/DocumentItemDetails.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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 com.android.documentsui.dirlist; + +import android.view.MotionEvent; + +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + +/** + * Provide information of a specific RecyclerView item for selection. + */ +public final class DocumentItemDetails extends ItemDetails<String> { + private final DocumentHolder mDocumentHolder; + + DocumentItemDetails(DocumentHolder holder) { + mDocumentHolder = holder; + } + + /** + * @return The view type of this ViewHolder. + */ + public int getItemViewType() { + return mDocumentHolder.getItemViewType(); + } + + @Override + public int getPosition() { + return mDocumentHolder.getAdapterPosition(); + } + + @Override + public String getSelectionKey() { + return mDocumentHolder.getModelId(); + } + + @Override + public boolean inDragRegion(MotionEvent e) { + return mDocumentHolder.inDragRegion(e); + } + + @Override + public boolean inSelectionHotspot(MotionEvent e) { + return mDocumentHolder.inSelectRegion(e); + } +} diff --git a/src/com/android/documentsui/dirlist/DragHost.java b/src/com/android/documentsui/dirlist/DragHost.java index 770097d32..c1f9c8cb0 100644 --- a/src/com/android/documentsui/dirlist/DragHost.java +++ b/src/com/android/documentsui/dirlist/DragHost.java @@ -20,9 +20,11 @@ import static com.android.documentsui.base.SharedMinimal.DEBUG; import android.app.Activity; import android.content.ClipData; +import android.util.Log; import android.view.DragEvent; import android.view.View; -import android.util.Log; + +import androidx.recyclerview.selection.SelectionTracker; import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.AbstractDragHost; @@ -32,7 +34,6 @@ import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.State; -import com.android.documentsui.selection.SelectionHelper; import com.android.documentsui.ui.DialogController; import java.util.function.Predicate; @@ -45,7 +46,7 @@ class DragHost<T extends Activity & AbstractActionHandler.CommonAddons> extends private static final String TAG = "dirlist.DragHost"; private final T mActivity; - private final SelectionHelper mSelectionMgr; + private final SelectionTracker<String> mSelectionMgr; private final ActionHandler mActions; private final State mState; private final DialogController mDialogs; @@ -56,7 +57,7 @@ class DragHost<T extends Activity & AbstractActionHandler.CommonAddons> extends DragHost( T activity, DragAndDropManager dragAndDropManager, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, ActionHandler actions, State state, DialogController dialogs, diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java index e3fa088a6..e88967573 100644 --- a/src/com/android/documentsui/dirlist/DragStartListener.java +++ b/src/com/android/documentsui/dirlist/DragStartListener.java @@ -17,24 +17,23 @@ package com.android.documentsui.dirlist; import static com.android.documentsui.base.SharedMinimal.DEBUG; -import static androidx.core.util.Preconditions.checkArgument; import android.net.Uri; -import androidx.annotation.VisibleForTesting; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; + import com.android.documentsui.DragAndDropManager; import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.Model; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Events; import com.android.documentsui.base.State; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; import java.util.ArrayList; import java.util.List; @@ -51,17 +50,12 @@ interface DragStartListener { static final DragStartListener DUMMY = new DragStartListener() { @Override - public boolean onMouseDragEvent(MotionEvent event) { - return false; - } - @Override - public boolean onTouchDragEvent(MotionEvent event) { + public boolean onDragEvent(MotionEvent event) { return false; } }; - boolean onMouseDragEvent(MotionEvent event); - boolean onTouchDragEvent(MotionEvent event); + boolean onDragEvent(MotionEvent event); @VisibleForTesting class RuntimeDragStartListener implements DragStartListener { @@ -70,12 +64,11 @@ interface DragStartListener { private final IconHelper mIconHelper; private final State mState; - private final ItemDetailsLookup mDetailsLookup; - private final SelectionHelper mSelectionMgr; + private final SelectionTracker<String> mSelectionMgr; private final SelectionDetails mSelectionDetails; private final ViewFinder mViewFinder; private final Function<View, String> mIdFinder; - private final Function<Selection, List<DocumentInfo>> mDocsConverter; + private final Function<Selection<String>, List<DocumentInfo>> mDocsConverter; private final DragAndDropManager mDragAndDropManager; @@ -84,17 +77,15 @@ interface DragStartListener { public RuntimeDragStartListener( IconHelper iconHelper, State state, - ItemDetailsLookup detailsLookup, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, SelectionDetails selectionDetails, ViewFinder viewFinder, Function<View, String> idFinder, - Function<Selection, List<DocumentInfo>> docsConverter, + Function<Selection<String>, List<DocumentInfo>> docsConverter, DragAndDropManager dragAndDropManager) { mIconHelper = iconHelper; mState = state; - mDetailsLookup = detailsLookup; mSelectionMgr = selectionMgr; mSelectionDetails = selectionDetails; mViewFinder = viewFinder; @@ -104,15 +95,7 @@ interface DragStartListener { } @Override - public final boolean onMouseDragEvent(MotionEvent event) { - checkArgument(Events.isMouseDragEvent(event)); - checkArgument(mDetailsLookup.inItemDragRegion(event)); - - return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); - } - - @Override - public final boolean onTouchDragEvent(MotionEvent event) { + public final boolean onDragEvent(MotionEvent event) { return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); } @@ -132,7 +115,7 @@ interface DragStartListener { return false; } - Selection selection = getSelectionToBeCopied(modelId, event); + Selection<String> selection = getSelectionToBeCopied(modelId, event); final List<DocumentInfo> srcs = mDocsConverter.apply(selection); @@ -158,8 +141,8 @@ interface DragStartListener { * coordinates of the event, return a valid selection for drag and drop operation */ @VisibleForTesting - MutableSelection getSelectionToBeCopied(String modelId, MotionEvent event) { - MutableSelection selection = new MutableSelection(); + MutableSelection<String> getSelectionToBeCopied(String modelId, MotionEvent event) { + MutableSelection<String> selection = new MutableSelection<>(); // If CTRL-key is held down and there's other existing selection, add item to // selection (if not already selected) if (Events.isCtrlKeyPressed(event) @@ -181,10 +164,9 @@ interface DragStartListener { static DragStartListener create( IconHelper iconHelper, Model model, - SelectionHelper selectionMgr, + SelectionTracker<String> selectionMgr, SelectionDetails selectionDetails, State state, - ItemDetailsLookup detailsLookup, Function<View, String> idFinder, ViewFinder viewFinder, DragAndDropManager dragAndDropManager) { @@ -192,7 +174,6 @@ interface DragStartListener { return new RuntimeDragStartListener( iconHelper, state, - detailsLookup, selectionMgr, selectionDetails, viewFinder, diff --git a/src/com/android/documentsui/dirlist/FocusHandler.java b/src/com/android/documentsui/dirlist/FocusHandler.java index 03cf23380..2c7f8e672 100644 --- a/src/com/android/documentsui/dirlist/FocusHandler.java +++ b/src/com/android/documentsui/dirlist/FocusHandler.java @@ -62,7 +62,7 @@ public interface FocusHandler extends View.OnFocusChangeListener { /** * @return The adapter position of the last focused item. */ - int getFocusPosition(); + int getFocusedPosition(); /** * @return True if there is currently an item in focus, false otherwise. diff --git a/src/com/android/documentsui/dirlist/InputHandlers.java b/src/com/android/documentsui/dirlist/InputHandlers.java index 2943c80a2..8cc2bfb74 100644 --- a/src/com/android/documentsui/dirlist/InputHandlers.java +++ b/src/com/android/documentsui/dirlist/InputHandlers.java @@ -17,22 +17,14 @@ package com.android.documentsui.dirlist; import static androidx.core.util.Preconditions.checkArgument; +import android.view.KeyEvent; + +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; -import android.view.MotionEvent; import com.android.documentsui.ActionHandler; -import com.android.documentsui.base.EventHandler; -import com.android.documentsui.base.State; -import com.android.documentsui.selection.GestureSelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.MouseInputHandler; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.TouchInputHandler; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; /** * Helper class dedicated to building gesture input handlers. The construction @@ -43,43 +35,36 @@ import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; final class InputHandlers { private ActionHandler mActions; - private SelectionHelper mSelectionHelper; - private SelectionPredicate mSelectionPredicate; - private ItemDetailsLookup mDetailsLookup; + private SelectionTracker<String> mSelectionHelper; + private SelectionPredicate<String> mSelectionPredicate; private FocusHandler mFocusHandler; private RecyclerView mRecView; - private State mState; InputHandlers( ActionHandler actions, - SelectionHelper selectionHelper, - SelectionPredicate selectionPredicate, - ItemDetailsLookup detailsLookup, + SelectionTracker<String> selectionHelper, + SelectionPredicate<String> selectionPredicate, FocusHandler focusHandler, - RecyclerView recView, - State state) { + RecyclerView recView) { checkArgument(actions != null); checkArgument(selectionHelper != null); checkArgument(selectionPredicate != null); - checkArgument(detailsLookup != null); checkArgument(focusHandler != null); checkArgument(recView != null); - checkArgument(state != null); mActions = actions; mSelectionHelper = selectionHelper; mSelectionPredicate = selectionPredicate; - mDetailsLookup = detailsLookup; mFocusHandler = focusHandler; mRecView = recView; - mState = state; } KeyInputHandler createKeyHandler() { - KeyInputHandler.Callbacks callbacks = new KeyInputHandler.Callbacks() { + KeyInputHandler.Callbacks<DocumentItemDetails> callbacks = + new KeyInputHandler.Callbacks<DocumentItemDetails>() { @Override - public boolean isInteractiveItem(ItemDetails item, KeyEvent e) { + public boolean isInteractiveItem(DocumentItemDetails item, KeyEvent e) { switch (item.getItemViewType()) { case DocumentsAdapter.ITEM_TYPE_HEADER_MESSAGE: case DocumentsAdapter.ITEM_TYPE_INFLATED_MESSAGE: @@ -95,7 +80,7 @@ final class InputHandlers { } @Override - public boolean onItemActivated(ItemDetails item, KeyEvent e) { + public boolean onItemActivated(DocumentItemDetails item, KeyEvent e) { // Handle enter key events switch (e.getKeyCode()) { case KeyEvent.KEYCODE_ENTER: @@ -116,7 +101,7 @@ final class InputHandlers { } @Override - public boolean onFocusItem(ItemDetails details, int keyCode, KeyEvent event) { + public boolean onFocusItem(DocumentItemDetails details, int keyCode, KeyEvent event) { ViewHolder holder = mRecView.findViewHolderForAdapterPosition(details.getPosition()); if (holder instanceof DocumentHolder) { @@ -124,104 +109,8 @@ final class InputHandlers { } return false; } - - @Override - public void onPerformHapticFeedback() { - mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } }; return new KeyInputHandler(mSelectionHelper, mSelectionPredicate, callbacks); } - - MouseInputHandler createMouseHandler( - EventHandler<MotionEvent> showContextMenuCallback) { - - checkArgument(showContextMenuCallback != null); - - MouseInputHandler.Callbacks callbacks = new MouseInputHandler.Callbacks() { - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - return mActions.openItem( - item, - ActionHandler.VIEW_TYPE_REGULAR, - ActionHandler.VIEW_TYPE_PREVIEW); - } - - @Override - public boolean onContextClick(MotionEvent e) { - return showContextMenuCallback.accept(e); - } - - @Override - public void onPerformHapticFeedback() { - mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - - @Override - public void focusItem(ItemDetails item) { - mFocusHandler.focusDocument(item.getStableId()); - } - - @Override - public void clearFocus() { - mFocusHandler.clearFocus(); - } - - @Override - public boolean hasFocusedItem() { - return mFocusHandler.hasFocusedItem(); - } - - @Override - public int getFocusedPosition() { - return mFocusHandler.getFocusPosition(); - } - }; - - return new MouseInputHandler(mSelectionHelper, mDetailsLookup, callbacks); - } - - /** - * Factory method for input touch delegate. Exists to reduce complexity in the - * calling scope. - * @param gestureHelper - */ - TouchInputHandler createTouchHandler( - GestureSelectionHelper gestureHelper, DragStartListener dragStartListener) { - checkArgument(dragStartListener != null); - - TouchInputHandler.Callbacks callbacks = new TouchInputHandler.Callbacks() { - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - return mActions.openItem( - item, - ActionHandler.VIEW_TYPE_PREVIEW, - ActionHandler.VIEW_TYPE_REGULAR); - } - - @Override - public boolean onDragInitiated(MotionEvent e) { - return dragStartListener.onTouchDragEvent(e); - } - - @Override - public void onPerformHapticFeedback() { - mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - - @Override - public void focusItem(ItemDetails item) { - mFocusHandler.focusDocument(item.getStableId()); - } - - @Override - public void clearFocus() { - mFocusHandler.clearFocus(); - } - }; - - return new TouchInputHandler( - mSelectionHelper, mDetailsLookup, mSelectionPredicate, gestureHelper, callbacks); - } } diff --git a/src/com/android/documentsui/dirlist/KeyInputHandler.java b/src/com/android/documentsui/dirlist/KeyInputHandler.java index 6e82c3e0e..826270ea1 100644 --- a/src/com/android/documentsui/dirlist/KeyInputHandler.java +++ b/src/com/android/documentsui/dirlist/KeyInputHandler.java @@ -17,31 +17,30 @@ package com.android.documentsui.dirlist; import android.view.KeyEvent; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; + import com.android.documentsui.base.Events; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.MotionInputHandler; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.MotionInputHandler.Callbacks; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; import javax.annotation.Nullable; +// TODO(b/69058726): Migrate to RecyclerView-Selection /** * Class that handles keyboard events on RecyclerView items. The input handler * must be attached directly to a RecyclerView item since, unlike DOM, events * don't appear bubble up. */ -public final class KeyInputHandler extends KeyboardEventListener { +public final class KeyInputHandler extends KeyboardEventListener<DocumentItemDetails> { - private final SelectionHelper mSelectionHelper; - private final SelectionPredicate mSelectionPredicate; - private final Callbacks mCallbacks; + private final SelectionTracker<String> mSelectionHelper; + private final SelectionPredicate<String> mSelectionPredicate; + private final Callbacks<DocumentItemDetails> mCallbacks; public KeyInputHandler( - SelectionHelper selectionHelper, - SelectionPredicate selectionPredicate, - Callbacks callbacks) { + SelectionTracker<String> selectionHelper, + SelectionPredicate<String> selectionPredicate, + Callbacks<DocumentItemDetails> callbacks) { mSelectionHelper = selectionHelper; mSelectionPredicate = selectionPredicate; @@ -49,7 +48,7 @@ public final class KeyInputHandler extends KeyboardEventListener { } @Override - public boolean onKey(@Nullable ItemDetails details, int keyCode, KeyEvent event) { + public boolean onKey(@Nullable DocumentItemDetails details, int keyCode, KeyEvent event) { // Only handle key-down events. This is simpler, consistent with most other UIs, and // enables the handling of repeated key events from holding down a key. if (event.getAction() != KeyEvent.ACTION_DOWN) { @@ -96,17 +95,17 @@ public final class KeyInputHandler extends KeyboardEventListener { return mCallbacks.onItemActivated(details, event); } - private boolean shouldExtendSelection(ItemDetails item, KeyEvent event) { + private boolean shouldExtendSelection(DocumentItemDetails item, KeyEvent event) { if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) { return false; } - return mSelectionPredicate.canSetStateForId(item.getStableId(), true); + return mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true); } - public static abstract class Callbacks extends MotionInputHandler.Callbacks { - public abstract boolean isInteractiveItem(ItemDetails item, KeyEvent e); - public abstract boolean onItemActivated(ItemDetails item, KeyEvent e); - public abstract boolean onFocusItem(ItemDetails details, int keyCode, KeyEvent event); + public static abstract class Callbacks<T extends ItemDetails<?>> { + public abstract boolean isInteractiveItem(T item, KeyEvent e); + public abstract boolean onItemActivated(T item, KeyEvent e); + public abstract boolean onFocusItem(T details, int keyCode, KeyEvent event); } } diff --git a/src/com/android/documentsui/dirlist/KeyboardEventListener.java b/src/com/android/documentsui/dirlist/KeyboardEventListener.java index fb1fc0a6b..d45885de6 100644 --- a/src/com/android/documentsui/dirlist/KeyboardEventListener.java +++ b/src/com/android/documentsui/dirlist/KeyboardEventListener.java @@ -17,14 +17,13 @@ package com.android.documentsui.dirlist; import android.view.KeyEvent; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; /** * KeyboardListener is implemented by {@link KeyInputHandler}. The handler * must be called from RecyclerView.Holders in response to keyboard events. */ -public abstract class KeyboardEventListener { +public abstract class KeyboardEventListener<T extends ItemDetails<?>> { /** * Handles key events on the view holder. @@ -35,5 +34,5 @@ public abstract class KeyboardEventListener { * * @return Whether the event was handled. */ - public abstract boolean onKey(ItemDetails details, int keyCode, KeyEvent event); + public abstract boolean onKey(T details, int keyCode, KeyEvent event); } diff --git a/src/com/android/documentsui/dirlist/MessageHolder.java b/src/com/android/documentsui/dirlist/MessageHolder.java index 92686025b..1d8d7d61c 100644 --- a/src/com/android/documentsui/dirlist/MessageHolder.java +++ b/src/com/android/documentsui/dirlist/MessageHolder.java @@ -16,10 +16,7 @@ package com.android.documentsui.dirlist; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; - import android.content.Context; -import android.view.MotionEvent; import android.view.ViewGroup; import android.widget.Space; @@ -36,7 +33,7 @@ abstract class MessageHolder extends DocumentHolder { } @Override - public ItemDetails getItemDetails() { + public DocumentItemDetails getItemDetails() { return null; } } diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java index 85be6a692..604badf4f 100644 --- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java @@ -23,16 +23,17 @@ import static com.android.documentsui.base.State.MODE_LIST; import android.database.Cursor; import android.provider.DocumentsContract.Document; -import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.ViewGroup; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.widget.RecyclerView; + import com.android.documentsui.Model; import com.android.documentsui.Model.Update; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.State; -import com.android.documentsui.selection.SelectionHelper; import java.util.ArrayList; import java.util.List; @@ -111,7 +112,7 @@ final class ModelBackedDocumentsAdapter extends DocumentsAdapter { @Override public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { - if (payload.contains(SelectionHelper.SELECTION_CHANGED_MARKER)) { + if (payload.contains(SelectionTracker.SELECTION_CHANGED_MARKER)) { final boolean selected = mEnv.isSelected(mModelIds.get(position)); holder.setSelected(selected, true); } else { diff --git a/src/com/android/documentsui/dirlist/MouseDragEventInterceptor.java b/src/com/android/documentsui/dirlist/MouseDragEventInterceptor.java deleted file mode 100644 index 2aba9277c..000000000 --- a/src/com/android/documentsui/dirlist/MouseDragEventInterceptor.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.dirlist; - -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; -import android.view.MotionEvent; - -import com.android.documentsui.base.EventHandler; -import com.android.documentsui.base.Events; -import com.android.documentsui.selection.BandSelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.TouchEventRouter; - -/** - * OnItemTouchListener that helps enable support for drag/drop functionality - * in DirectoryFragment. - * - * <p>This class takes an OnItemTouchListener that is presumably the - * one provided by {@link BandSelectionHelper#getListener()}, and - * is used in conjunction with the {@link TouchEventRouter}. - * - * <p>See DirectoryFragment for more details on how this is glued - * into DocumetnsUI event handling to make the magic happen. - */ -class MouseDragEventInterceptor implements OnItemTouchListener { - - private final ItemDetailsLookup mEventDetailsLookup; - private final EventHandler<MotionEvent> mMouseDragListener; - private final @Nullable OnItemTouchListener mDelegate; - - public MouseDragEventInterceptor( - ItemDetailsLookup eventDetailsLookup, - EventHandler<MotionEvent> mouseDragListener, - @Nullable OnItemTouchListener delegate) { - - checkArgument(eventDetailsLookup != null); - checkArgument(mouseDragListener != null); - - mEventDetailsLookup = eventDetailsLookup; - mMouseDragListener = mouseDragListener; - mDelegate = delegate; - } - - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { - if (Events.isMouseDragEvent(e) && mEventDetailsLookup.inItemDragRegion(e)) { - return mMouseDragListener.accept(e); - } else if (mDelegate != null) { - return mDelegate.onInterceptTouchEvent(rv, e); - } - return false; - } - - @Override - public void onTouchEvent(RecyclerView rv, MotionEvent e) { - if (mDelegate != null) { - mDelegate.onTouchEvent(rv, e); - } - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} -} diff --git a/src/com/android/documentsui/dirlist/SelectionMetadata.java b/src/com/android/documentsui/dirlist/SelectionMetadata.java index 3ac3a0741..3abc3e190 100644 --- a/src/com/android/documentsui/dirlist/SelectionMetadata.java +++ b/src/com/android/documentsui/dirlist/SelectionMetadata.java @@ -23,11 +23,12 @@ import android.database.Cursor; import android.provider.DocumentsContract.Document; import android.util.Log; +import androidx.recyclerview.selection.SelectionTracker.SelectionObserver; + import com.android.documentsui.MenuManager; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.MimeTypes; import com.android.documentsui.roots.RootCursorWrapper; -import com.android.documentsui.selection.SelectionHelper.SelectionObserver; import java.util.function.Function; @@ -38,7 +39,7 @@ import java.util.function.Function; * <p>By collecting information in real-time as the selection changes the need to * traverse the entire selection in order to answer questions is eliminated. */ -public class SelectionMetadata extends SelectionObserver +public class SelectionMetadata extends SelectionObserver<String> implements MenuManager.SelectionDetails { private static final String TAG = "SelectionMetadata"; @@ -107,7 +108,7 @@ public class SelectionMetadata extends SelectionObserver } @Override - public void onSelectionReset() { + public void onSelectionRefresh() { mFileCount = 0; mDirectoryCount = 0; mPartialCount = 0; diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index 19c812ffd..62ae87807 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -31,6 +31,10 @@ import android.util.Log; import android.view.DragEvent; import android.view.View; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; + import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.ActionModeAddons; import com.android.documentsui.ActivityConfig; @@ -60,9 +64,6 @@ import com.android.documentsui.files.ActionHandler.Addons; import com.android.documentsui.inspector.InspectorActivity; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.ProvidersAccess; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperations; @@ -140,7 +141,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void openSelectedInNewWindow() { - Selection selection = getStableSelection(); + Selection<String> selection = getStableSelection(); assert(selection.size() == 1); DocumentInfo doc = mModel.getDocument(selection.iterator().next()); assert(doc != null); @@ -194,12 +195,12 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa } @Override - public boolean openItem(ItemDetails details, @ViewType int type, + public boolean openItem(ItemDetails<String> details, @ViewType int type, @ViewType int fallback) { - DocumentInfo doc = mModel.getDocument(details.getStableId()); + DocumentInfo doc = mModel.getDocument(details.getSelectionKey()); if (doc == null) { - Log.w(TAG, - "Can't view item. No Document available for modeId: " + details.getStableId()); + Log.w(TAG, "Can't view item. No Document available for modeId: " + + details.getSelectionKey()); return false; } @@ -224,8 +225,8 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa openContainerDocument(doc); } - private Selection getSelectedOrFocused() { - final MutableSelection selection = this.getStableSelection(); + private Selection<String> getSelectedOrFocused() { + final MutableSelection<String> selection = this.getStableSelection(); if (selection.isEmpty()) { String focusModelId = mFocusHandler.getFocusModelId(); if (focusModelId != null) { @@ -239,7 +240,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void cutToClipboard() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_CUT_CLIPBOARD); - Selection selection = getSelectedOrFocused(); + Selection<String> selection = getSelectedOrFocused(); if (selection.isEmpty()) { return; @@ -260,7 +261,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void copyToClipboard() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_COPY_CLIPBOARD); - Selection selection = getSelectedOrFocused(); + Selection<String> selection = getSelectedOrFocused(); if (selection.isEmpty()) { return; @@ -275,7 +276,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void viewInOwner() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_VIEW_IN_APPLICATION); - Selection selection = getSelectedOrFocused(); + Selection<String> selection = getSelectedOrFocused(); if (selection.isEmpty() || selection.size() > 1) { return; @@ -297,7 +298,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void deleteSelectedDocuments() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE); - Selection selection = getSelectedOrFocused(); + Selection<String> selection = getSelectedOrFocused(); if (selection.isEmpty()) { return; @@ -362,7 +363,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa public void shareSelectedDocuments() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_SHARE); - Selection selection = getStableSelection(); + Selection<String> selection = getStableSelection(); assert(!selection.isEmpty()); diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index e8cdc642f..c07deb6ed 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -98,7 +98,7 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons super.onCreate(icicle); DocumentClipper clipper = DocumentsApplication.getDocumentClipper(this); - mInjector.selectionMgr = DocsSelectionHelper.createMultiSelect(); + mInjector.selectionMgr = DocsSelectionHelper.create(); mInjector.focusManager = new FocusManager( mInjector.features, diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java index bc21a2d49..9bb102c83 100644 --- a/src/com/android/documentsui/files/MenuManager.java +++ b/src/com/android/documentsui/files/MenuManager.java @@ -28,15 +28,15 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import androidx.recyclerview.selection.SelectionTracker; + import com.android.documentsui.R; -import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Features; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.queries.SearchViewManager; -import com.android.documentsui.selection.SelectionHelper; import java.util.List; import java.util.function.IntFunction; @@ -45,7 +45,7 @@ public final class MenuManager extends com.android.documentsui.MenuManager { private final Features mFeatures; private final Context mContext; - private final SelectionHelper mSelectionManager; + private final SelectionTracker<String> mSelectionManager; private final Lookup<String, Uri> mUriLookup; private final Lookup<String, String> mAppNameLookup; @@ -55,7 +55,7 @@ public final class MenuManager extends com.android.documentsui.MenuManager { State displayState, DirectoryDetails dirDetails, Context context, - SelectionHelper selectionManager, + SelectionTracker<String> selectionManager, Lookup<String, String> appNameLookup, Lookup<String, Uri> uriLookup) { diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java index f18a7fe6f..ff8a2dd71 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -36,6 +36,8 @@ import android.provider.DocumentsContract; import android.provider.Settings; import android.util.Log; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.ActivityConfig; import com.android.documentsui.DocumentsAccess; @@ -54,7 +56,6 @@ import com.android.documentsui.dirlist.AnimationView; import com.android.documentsui.picker.ActionHandler.Addons; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.ProvidersAccess; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import com.android.documentsui.services.FileOperationService; import androidx.annotation.VisibleForTesting; @@ -246,12 +247,12 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T } @Override - public boolean openItem(ItemDetails details, @ViewType int type, + public boolean openItem(ItemDetails<String> details, @ViewType int type, @ViewType int fallback) { - DocumentInfo doc = mModel.getDocument(details.getStableId()); + DocumentInfo doc = mModel.getDocument(details.getSelectionKey()); if (doc == null) { - Log.w(TAG, - "Can't view item. No Document available for modeId: " + details.getStableId()); + Log.w(TAG, "Can't view item. No Document available for modeId: " + + details.getSelectionKey()); return false; } diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java index dc9690e1b..4e1556275 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -94,9 +94,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { super.onCreate(icicle); - mInjector.selectionMgr = mState.allowMultiple - ? DocsSelectionHelper.createMultiSelect() - : DocsSelectionHelper.createSingleSelect(); + mInjector.selectionMgr = DocsSelectionHelper.create(); mInjector.focusManager = new FocusManager( mInjector.features, diff --git a/src/com/android/documentsui/selection/BandPredicate.java b/src/com/android/documentsui/selection/BandPredicate.java deleted file mode 100644 index 08d64a81b..000000000 --- a/src/com/android/documentsui/selection/BandPredicate.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; -import android.view.View; - -/** - * Provides a means of controlling when and where band selection is initiated. - * This can be used to permit band initiation in non-empty areas, like in the whitespace of - * a bound view. This is especially useful when there is no empty space between items. - */ -public abstract class BandPredicate { - - /** @return true if band selection can be initiated in response to the {@link MotionEvent}. */ - public abstract boolean canInitiate(MotionEvent e); - - /** - * A BandPredicate that allows initiation of band selection only in areas of RecyclerView - * that have {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas - * between views. - */ - public static final class NoPositionBandPredicate extends BandPredicate { - - private final RecyclerView mRecView; - - public NoPositionBandPredicate(RecyclerView recView) { - checkArgument(recView != null); - - mRecView = recView; - } - - @Override - public boolean canInitiate(MotionEvent e) { - View itemView = mRecView.findChildViewUnder(e.getX(), e.getY()); - int position = itemView != null - ? mRecView.getChildAdapterPosition(itemView) - : RecyclerView.NO_POSITION; - - return position == RecyclerView.NO_POSITION; - } - }; -} diff --git a/src/com/android/documentsui/selection/BandSelectionHelper.java b/src/com/android/documentsui/selection/BandSelectionHelper.java deleted file mode 100644 index f5aa4665d..000000000 --- a/src/com/android/documentsui/selection/BandSelectionHelper.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.core.util.Preconditions.checkState; -import static com.android.documentsui.base.SharedMinimal.DEBUG; - -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Build; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; -import com.android.documentsui.selection.ViewAutoScroller.ScrollHost; -import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Provides mouse driven band-selection support when used in conjunction with a {@link RecyclerView} - * instance. This class is responsible for rendering a band overlay and manipulating selection - * status of the items it intersects with. - * - * <p> Given the recycling nature of RecyclerView items that have scrolled off-screen would not - * be selectable with a band that itself was partially rendered off-screen. To address this, - * BandSelectionController builds a model of the list/grid information presented by RecyclerView as - * 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. - */ -public class BandSelectionHelper implements OnItemTouchListener { - - static final String TAG = "BandController"; - - private final BandHost mHost; - private final StableIdProvider mStableIds; - private final RecyclerView.Adapter<?> mAdapter; - private final SelectionHelper mSelectionHelper; - private final SelectionPredicate mSelectionPredicate; - private final BandPredicate mBandPredicate; - private final ContentLock mLock; - private final Runnable mViewScroller; - private final GridModel.SelectionObserver mGridObserver; - private final List<Runnable> mBandStartedListeners = new ArrayList<>(); - - @Nullable private Rect mBounds; - @Nullable private Point mCurrentPosition; - @Nullable private Point mOrigin; - @Nullable private GridModel mModel; - - public BandSelectionHelper( - BandHost host, - RecyclerView.Adapter<?> adapter, - StableIdProvider stableIds, - SelectionHelper selectionHelper, - SelectionPredicate selectionPredicate, - BandPredicate bandPredicate, - ContentLock lock) { - - checkArgument(host != null); - checkArgument(adapter != null); - checkArgument(stableIds != null); - checkArgument(selectionHelper != null); - checkArgument(selectionPredicate != null); - checkArgument(bandPredicate != null); - checkArgument(lock != null); - - mHost = host; - mStableIds = stableIds; - mAdapter = adapter; - mSelectionHelper = selectionHelper; - mSelectionPredicate = selectionPredicate; - mBandPredicate = bandPredicate; - mLock = lock; - - mHost.addOnScrollListener( - new OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - BandSelectionHelper.this.onScrolled(recyclerView, dx, dy); - } - }); - - mViewScroller = new ViewAutoScroller( - new ScrollHost() { - @Override - public Point getCurrentPosition() { - return mCurrentPosition; - } - - @Override - public int getViewHeight() { - return mHost.getHeight(); - } - - @Override - public boolean isActive() { - return BandSelectionHelper.this.isActive(); - } - }, - host); - - mAdapter.registerAdapterDataObserver( - new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - if (isActive()) { - endBandSelect(); - } - } - - @Override - public void onItemRangeChanged( - int startPosition, int itemCount, Object payload) { - // No change in position. Ignoring. - } - - @Override - public void onItemRangeInserted(int startPosition, int itemCount) { - if (isActive()) { - endBandSelect(); - } - } - - @Override - public void onItemRangeRemoved(int startPosition, int itemCount) { - assert(startPosition >= 0); - assert(itemCount > 0); - - // TODO: Should update grid model. - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - throw new UnsupportedOperationException(); - } - }); - - mGridObserver = new GridModel.SelectionObserver() { - @Override - public void onSelectionChanged(Set<String> updatedSelection) { - mSelectionHelper.setProvisionalSelection(updatedSelection); - } - }; - } - - @VisibleForTesting - boolean isActive() { - boolean active = mModel != null; - if (DEBUG && active) { - mLock.checkLocked(); - } - return active; - } - - /** - * Adds a new listener to be notified when band is created. - */ - public void addOnBandStartedListener(Runnable listener) { - checkArgument(listener != null); - - mBandStartedListeners.add(listener); - } - - /** - * Removes listener. No-op if listener was not previously installed. - */ - public void removeOnBandStartedListener(Runnable listener) { - mBandStartedListeners.remove(listener); - } - - /** - * Clients must call reset when there are any material changes to the layout of items - * in RecyclerView. - */ - public void reset() { - if (!isActive()) { - return; - } - - mHost.hideBand(); - mModel.stopCapturing(); - mModel.onDestroy(); - mModel = null; - mOrigin = null; - mLock.unblock(); - } - - boolean shouldStart(MotionEvent e) { - // Don't start, or extend bands on non-left clicks. - if (!MotionEvents.isPrimaryButtonPressed(e)) { - return false; - } - - // TODO: Refactor to NOT have side-effects on this "should" method. - // Weird things happen if we keep up band select - // when touch events happen. - if (isActive() && !MotionEvents.isMouseEvent(e)) { - endBandSelect(); - return false; - } - - // 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, or else starting band selection on mouse down can cause problems as events - // don't get routed correctly to onTouchEvent. - return !isActive() - && MotionEvents.isActionMove(e) - // the initial button move via mouse-touch (ie. down press) - // The adapter inserts items for UI layout purposes that aren't - // associated with files. Checking against actual modelIds count - // effectively ignores those UI layout items. - && !mStableIds.getStableIds().isEmpty() - && mBandPredicate.canInitiate(e); - } - - public boolean shouldStop(MotionEvent e) { - return isActive() - && MotionEvents.isMouseEvent(e) - && (MotionEvents.isActionUp(e) - || MotionEvents.isActionPointerUp(e) - || MotionEvents.isActionCancel(e)); - } - - @Override - public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) { - if (shouldStart(e)) { - if (!MotionEvents.isCtrlKeyPressed(e)) { - mSelectionHelper.clearSelection(); - } - - startBandSelect(MotionEvents.getOrigin(e)); - return isActive(); - } - - if (shouldStop(e)) { - endBandSelect(); - checkState(mModel == null); - // fall through to return false, because the band eeess done! - } - - return false; - } - - /** - * Processes a MotionEvent by starting, ending, or resizing the band select overlay. - * @param input - */ - @Override - public void onTouchEvent(RecyclerView unused, MotionEvent e) { - if (shouldStop(e)) { - endBandSelect(); - return; - } - - // We shouldn't get any events in this method when band select is not active, - // but it turns some guests show up late to the party. - // Probably happening when a re-layout is happening to the ReyclerView (ie. Pull-To-Refresh) - if (!isActive()) { - return; - } - - assert MotionEvents.isActionMove(e); - - mCurrentPosition = MotionEvents.getOrigin(e); - mModel.resizeSelection(mCurrentPosition); - - scrollViewIfNecessary(); - resizeBand(); - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} - - /** - * Starts band select by adding the drawable to the RecyclerView's overlay. - */ - private void startBandSelect(Point origin) { - if (DEBUG) Log.d(TAG, "Starting band select @ " + origin); - - reset(); - mModel = new GridModel(mHost, mStableIds, mSelectionPredicate); - mModel.addOnSelectionChangedListener(mGridObserver); - - mLock.block(); - notifyBandStarted(); - mOrigin = origin; - mModel.startCapturing(mOrigin); - } - - private void notifyBandStarted() { - for (Runnable listener : mBandStartedListeners) { - listener.run(); - } - } - - private void scrollViewIfNecessary() { - mHost.removeCallback(mViewScroller); - mViewScroller.run(); - mHost.invalidateView(); - } - - /** - * Resizes the band select rectangle by using the origin and the current pointer position as - * two opposite corners of the selection. - */ - private void resizeBand() { - mBounds = new Rect(Math.min(mOrigin.x, mCurrentPosition.x), - Math.min(mOrigin.y, mCurrentPosition.y), - Math.max(mOrigin.x, mCurrentPosition.x), - Math.max(mOrigin.y, mCurrentPosition.y)); - - mHost.showBand(mBounds); - } - - /** - * Ends band select by removing the overlay. - */ - private void endBandSelect() { - if (DEBUG) Log.d(TAG, "Ending band select."); - - // TODO: Currently when a band select operation ends outside - // of an item (e.g. in the empty area between items), - // getPositionNearestOrigin may return an unselected item. - // Since the point of this code is to establish the - // anchor point for subsequent range operations (SHIFT+CLICK) - // we really want to do a better job figuring out the last - // item selected (and nearest to the cursor). - int firstSelected = mModel.getPositionNearestOrigin(); - if (firstSelected != GridModel.NOT_SET - && mSelectionHelper.isSelected(mStableIds.getStableId(firstSelected))) { - // Establish the band selection point as range anchor. This - // allows touch and keyboard based selection activities - // to be based on the band selection anchor point. - mSelectionHelper.anchorRange(firstSelected); - } - - mSelectionHelper.mergeProvisionalSelection(); - reset(); - } - - /** - * @see RecyclerView.OnScrollListener - */ - private void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (!isActive()) { - return; - } - - // Adjust the y-coordinate of the origin the opposite number of pixels so that the - // origin remains in the same place relative to the view's items. - mOrigin.y -= dy; - resizeBand(); - } - - /** - * Provides functionality for BandController. Exists primarily to tests that are - * fully isolated from RecyclerView. - */ - public static abstract class BandHost extends ScrollerCallbacks { - public abstract void showBand(Rect rect); - public abstract void hideBand(); - public abstract void addOnScrollListener(RecyclerView.OnScrollListener listener); - public abstract void removeOnScrollListener(RecyclerView.OnScrollListener listener); - public abstract int getHeight(); - public abstract void invalidateView(); - public abstract Point createAbsolutePoint(Point relativePoint); - public abstract Rect getAbsoluteRectForChildViewAt(int index); - public abstract int getAdapterPositionAt(int index); - public abstract int getColumnCount(); - public abstract int getChildCount(); - public abstract int getVisibleChildCount(); - /** - * @return true if the item at adapter position is attached to a view. - */ - public abstract boolean hasView(int adapterPosition); - } -} diff --git a/src/com/android/documentsui/selection/ContentLock.java b/src/com/android/documentsui/selection/ContentLock.java deleted file mode 100644 index 387e70ccc..000000000 --- a/src/com/android/documentsui/selection/ContentLock.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkState; -import static com.android.documentsui.selection.Shared.DEBUG; -import static com.android.documentsui.selection.Shared.TAG; - -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import android.content.Loader; -import android.util.Log; - -/** - * ContentLock provides a mechanism to block content from reloading while selection - * activities like gesture and band selection are active. Clients using live data - * (data loaded, for example by a {@link Loader}), should route calls to load - * content through this lock using {@link ContentLock#runWhenUnlocked(Runnable)}. - */ -public final class ContentLock { - - private int mLocks = 0; - private @Nullable Runnable mCallback; - - /** - * Increment the block count by 1 - */ - @MainThread - public synchronized void block() { - mLocks++; - if (DEBUG) Log.v(TAG, "Incremented content lock count to " + mLocks + "."); - } - - /** - * Decrement the block count by 1; If no other object is trying to block and there exists some - * callback, that callback will be run - */ - @MainThread - public synchronized void unblock() { - checkState(mLocks > 0); - - mLocks--; - if (DEBUG) Log.v(TAG, "Decremented content lock count to " + mLocks + "."); - - if (mLocks == 0 && mCallback != null) { - mCallback.run(); - mCallback = null; - } - } - - /** - * Attempts to run the given Runnable if not-locked, or else the Runnable is set to be ran next - * (replacing any previous set Runnables). - */ - public synchronized void runWhenUnlocked(Runnable runnable) { - if (mLocks == 0) { - runnable.run(); - } else { - mCallback = runnable; - } - } - - /** - * Returns true if locked. - */ - synchronized boolean isLocked() { - return mLocks > 0; - } - - /** - * Allows other selection code to perform a precondition check asserting the state is locked. - */ - final void checkLocked() { - checkState(isLocked()); - } - - /** - * Allows other selection code to perform a precondition check asserting the state is unlocked. - */ - final void checkUnlocked() { - checkState(!isLocked()); - } -} diff --git a/src/com/android/documentsui/selection/DefaultBandHost.java b/src/com/android/documentsui/selection/DefaultBandHost.java deleted file mode 100644 index 5b24e8e92..000000000 --- a/src/com/android/documentsui/selection/DefaultBandHost.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.annotation.DrawableRes; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; - -import com.android.documentsui.selection.BandSelectionHelper.BandHost; - -/** - * RecyclerView backed {@link BandHost}. - */ -public final class DefaultBandHost extends BandHost { - - private final RecyclerView mRecView; - private final Drawable mBand; - - private boolean mIsOverlayShown; - - public DefaultBandHost(RecyclerView recView, @DrawableRes int bandOverlayId) { - - checkArgument(recView != null); - - mRecView = recView; - mBand = mRecView.getContext().getTheme().getDrawable(bandOverlayId); - - checkArgument(mBand != null); - } - - @Override - public int getAdapterPositionAt(int index) { - return mRecView.getChildAdapterPosition(mRecView.getChildAt(index)); - } - - @Override - public void addOnScrollListener(RecyclerView.OnScrollListener listener) { - mRecView.addOnScrollListener(listener); - } - - @Override - public void removeOnScrollListener(RecyclerView.OnScrollListener listener) { - mRecView.removeOnScrollListener(listener); - } - - @Override - public Point createAbsolutePoint(Point relativePoint) { - return new Point(relativePoint.x + mRecView.computeHorizontalScrollOffset(), - relativePoint.y + mRecView.computeVerticalScrollOffset()); - } - - @Override - public Rect getAbsoluteRectForChildViewAt(int index) { - final View child = mRecView.getChildAt(index); - final Rect childRect = new Rect(); - child.getHitRect(childRect); - childRect.left += mRecView.computeHorizontalScrollOffset(); - childRect.right += mRecView.computeHorizontalScrollOffset(); - childRect.top += mRecView.computeVerticalScrollOffset(); - childRect.bottom += mRecView.computeVerticalScrollOffset(); - return childRect; - } - - @Override - public int getChildCount() { - return mRecView.getAdapter().getItemCount(); - } - - @Override - public int getVisibleChildCount() { - return mRecView.getChildCount(); - } - - @Override - public int getColumnCount() { - RecyclerView.LayoutManager layoutManager = mRecView.getLayoutManager(); - if (layoutManager instanceof GridLayoutManager) { - return ((GridLayoutManager) layoutManager).getSpanCount(); - } - - // Otherwise, it is a list with 1 column. - return 1; - } - - @Override - public int getHeight() { - return mRecView.getHeight(); - } - - @Override - public void invalidateView() { - mRecView.invalidate(); - } - - @Override - public void runAtNextFrame(Runnable r) { - mRecView.postOnAnimation(r); - } - - @Override - public void removeCallback(Runnable r) { - mRecView.removeCallbacks(r); - } - - @Override - public void scrollBy(int dy) { - mRecView.scrollBy(0, dy); - } - - @Override - public void showBand(Rect rect) { - mBand.setBounds(rect); - - if (!mIsOverlayShown) { - mRecView.getOverlay().add(mBand); - } - } - - @Override - public void hideBand() { - mRecView.getOverlay().remove(mBand); - } - - @Override - public boolean hasView(int pos) { - return mRecView.findViewHolderForAdapterPosition(pos) != null; - } -}
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/DefaultBandPredicate.java b/src/com/android/documentsui/selection/DefaultBandPredicate.java deleted file mode 100644 index afbaa3361..000000000 --- a/src/com/android/documentsui/selection/DefaultBandPredicate.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.annotation.Nullable; -import android.view.MotionEvent; - -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; - -/** - * BandPredicate that consults ItemDetails, premitting band selection to start - * when the event is not in the drag region of the item. - */ -public final class DefaultBandPredicate extends BandPredicate { - - private final ItemDetailsLookup mDetailsLookup; - - public DefaultBandPredicate(ItemDetailsLookup detailsLookup) { - checkArgument(detailsLookup != null); - - mDetailsLookup = detailsLookup; - } - - @Override - public boolean canInitiate(MotionEvent e) { - @Nullable ItemDetails details = mDetailsLookup.getItemDetails(e); - return (details != null) - ? !details.inDragRegion(e) - : true; - } -} diff --git a/src/com/android/documentsui/selection/DefaultSelectionHelper.java b/src/com/android/documentsui/selection/DefaultSelectionHelper.java deleted file mode 100644 index 0bd45a46e..000000000 --- a/src/com/android/documentsui/selection/DefaultSelectionHelper.java +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.core.util.Preconditions.checkState; -import static com.android.documentsui.selection.Shared.DEBUG; -import static com.android.documentsui.selection.Shared.TAG; - -import androidx.annotation.IntDef; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nullable; - -/** - * {@link SelectionHelper} providing support traditional multi-item selection on top - * of {@link RecyclerView}. - * - * <p>The class supports running in a single-select mode, which can be enabled - * by passing {@colde #MODE_SINGLE} to the constructor. - */ -public final class DefaultSelectionHelper extends SelectionHelper { - - public static final int MODE_MULTIPLE = 0; - public static final int MODE_SINGLE = 1; - - @IntDef(flag = true, value = { - MODE_MULTIPLE, - MODE_SINGLE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SelectionMode {} - - private static final int RANGE_REGULAR = 0; - - /** - * "Provisional" selection represents a overlay on the primary selection. A provisional - * selection maybe be eventually added to the primary selection, or it may be abandoned. - * - * <p>E.g. BandController creates a provisional selection while a user is actively selecting - * items with the band. Provisionally selected items are considered to be selected in - * {@link Selection#contains(String)} and related methods. A provisional may be abandoned or - * applied by selection components (like - * {@link com.android.documentsui.selection.BandSelectionHelper}). - * - * <p>A provisional selection may intersect the primary selection, however clearing the - * provisional selection will not affect the primary selection where the two may intersect. - */ - private static final int RANGE_PROVISIONAL = 1; - @IntDef({ - RANGE_REGULAR, - RANGE_PROVISIONAL - }) - @Retention(RetentionPolicy.SOURCE) - @interface RangeType {} - - private final Selection mSelection = new Selection(); - private final List<SelectionObserver> mObservers = new ArrayList<>(1); - private final RecyclerView.Adapter<?> mAdapter; - private final StableIdProvider mStableIds; - private final SelectionPredicate mSelectionPredicate; - private final RecyclerView.AdapterDataObserver mAdapterObserver; - private final RangeCallbacks mRangeCallbacks; - private final boolean mSingleSelect; - - private @Nullable Range mRange; - - /** - * Creates a new instance. - * - * @param mode single or multiple selection mode. In single selection mode - * users can only select a single item. - * @param adapter {@link Adapter} for the RecyclerView this instance is coupled with. - * @param stableIds client supplied class providing access to stable ids. - * @param selectionPredicate A predicate allowing the client to disallow selection - * of individual elements. - */ - public DefaultSelectionHelper( - @SelectionMode int mode, - RecyclerView.Adapter<?> adapter, - StableIdProvider stableIds, - SelectionPredicate selectionPredicate) { - - checkArgument(mode == MODE_SINGLE || mode == MODE_MULTIPLE); - checkArgument(adapter != null); - checkArgument(stableIds != null); - checkArgument(selectionPredicate != null); - - mAdapter = adapter; - mStableIds = stableIds; - mSelectionPredicate = selectionPredicate; - mAdapterObserver = new AdapterObserver(); - mRangeCallbacks = new RangeCallbacks(); - - mSingleSelect = mode == MODE_SINGLE; - - mAdapter.registerAdapterDataObserver(mAdapterObserver); - } - - @Override - public void addObserver(SelectionObserver callback) { - checkArgument(callback != null); - mObservers.add(callback); - } - - @Override - public boolean hasSelection() { - return !mSelection.isEmpty(); - } - - @Override - public Selection getSelection() { - return mSelection; - } - - @Override - public void copySelection(Selection dest) { - dest.copyFrom(mSelection); - } - - @Override - public boolean isSelected(String id) { - return mSelection.contains(id); - } - - @Override - public void restoreSelection(Selection other) { - setItemsSelectedQuietly(other.mSelection, true); - // NOTE: We intentionally don't restore provisional selection. It's provisional. - notifySelectionRestored(); - } - - @Override - public boolean setItemsSelected(Iterable<String> ids, boolean selected) { - boolean changed = setItemsSelectedQuietly(ids, selected); - notifySelectionChanged(); - return changed; - } - - private boolean setItemsSelectedQuietly(Iterable<String> ids, boolean selected) { - boolean changed = false; - for (String id: ids) { - boolean itemChanged = selected - ? canSetState(id, true) && mSelection.add(id) - : canSetState(id, false) && mSelection.remove(id); - if (itemChanged) { - notifyItemStateChanged(id, selected); - } - changed |= itemChanged; - } - return changed; - } - - @Override - public void clearSelection() { - if (!hasSelection()) { - return; - } - - Selection prev = clearSelectionQuietly(); - notifySelectionCleared(prev); - notifySelectionChanged(); - } - - /** - * Clears the selection, without notifying selection listeners. - * Returns items in previous selection. Callers are responsible for notifying - * listeners about changes. - */ - private Selection clearSelectionQuietly() { - mRange = null; - - Selection prevSelection = new Selection(); - if (hasSelection()) { - copySelection(prevSelection); - mSelection.clear(); - } - - return prevSelection; - } - - @Override - public boolean select(String id) { - checkArgument(id != null); - - if (!mSelection.contains(id)) { - if (!canSetState(id, true)) { - if (DEBUG) Log.d(TAG, "Select cancelled by selection predicate test."); - return false; - } - - // Enforce single selection policy. - if (mSingleSelect && hasSelection()) { - Selection prev = clearSelectionQuietly(); - notifySelectionCleared(prev); - } - - mSelection.add(id); - notifyItemStateChanged(id, true); - notifySelectionChanged(); - - return true; - } - - return false; - } - - @Override - public boolean deselect(String id) { - checkArgument(id != null); - - if (mSelection.contains(id)) { - if (!canSetState(id, false)) { - if (DEBUG) Log.d(TAG, "Deselect cancelled by selection predicate test."); - return false; - } - mSelection.remove(id); - notifyItemStateChanged(id, false); - notifySelectionChanged(); - if (mSelection.isEmpty() && isRangeActive()) { - // if there's nothing in the selection and there is an active ranger it results - // in unexpected behavior when the user tries to start range selection: the item - // which the ranger 'thinks' is the already selected anchor becomes unselectable - endRange(); - } - return true; - } - - return false; - } - - @Override - public void startRange(int pos) { - select(mStableIds.getStableId(pos)); - anchorRange(pos); - } - - @Override - public void extendRange(int pos) { - extendRange(pos, RANGE_REGULAR); - } - - @Override - public void endRange() { - mRange = null; - // Clean up in case there was any leftover provisional selection - clearProvisionalSelection(); - } - - @Override - public void anchorRange(int position) { - checkArgument(position != RecyclerView.NO_POSITION); - - // TODO: I'm not a fan of silently ignoring calls. - // Determine if there are any cases where method can be called - // w/o item already being selected. Else, tighten up the ship - // and make this conditional guard into a proper precondition check. - if (mSelection.contains(mStableIds.getStableId(position))) { - mRange = new Range(mRangeCallbacks, position); - } - } - - @Override - public void extendProvisionalRange(int pos) { - extendRange(pos, RANGE_PROVISIONAL); - } - - /** - * Sets the end point for the current range selection, started by a call to - * {@link #startRange(int)}. This function should only be called when a range selection - * is active (see {@link #isRangeActive()}. Items in the range [anchor, end] will be - * selected or in provisional select, depending on the type supplied. Note that if the type is - * provisional selection, one should do {@link #mergeProvisionalSelection()} at some - * point before calling on {@link #endRange()}. - * - * @param pos The new end position for the selection range. - * @param type The type of selection the range should utilize. - */ - private void extendRange(int pos, @RangeType int type) { - checkState(isRangeActive(), "Range start point not set."); - - mRange.extendSelection(pos, type); - - // We're being lazy here notifying even when something might not have changed. - // To make this more correct, we'd need to update the Ranger class to return - // information about what has changed. - notifySelectionChanged(); - } - - @Override - public void setProvisionalSelection(Set<String> newSelection) { - Map<String, Boolean> delta = mSelection.setProvisionalSelection(newSelection); - for (Map.Entry<String, Boolean> entry: delta.entrySet()) { - notifyItemStateChanged(entry.getKey(), entry.getValue()); - } - - notifySelectionChanged(); - } - - @Override - public void mergeProvisionalSelection() { - mSelection.mergeProvisionalSelection(); - } - - @Override - public void clearProvisionalSelection() { - for (String id : mSelection.mProvisionalSelection) { - notifyItemStateChanged(id, false); - } - mSelection.clearProvisionalSelection(); - } - - @Override - public boolean isRangeActive() { - return mRange != null; - } - - private boolean canSetState(String id, boolean nextState) { - return mSelectionPredicate.canSetStateForId(id, nextState); - } - - private void onDataSetChanged() { - // Update the selection to remove any disappeared IDs. - mSelection.clearProvisionalSelection(); - mSelection.intersect(mStableIds.getStableIds()); - notifySelectionReset(); - - for (String id : mSelection) { - // If the underlying data set has changed, before restoring - // selection we must re-verify that it can be selected. - // Why? Because if the dataset has changed, then maybe the - // selectability of an item has changed. - if (!canSetState(id, true)) { - deselect(id); - } else { - int lastListener = mObservers.size() - 1; - for (int i = lastListener; i >= 0; i--) { - mObservers.get(i).onItemStateChanged(id, true); - } - } - } - notifySelectionChanged(); - } - - private void onDataSetItemRangeInserted(int startPosition, int itemCount) { - mSelection.clearProvisionalSelection(); - } - - private void onDataSetItemRangeRemoved(int startPosition, int itemCount) { - checkArgument(startPosition >= 0); - checkArgument(itemCount > 0); - - mSelection.clearProvisionalSelection(); - - // Remove any disappeared IDs from the selection. - // - // Ideally there could be a cheaper approach, checking - // each position individually, but since the source of - // truth for stable ids (StableIdProvider) probably - // it-self no-longer knows about the positions in question - // we fall back to the sledge hammer approach. - mSelection.intersect(mStableIds.getStableIds()); - } - - /** - * Notifies registered listeners when the selection status of a single item - * (identified by {@code position}) changes. - */ - private void notifyItemStateChanged(String id, boolean selected) { - checkArgument(id != null); - - int lastListenerIndex = mObservers.size() - 1; - for (int i = lastListenerIndex; i >= 0; i--) { - mObservers.get(i).onItemStateChanged(id, selected); - } - - int position = mStableIds.getPosition(id); - if (DEBUG) Log.d(TAG, "ITEM " + id + " CHANGED at pos: " + position); - - if (position >= 0) { - mAdapter.notifyItemChanged(position, SelectionHelper.SELECTION_CHANGED_MARKER); - } else { - Log.w(TAG, "Item change notification received for unknown item: " + id); - } - } - - private void notifySelectionCleared(Selection selection) { - for (String id: selection.mSelection) { - notifyItemStateChanged(id, false); - } - for (String id: selection.mProvisionalSelection) { - notifyItemStateChanged(id, false); - } - } - - /** - * Notifies registered listeners when the selection has changed. This - * notification should be sent only once a full series of changes - * is complete, e.g. clearingSelection, or updating the single - * selection from one item to another. - */ - private void notifySelectionChanged() { - int lastListenerIndex = mObservers.size() - 1; - for (int i = lastListenerIndex; i >= 0; i--) { - mObservers.get(i).onSelectionChanged(); - } - } - - private void notifySelectionRestored() { - int lastListenerIndex = mObservers.size() - 1; - for (int i = lastListenerIndex; i >= 0; i--) { - mObservers.get(i).onSelectionRestored(); - } - } - - private void notifySelectionReset() { - int lastListenerIndex = mObservers.size() - 1; - for (int i = lastListenerIndex; i >= 0; i--) { - mObservers.get(i).onSelectionReset(); - } - } - - private void updateForRange(int begin, int end, boolean selected, @RangeType int type) { - switch (type) { - case RANGE_REGULAR: - updateForRegularRange(begin, end, selected); - break; - case RANGE_PROVISIONAL: - updateForProvisionalRange(begin, end, selected); - break; - default: - throw new IllegalArgumentException("Invalid range type: " + type); - } - } - - private void updateForRegularRange(int begin, int end, boolean selected) { - checkArgument(end >= begin); - - for (int i = begin; i <= end; i++) { - String id = mStableIds.getStableId(i); - if (id == null) { - continue; - } - - if (selected) { - select(id); - } else { - deselect(id); - } - } - } - - private void updateForProvisionalRange(int begin, int end, boolean selected) { - checkArgument(end >= begin); - - for (int i = begin; i <= end; i++) { - String id = mStableIds.getStableId(i); - if (id == null) { - continue; - } - - boolean changedState = false; - if (selected) { - boolean canSelect = canSetState(id, true); - if (canSelect && !mSelection.mSelection.contains(id)) { - mSelection.mProvisionalSelection.add(id); - changedState = true; - } - } else { - mSelection.mProvisionalSelection.remove(id); - changedState = true; - } - - // Only notify item callbacks when something's state is actually changed in provisional - // selection. - if (changedState) { - notifyItemStateChanged(id, selected); - } - } - - notifySelectionChanged(); - } - - private final class AdapterObserver extends RecyclerView.AdapterDataObserver { - @Override - public void onChanged() { - onDataSetChanged(); - } - - @Override - public void onItemRangeChanged( - int startPosition, int itemCount, Object payload) { - // No change in position. Ignore, since we assume - // selection is a user driven activity. So changes - // in properties of items shouldn't result in a - // change of selection. - } - - @Override - public void onItemRangeInserted(int startPosition, int itemCount) { - onDataSetItemRangeInserted(startPosition, itemCount); - } - - @Override - public void onItemRangeRemoved(int startPosition, int itemCount) { - onDataSetItemRangeRemoved(startPosition, itemCount); - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - throw new UnsupportedOperationException(); - } - } - - private final class RangeCallbacks extends Range.Callbacks { - @Override - void updateForRange(int begin, int end, boolean selected, int type) { - switch (type) { - case RANGE_REGULAR: - updateForRegularRange(begin, end, selected); - break; - case RANGE_PROVISIONAL: - updateForProvisionalRange(begin, end, selected); - break; - default: - throw new IllegalArgumentException( - "Invalid range type: " + type); - } - } - } -} diff --git a/src/com/android/documentsui/selection/GestureRouter.java b/src/com/android/documentsui/selection/GestureRouter.java deleted file mode 100644 index be143c45b..000000000 --- a/src/com/android/documentsui/selection/GestureRouter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkNotNull; - -import androidx.annotation.Nullable; -import android.view.GestureDetector.OnDoubleTapListener; -import android.view.GestureDetector.OnGestureListener; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.MotionEvent; - -/** - * GestureRouter is responsible for routing gestures detected by a GestureDetector - * to registered handlers. The primary function is to divide events by tool-type - * allowing handlers to cleanly implement tool-type specific policies. - */ -public final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener> - implements OnGestureListener, OnDoubleTapListener { - - private final ToolHandlerRegistry<T> mDelegates; - - public GestureRouter(T defaultDelegate) { - checkNotNull(defaultDelegate); - mDelegates = new ToolHandlerRegistry<>(defaultDelegate); - } - - public GestureRouter() { - this((T) new SimpleOnGestureListener()); - } - - /** - * @param toolType - * @param delegate the delegate, or null to unregister. - */ - public void register(int toolType, @Nullable T delegate) { - mDelegates.set(toolType, delegate); - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return mDelegates.get(e).onSingleTapConfirmed(e); - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - return mDelegates.get(e).onDoubleTap(e); - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - return mDelegates.get(e).onDoubleTapEvent(e); - } - - @Override - public boolean onDown(MotionEvent e) { - return mDelegates.get(e).onDown(e); - } - - @Override - public void onShowPress(MotionEvent e) { - mDelegates.get(e).onShowPress(e); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - return mDelegates.get(e).onSingleTapUp(e); - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - return mDelegates.get(e2).onScroll(e1, e2, distanceX, distanceY); - } - - @Override - public void onLongPress(MotionEvent e) { - mDelegates.get(e).onLongPress(e); - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mDelegates.get(e2).onFling(e1, e2, velocityX, velocityY); - } -} diff --git a/src/com/android/documentsui/selection/GestureSelectionHelper.java b/src/com/android/documentsui/selection/GestureSelectionHelper.java deleted file mode 100644 index 21f9b09ef..000000000 --- a/src/com/android/documentsui/selection/GestureSelectionHelper.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.core.util.Preconditions.checkState; - -import android.graphics.Point; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; - -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; - -import com.android.documentsui.selection.ViewAutoScroller.ScrollHost; -import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks; - -/** - * GestureSelectionHelper provides logic that interprets a combination - * of motions and gestures in order to provide gesture driven selection support - * when used in conjunction with RecyclerView and other classes in the ReyclerView - * selection support package. - */ -public final class GestureSelectionHelper extends ScrollHost implements OnItemTouchListener { - - private static final String TAG = "GestureSelectionHelper"; - - private final SelectionHelper mSelectionMgr; - private final Runnable mScroller; - private final ViewDelegate mView; - private final ContentLock mLock; - private final ItemDetailsLookup mItemLookup; - - private int mLastTouchedItemPosition = RecyclerView.NO_POSITION; - private boolean mStarted = false; - private Point mLastInterceptedPoint; - - /** - * See {@link #create(SelectionHelper, RecyclerView, ContentLock)} for convenience - * method. - */ - @VisibleForTesting - GestureSelectionHelper( - SelectionHelper selectionHelper, - ViewDelegate view, - ContentLock lock, - ItemDetailsLookup itemLookup) { - - checkArgument(selectionHelper != null); - checkArgument(view != null); - checkArgument(lock != null); - checkArgument(itemLookup != null); - - mSelectionMgr = selectionHelper; - mView = view; - mLock = lock; - mItemLookup = itemLookup; - - mScroller = new ViewAutoScroller(this, mView); - } - - /** - * Explicitly kicks off a gesture multi-select. - * - * @return true if started. - */ - public void start() { - checkState(!mStarted); - // See: b/70518185. It appears start() is being called via onLongPress - // even though we never received an intial handleInterceptedDownEvent - // where we would usually initialize mLastStartedItemPos. - if (mLastTouchedItemPosition == RecyclerView.NO_POSITION) { - Log.w(TAG, "Illegal state. Can't start without valid mLastStartedItemPos."); - return; - } - - // Partner code in MotionInputHandler ensures items - // are selected and range established 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.checkUnlocked(); - - mStarted = true; - mLock.block(); - } - - @Override - public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) { - if (MotionEvents.isMouseEvent(e)) { - if (Shared.DEBUG) Log.w(TAG, "Unexpected Mouse event. Check configuration."); - } - - // TODO(b/109808552): It seems that mLastStartedItemPos should likely be set as a method - // parameter in start(). - if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { - if (mItemLookup.getItemDetails(e) != null) { - mLastTouchedItemPosition = mView.getItemUnder(e); - } - } - - // See handleTouch(MotionEvent) javadoc for explanation as to why this is correct. - return handleTouch(e); - } - - @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 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 boolean handleTouch(MotionEvent e) { - if (!mStarted) { - return false; - } - - switch (e.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - handleMoveEvent(e); - return true; - case MotionEvent.ACTION_UP: - handleUpEvent(); - return true; - case MotionEvent.ACTION_CANCEL: - handleCancelEvent(); - return true; - } - - return false; - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - } - - // Called when ACTION_UP event is to be handled. - // Essentially, since this means all gesture movement is over, reset everything and apply - // provisional selection. - private void handleUpEvent() { - mSelectionMgr.mergeProvisionalSelection(); - endSelection(); - if (mLastTouchedItemPosition != RecyclerView.NO_POSITION) { - mSelectionMgr.startRange(mLastTouchedItemPosition); - } - } - - // 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(); - } - - private void endSelection() { - checkState(mStarted); - - mLastTouchedItemPosition = RecyclerView.NO_POSITION; - mStarted = false; - mLock.unblock(); - } - - // 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(MotionEvent e) { - mLastInterceptedPoint = MotionEvents.getOrigin(e); - - int lastGlidedItemPos = mView.getLastGlidedItemPosition(e); - if (lastGlidedItemPos != RecyclerView.NO_POSITION) { - doGestureMultiSelect(lastGlidedItemPos); - } - scrollIfNecessary(); - } - - // It's possible for events to go over the top/bottom of the RecyclerView. - // We want to get a Y-coordinate within the RecyclerView so we can find the childView underneath - // correctly. - private static float getInboundY(float max, float y) { - if (y < 0f) { - return 0f; - } else if (y > max) { - return max; - } - return y; - } - - /* Given the end position, select everything in-between. - * @param endPos The adapter position of the end item. - */ - private void doGestureMultiSelect(int endPos) { - mSelectionMgr.extendProvisionalRange(endPos); - } - - private void scrollIfNecessary() { - mScroller.run(); - } - - @Override - public Point getCurrentPosition() { - return mLastInterceptedPoint; - } - - @Override - public int getViewHeight() { - return mView.getHeight(); - } - - @Override - public boolean isActive() { - return mStarted && mSelectionMgr.hasSelection(); - } - - /** - * Returns a new instance of GestureSelectionHelper, wrapping the - * RecyclerView in a test friendly wrapper. - */ - public static GestureSelectionHelper create( - SelectionHelper selectionMgr, - RecyclerView recycler, - ContentLock lock, - ItemDetailsLookup itemLookup) { - - return new GestureSelectionHelper( - selectionMgr, new RecyclerViewDelegate(recycler), lock, itemLookup); - } - - @VisibleForTesting - static abstract class ViewDelegate extends ScrollerCallbacks { - abstract int getHeight(); - - abstract int getItemUnder(MotionEvent e); - - abstract int getLastGlidedItemPosition(MotionEvent e); - } - - @VisibleForTesting - static final class RecyclerViewDelegate extends ViewDelegate { - - private final RecyclerView mView; - - RecyclerViewDelegate(RecyclerView view) { - checkArgument(view != null); - mView = view; - } - - @Override - int getHeight() { - return mView.getHeight(); - } - - @Override - int getItemUnder(MotionEvent e) { - View child = mView.findChildViewUnder(e.getX(), e.getY()); - return child != null - ? mView.getChildAdapterPosition(child) - : RecyclerView.NO_POSITION; - } - - @Override - int getLastGlidedItemPosition(MotionEvent e) { - // If user has moved his pointer to the bottom-right empty pane (ie. to the right of the - // last item of the recycler view), we would want to set that as the currentItemPos - View lastItem = mView.getLayoutManager() - .getChildAt(mView.getLayoutManager().getChildCount() - 1); - int direction = - mView.getContext().getResources().getConfiguration().getLayoutDirection(); - final boolean pastLastItem = isPastLastItem(lastItem.getTop(), - lastItem.getLeft(), - lastItem.getRight(), - e, - direction); - - // Since views get attached & detached from RecyclerView, - // {@link LayoutManager#getChildCount} can return a different number from the actual - // number - // of items in the adapter. Using the adapter is the for sure way to get the actual last - // item position. - final float inboundY = getInboundY(mView.getHeight(), e.getY()); - return (pastLastItem) ? mView.getAdapter().getItemCount() - 1 - : mView.getChildAdapterPosition(mView.findChildViewUnder(e.getX(), inboundY)); - } - - /* - * Check to see if MotionEvent if past a particular item, i.e. to the right or to the bottom - * of the item. - * For RTL, it would to be to the left or to the bottom of the item. - */ - @VisibleForTesting - static boolean isPastLastItem(int top, int left, int right, MotionEvent e, int direction) { - if (direction == View.LAYOUT_DIRECTION_LTR) { - return e.getX() > right && e.getY() > top; - } else { - return e.getX() < left && e.getY() > top; - } - } - - @Override - public void scrollBy(int dy) { - mView.scrollBy(0, dy); - } - - @Override - public void runAtNextFrame(Runnable r) { - mView.postOnAnimation(r); - } - - @Override - public void removeCallback(Runnable r) { - mView.removeCallbacks(r); - } - } -} diff --git a/src/com/android/documentsui/selection/GridModel.java b/src/com/android/documentsui/selection/GridModel.java deleted file mode 100644 index feca271ab..000000000 --- a/src/com/android/documentsui/selection/GridModel.java +++ /dev/null @@ -1,728 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static com.android.documentsui.base.SharedMinimal.DEBUG; - -import android.graphics.Point; -import android.graphics.Rect; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; - -import com.android.documentsui.selection.BandSelectionHelper.BandHost; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Provides a band selection item model for views within a RecyclerView. This class queries the - * RecyclerView to determine where its items are placed; then, once band selection is underway, - * it alerts listeners of which items are covered by the selections. - */ -final class GridModel { - - // Magical value indicating that a value has not been previously set. primitive null :) - static final int NOT_SET = -1; - - // Enum values used to determine the corner at which the origin is located within the - private static final int UPPER = 0x00; - private static final int LOWER = 0x01; - private static final int LEFT = 0x00; - private static final int RIGHT = 0x02; - private static final int UPPER_LEFT = UPPER | LEFT; - private static final int UPPER_RIGHT = UPPER | RIGHT; - private static final int LOWER_LEFT = LOWER | LEFT; - private static final int LOWER_RIGHT = LOWER | RIGHT; - - private final BandHost mHost; - private final StableIdProvider mStableIds; - private final SelectionPredicate mSelectionPredicate; - - private final List<SelectionObserver> 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, - // mColumns.get(5) would return an array of positions in that column. Within that array, the - // value for key y is the adapter position for the item whose y-offset is y. - private final SparseArray<SparseIntArray> mColumns = new SparseArray<>(); - - // List of limits along the x-axis (columns). - // This list is sorted from furthest left to furthest right. - private final List<Limits> mColumnBounds = new ArrayList<>(); - - // List of limits along the y-axis (rows). Note that this list only contains items which - // have been in the viewport. - private final List<Limits> mRowBounds = new ArrayList<>(); - - // The adapter positions which have been recorded so far. - private final SparseBooleanArray mKnownPositions = new SparseBooleanArray(); - - // Array passed to registered OnSelectionChangedListeners. One array is created and reused - // throughout the lifetime of the object. - private final Set<String> mSelection = new HashSet<>(); - - // The current pointer (in absolute positioning from the top of the view). - private Point mPointer = null; - - // The bounds of the band selection. - private RelativePoint mRelativeOrigin; - private RelativePoint mRelativePointer; - - private boolean mIsActive; - - // Tracks where the band select originated from. This is used to determine where selections - // should expand from when Shift+click is used. - private int mPositionNearestOrigin = NOT_SET; - - private final OnScrollListener mScrollListener; - - GridModel( - BandHost host, - StableIdProvider stableIds, - SelectionPredicate selectionPredicate) { - - mHost = host; - mStableIds = stableIds; - mSelectionPredicate = selectionPredicate; - - mScrollListener = new OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - GridModel.this.onScrolled(recyclerView, dx, dy); - } - }; - - mHost.addOnScrollListener(mScrollListener); - } - - /** - * Stops listening to the view's scrolls. Call this function before discarding a - * GridModel object to prevent memory leaks. - */ - void stopListening() { - mHost.removeOnScrollListener(mScrollListener); - } - - /** - * Start a band select operation at the given point. - * @param relativeOrigin The origin of the band select operation, relative to the viewport. - * For example, if the view is scrolled to the bottom, the top-left of the viewport - * would have a relative origin of (0, 0), even though its absolute point has a higher - * y-value. - */ - void startCapturing(Point relativeOrigin) { - recordVisibleChildren(); - if (isEmpty()) { - // The selection band logic works only if there is at least one visible child. - return; - } - - mIsActive = true; - mPointer = mHost.createAbsolutePoint(relativeOrigin); - mRelativeOrigin = new RelativePoint(mPointer); - mRelativePointer = new RelativePoint(mPointer); - computeCurrentSelection(); - notifySelectionChanged(); - } - - /** - * Ends the band selection. - */ - void stopCapturing() { - mIsActive = false; - } - - /** - * Resizes the selection by adjusting the pointer (i.e., the corner of the selection - * opposite the origin. - * @param relativePointer The pointer (opposite of the origin) of the band select operation, - * relative to the viewport. For example, if the view is scrolled to the bottom, the - * top-left of the viewport would have a relative origin of (0, 0), even though its - * absolute point has a higher y-value. - */ - @VisibleForTesting - void resizeSelection(Point relativePointer) { - mPointer = mHost.createAbsolutePoint(relativePointer); - updateModel(); - } - - /** - * @return The adapter position for the item nearest the origin corresponding to the latest - * band select operation, or NOT_SET if the selection did not cover any items. - */ - int getPositionNearestOrigin() { - return mPositionNearestOrigin; - } - - private void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (!mIsActive) { - return; - } - - mPointer.x += dx; - mPointer.y += dy; - recordVisibleChildren(); - updateModel(); - } - - /** - * Queries the view for all children and records their location metadata. - */ - private void recordVisibleChildren() { - for (int i = 0; i < mHost.getVisibleChildCount(); i++) { - int adapterPosition = mHost.getAdapterPositionAt(i); - // Sometimes the view is not attached, as we notify the multi selection manager - // synchronously, while views are attached asynchronously. As a result items which - // are in the adapter may not actually have a corresponding view (yet). - if (mHost.hasView(adapterPosition) && - mSelectionPredicate.canSetStateAtPosition(adapterPosition, true) && - !mKnownPositions.get(adapterPosition)) { - mKnownPositions.put(adapterPosition, true); - recordItemData(mHost.getAbsoluteRectForChildViewAt(i), adapterPosition); - } - } - } - - /** - * Checks if there are any recorded children. - */ - private boolean isEmpty() { - return mColumnBounds.size() == 0 || mRowBounds.size() == 0; - } - - /** - * Updates the limits lists and column map with the given item metadata. - * @param absoluteChildRect The absolute rectangle for the child view being processed. - * @param adapterPosition The position of the child view being processed. - */ - private void recordItemData(Rect absoluteChildRect, int adapterPosition) { - if (mColumnBounds.size() != mHost.getColumnCount()) { - // If not all x-limits have been recorded, record this one. - recordLimits( - mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right)); - } - - recordLimits(mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom)); - - SparseIntArray columnList = mColumns.get(absoluteChildRect.left); - if (columnList == null) { - columnList = new SparseIntArray(); - mColumns.put(absoluteChildRect.left, columnList); - } - columnList.put(absoluteChildRect.top, adapterPosition); - } - - /** - * Ensures limits exists within the sorted list limitsList, and adds it to the list if it - * does not exist. - */ - private void recordLimits(List<Limits> limitsList, Limits limits) { - int index = Collections.binarySearch(limitsList, limits); - if (index < 0) { - limitsList.add(~index, limits); - } - } - - /** - * Handles a moved pointer; this function determines whether the pointer movement resulted - * in a selection change and, if it has, notifies listeners of this change. - */ - private void updateModel() { - RelativePoint old = mRelativePointer; - mRelativePointer = new RelativePoint(mPointer); - if (old != null && mRelativePointer.equals(old)) { - return; - } - - computeCurrentSelection(); - notifySelectionChanged(); - } - - /** - * Computes the currently-selected items. - */ - private void computeCurrentSelection() { - if (areItemsCoveredByBand(mRelativePointer, mRelativeOrigin)) { - updateSelection(computeBounds()); - } else { - mSelection.clear(); - mPositionNearestOrigin = NOT_SET; - } - } - - /** - * Notifies all listeners of a selection change. Note that this function simply passes - * mSelection, so computeCurrentSelection() should be called before this - * function. - */ - private void notifySelectionChanged() { - for (SelectionObserver listener : mOnSelectionChangedListeners) { - listener.onSelectionChanged(mSelection); - } - } - - /** - * @param rect Rectangle including all covered items. - */ - private void updateSelection(Rect rect) { - int columnStart = - Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left)); - - checkArgument(columnStart >= 0, "Rect doesn't intesect any known column."); - - int columnEnd = columnStart; - - for (int i = columnStart; i < mColumnBounds.size() - && mColumnBounds.get(i).lowerLimit <= rect.right; i++) { - columnEnd = i; - } - - int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top)); - if (rowStart < 0) { - mPositionNearestOrigin = NOT_SET; - return; - } - - int rowEnd = rowStart; - for (int i = rowStart; i < mRowBounds.size() - && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) { - rowEnd = i; - } - - updateSelection(columnStart, columnEnd, rowStart, rowEnd); - } - - /** - * Computes the selection given the previously-computed start- and end-indices for each - * row and column. - */ - private void updateSelection( - int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) { - - if (DEBUG) { - Log.d(BandSelectionHelper.TAG, String.format( - "updateSelection: %d, %d, %d, %d", - columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex)); - } - - mSelection.clear(); - for (int column = columnStartIndex; column <= columnEndIndex; column++) { - SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit); - for (int row = rowStartIndex; row <= rowEndIndex; row++) { - // The default return value for SparseIntArray.get is 0, which is a valid - // position. Use a sentry value to prevent erroneously selecting item 0. - final int rowKey = mRowBounds.get(row).lowerLimit; - int position = items.get(rowKey, NOT_SET); - if (position != NOT_SET) { - String id = mStableIds.getStableId(position); - if (id != null) { - // The adapter inserts items for UI layout purposes that aren't - // associated with files. Those will have a null model ID. - // Don't select them. - if (canSelect(id)) { - mSelection.add(id); - } - } - if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex, - row, rowStartIndex, rowEndIndex)) { - // If this is the position nearest the origin, record it now so that it - // can be returned by endSelection() later. - mPositionNearestOrigin = position; - } - } - } - } - } - - private boolean canSelect(String id) { - return mSelectionPredicate.canSetStateForId(id, true); - } - - /** - * @return Returns true if the position is the nearest to the origin, or, in the case of the - * lower-right corner, whether it is possible that the position is the nearest to the - * origin. See comment below for reasoning for this special case. - */ - private boolean isPossiblePositionNearestOrigin(int columnIndex, int columnStartIndex, - int columnEndIndex, int rowIndex, int rowStartIndex, int rowEndIndex) { - int corner = computeCornerNearestOrigin(); - switch (corner) { - case UPPER_LEFT: - return columnIndex == columnStartIndex && rowIndex == rowStartIndex; - case UPPER_RIGHT: - return columnIndex == columnEndIndex && rowIndex == rowStartIndex; - case LOWER_LEFT: - return columnIndex == columnStartIndex && rowIndex == rowEndIndex; - case LOWER_RIGHT: - // Note that in some cases, the last row will not have as many items as there - // are columns (e.g., if there are 4 items and 3 columns, the second row will - // only have one item in the first column). This function is invoked for each - // position from left to right, so return true for any position in the bottom - // row and only the right-most position in the bottom row will be recorded. - return rowIndex == rowEndIndex; - default: - throw new RuntimeException("Invalid corner type."); - } - } - - /** - * Listener for changes in which items have been band selected. - */ - public static abstract class SelectionObserver { - abstract void onSelectionChanged(Set<String> updatedSelection); - } - - void addOnSelectionChangedListener(SelectionObserver listener) { - mOnSelectionChangedListeners.add(listener); - } - - /** - * Called when {@link BandSelectionHelper} is finished with a GridModel. - */ - void onDestroy() { - mOnSelectionChangedListeners.clear(); - stopListening(); - } - - /** - * Limits of a view item. For example, if an item's left side is at x-value 5 and its right side - * is at x-value 10, the limits would be from 5 to 10. Used to record the left- and right sides - * of item columns and the top- and bottom sides of item rows so that it can be determined - * whether the pointer is located within the bounds of an item. - */ - private static class Limits implements Comparable<Limits> { - int lowerLimit; - int upperLimit; - - Limits(int lowerLimit, int upperLimit) { - this.lowerLimit = lowerLimit; - this.upperLimit = upperLimit; - } - - @Override - public int compareTo(Limits other) { - return lowerLimit - other.lowerLimit; - } - - @Override - public int hashCode() { - return lowerLimit ^ upperLimit; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Limits)) { - return false; - } - - return ((Limits) other).lowerLimit == lowerLimit && - ((Limits) other).upperLimit == upperLimit; - } - - @Override - public String toString() { - return "(" + lowerLimit + ", " + upperLimit + ")"; - } - } - - /** - * The location of a coordinate relative to items. This class represents a general area of the - * view as it relates to band selection rather than an explicit point. For example, two - * different points within an item are considered to have the same "location" because band - * selection originating within the item would select the same items no matter which point - * was used. Same goes for points between items as well as those at the very beginning or end - * of the view. - * - * Tracking a coordinate (e.g., an x-value) as a CoordinateLocation instead of as an int has the - * advantage of tying the value to the Limits of items along that axis. This allows easy - * selection of items within those Limits as opposed to a search through every item to see if a - * given coordinate value falls within those Limits. - */ - private static class RelativeCoordinate - implements Comparable<RelativeCoordinate> { - /** - * Location describing points after the last known item. - */ - static final int AFTER_LAST_ITEM = 0; - - /** - * Location describing points before the first known item. - */ - static final int BEFORE_FIRST_ITEM = 1; - - /** - * Location describing points between two items. - */ - static final int BETWEEN_TWO_ITEMS = 2; - - /** - * Location describing points within the limits of one item. - */ - static final int WITHIN_LIMITS = 3; - - /** - * The type of this coordinate, which is one of AFTER_LAST_ITEM, BEFORE_FIRST_ITEM, - * BETWEEN_TWO_ITEMS, or WITHIN_LIMITS. - */ - final int type; - - /** - * The limits before the coordinate; only populated when type == WITHIN_LIMITS or type == - * BETWEEN_TWO_ITEMS. - */ - Limits limitsBeforeCoordinate; - - /** - * The limits after the coordinate; only populated when type == BETWEEN_TWO_ITEMS. - */ - Limits limitsAfterCoordinate; - - // Limits of the first known item; only populated when type == BEFORE_FIRST_ITEM. - Limits mFirstKnownItem; - // Limits of the last known item; only populated when type == AFTER_LAST_ITEM. - Limits mLastKnownItem; - - /** - * @param limitsList The sorted limits list for the coordinate type. If this - * CoordinateLocation is an x-value, mXLimitsList should be passed; otherwise, - * mYLimitsList should be pased. - * @param value The coordinate value. - */ - RelativeCoordinate(List<Limits> limitsList, int value) { - int index = Collections.binarySearch(limitsList, new Limits(value, value)); - - if (index >= 0) { - this.type = WITHIN_LIMITS; - this.limitsBeforeCoordinate = limitsList.get(index); - } else if (~index == 0) { - this.type = BEFORE_FIRST_ITEM; - this.mFirstKnownItem = limitsList.get(0); - } else if (~index == limitsList.size()) { - Limits lastLimits = limitsList.get(limitsList.size() - 1); - if (lastLimits.lowerLimit <= value && value <= lastLimits.upperLimit) { - this.type = WITHIN_LIMITS; - this.limitsBeforeCoordinate = lastLimits; - } else { - this.type = AFTER_LAST_ITEM; - this.mLastKnownItem = lastLimits; - } - } else { - Limits limitsBeforeIndex = limitsList.get(~index - 1); - if (limitsBeforeIndex.lowerLimit <= value - && value <= limitsBeforeIndex.upperLimit) { - this.type = WITHIN_LIMITS; - this.limitsBeforeCoordinate = limitsList.get(~index - 1); - } else { - this.type = BETWEEN_TWO_ITEMS; - this.limitsBeforeCoordinate = limitsList.get(~index - 1); - this.limitsAfterCoordinate = limitsList.get(~index); - } - } - } - - int toComparisonValue() { - if (type == BEFORE_FIRST_ITEM) { - return mFirstKnownItem.lowerLimit - 1; - } else if (type == AFTER_LAST_ITEM) { - return mLastKnownItem.upperLimit + 1; - } else if (type == BETWEEN_TWO_ITEMS) { - return limitsBeforeCoordinate.upperLimit + 1; - } else { - return limitsBeforeCoordinate.lowerLimit; - } - } - - @Override - public int hashCode() { - return mFirstKnownItem.lowerLimit - ^ mLastKnownItem.upperLimit - ^ limitsBeforeCoordinate.upperLimit - ^ limitsBeforeCoordinate.lowerLimit; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof RelativeCoordinate)) { - return false; - } - - RelativeCoordinate otherCoordinate = (RelativeCoordinate) other; - return toComparisonValue() == otherCoordinate.toComparisonValue(); - } - - @Override - public int compareTo(RelativeCoordinate other) { - return toComparisonValue() - other.toComparisonValue(); - } - } - - /** - * The location of a point relative to the Limits of nearby items; consists of both an x- and - * y-RelativeCoordinateLocation. - */ - private class RelativePoint { - final RelativeCoordinate xLocation; - final RelativeCoordinate yLocation; - - RelativePoint(Point point) { - this.xLocation = new RelativeCoordinate(mColumnBounds, point.x); - this.yLocation = new RelativeCoordinate(mRowBounds, point.y); - } - - @Override - public int hashCode() { - return xLocation.toComparisonValue() - ^ yLocation.toComparisonValue(); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof RelativePoint)) { - return false; - } - - RelativePoint otherPoint = (RelativePoint) other; - return xLocation.equals(otherPoint.xLocation) && yLocation.equals(otherPoint.yLocation); - } - } - - /** - * Generates a rectangle which contains the items selected by the pointer and origin. - * @return The rectangle, or null if no items were selected. - */ - private Rect computeBounds() { - Rect rect = new Rect(); - rect.left = getCoordinateValue( - min(mRelativeOrigin.xLocation, mRelativePointer.xLocation), - mColumnBounds, - true); - rect.right = getCoordinateValue( - max(mRelativeOrigin.xLocation, mRelativePointer.xLocation), - mColumnBounds, - false); - rect.top = getCoordinateValue( - min(mRelativeOrigin.yLocation, mRelativePointer.yLocation), - mRowBounds, - true); - rect.bottom = getCoordinateValue( - max(mRelativeOrigin.yLocation, mRelativePointer.yLocation), - mRowBounds, - false); - return rect; - } - - /** - * Computes the corner of the selection nearest the origin. - * @return - */ - private int computeCornerNearestOrigin() { - int cornerValue = 0; - - if (mRelativeOrigin.yLocation == - min(mRelativeOrigin.yLocation, mRelativePointer.yLocation)) { - cornerValue |= UPPER; - } else { - cornerValue |= LOWER; - } - - if (mRelativeOrigin.xLocation == - min(mRelativeOrigin.xLocation, mRelativePointer.xLocation)) { - cornerValue |= LEFT; - } else { - cornerValue |= RIGHT; - } - - return cornerValue; - } - - private RelativeCoordinate min(RelativeCoordinate first, RelativeCoordinate second) { - return first.compareTo(second) < 0 ? first : second; - } - - private RelativeCoordinate max(RelativeCoordinate first, RelativeCoordinate second) { - return first.compareTo(second) > 0 ? first : second; - } - - /** - * @return The absolute coordinate (i.e., the x- or y-value) of the given relative - * coordinate. - */ - private int getCoordinateValue( - RelativeCoordinate coordinate, List<Limits> limitsList, boolean isStartOfRange) { - - switch (coordinate.type) { - case RelativeCoordinate.BEFORE_FIRST_ITEM: - return limitsList.get(0).lowerLimit; - case RelativeCoordinate.AFTER_LAST_ITEM: - return limitsList.get(limitsList.size() - 1).upperLimit; - case RelativeCoordinate.BETWEEN_TWO_ITEMS: - if (isStartOfRange) { - return coordinate.limitsAfterCoordinate.lowerLimit; - } else { - return coordinate.limitsBeforeCoordinate.upperLimit; - } - case RelativeCoordinate.WITHIN_LIMITS: - return coordinate.limitsBeforeCoordinate.lowerLimit; - } - - throw new RuntimeException("Invalid coordinate value."); - } - - private boolean areItemsCoveredByBand( - RelativePoint first, RelativePoint second) { - - return doesCoordinateLocationCoverItems(first.xLocation, second.xLocation) && - doesCoordinateLocationCoverItems(first.yLocation, second.yLocation); - } - - private boolean doesCoordinateLocationCoverItems( - RelativeCoordinate pointerCoordinate, RelativeCoordinate originCoordinate) { - - if (pointerCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM && - originCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM) { - return false; - } - - if (pointerCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM && - originCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM) { - return false; - } - - if (pointerCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS && - originCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS && - pointerCoordinate.limitsBeforeCoordinate.equals( - originCoordinate.limitsBeforeCoordinate) && - pointerCoordinate.limitsAfterCoordinate.equals( - originCoordinate.limitsAfterCoordinate)) { - return false; - } - - return true; - } -}
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/ItemDetailsLookup.java b/src/com/android/documentsui/selection/ItemDetailsLookup.java deleted file mode 100644 index 8c0490192..000000000 --- a/src/com/android/documentsui/selection/ItemDetailsLookup.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; - -import javax.annotation.Nullable; - -/** - * Provides event handlers w/ access to details about documents details - * view items Documents in the UI (RecyclerView). - */ -public abstract class ItemDetailsLookup { - - /** @return true if there is an item under the finger/cursor. */ - public abstract boolean overItem(MotionEvent e); - - /** @return true if there is an item w/ a stable ID under the finger/cursor. */ - public abstract boolean overStableItem(MotionEvent e); - - /** - * @return true if the event is over an area that can be dragged via touch - * or via mouse. List items have a white area that is not draggable. - */ - public abstract boolean inItemDragRegion(MotionEvent e); - - /** - * @return true if the event is in the "selection hot spot" region. - * The hot spot region instantly selects in touch mode, vs launches. - */ - public abstract boolean inItemSelectRegion(MotionEvent e); - - /** - * @return the adapter position of the item under the finger/cursor. - */ - public abstract int getItemPosition(MotionEvent e); - - /** - * @return the DocumentDetails for the item under the event, or null. - */ - public abstract @Nullable ItemDetails getItemDetails(MotionEvent e); - - /** - * Abstract class providing helper classes with access to information about - * RecyclerView item associated with a MotionEvent. - */ - public static abstract class ItemDetails { - - public boolean hasPosition() { - return getPosition() > RecyclerView.NO_POSITION; - } - - public abstract int getPosition(); - - public boolean hasStableId() { - return getStableId() != null; - } - - public abstract @Nullable String getStableId(); - - /** - * @return The view type of this ViewHolder. - */ - public abstract int getItemViewType(); - - /** - * @return true if the event is in an area of the item that should be - * directly interpreted as a user wishing to select the item. This - * is useful for checkboxes and other UI affordances focused on enabling - * selection. - */ - public boolean inSelectionHotspot(MotionEvent e) { - return false; - } - - /** - * Events in the drag region will not be processed as selection events. This - * allows the client to implement custom handling for events related to drag - * and drop. - */ - public boolean inDragRegion(MotionEvent e) { - return false; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ItemDetails) { - return equals((ItemDetails) obj); - } - return false; - } - - private boolean equals(ItemDetails other) { - return this.getPosition() == other.getPosition() - && this.getStableId() == other.getStableId(); - } - - @Override - public int hashCode() { - return getPosition() >>> 8; - } - } -} diff --git a/src/com/android/documentsui/selection/MotionEvents.java b/src/com/android/documentsui/selection/MotionEvents.java deleted file mode 100644 index 1915c5ab6..000000000 --- a/src/com/android/documentsui/selection/MotionEvents.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import android.graphics.Point; -import android.view.KeyEvent; -import android.view.MotionEvent; - -/** - * Utility methods for working with {@link MotionEvent} instances. - */ -final class MotionEvents { - - private MotionEvents() {} - - static boolean isMouseEvent(MotionEvent e) { - return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE; - } - - static boolean isTouchEvent(MotionEvent e) { - return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER; - } - - static boolean isActionMove(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_MOVE; - } - - static boolean isActionDown(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_DOWN; - } - - static boolean isActionUp(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_UP; - } - - static boolean isActionPointerUp(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP; - } - - static boolean isActionPointerDown(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN; - } - - static boolean isActionCancel(MotionEvent e) { - return e.getActionMasked() == MotionEvent.ACTION_CANCEL; - } - - static Point getOrigin(MotionEvent e) { - return new Point((int) e.getX(), (int) e.getY()); - } - - static boolean isPrimaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_PRIMARY); - } - - public static boolean isSecondaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_SECONDARY); - } - - public static boolean isTertiaryButtonPressed(MotionEvent e) { - return e.isButtonPressed(MotionEvent.BUTTON_TERTIARY); - } - - static boolean isShiftKeyPressed(MotionEvent e) { - return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON); - } - - static boolean isCtrlKeyPressed(MotionEvent e) { - return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON); - } - - static boolean isAltKeyPressed(MotionEvent e) { - return hasBit(e.getMetaState(), KeyEvent.META_ALT_ON); - } - - public static boolean isTouchpadScroll(MotionEvent e) { - // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons - // returned. - return isMouseEvent(e) && isActionMove(e) && e.getButtonState() == 0; - } - - private static boolean hasBit(int metaState, int bit) { - return (metaState & bit) != 0; - } -} diff --git a/src/com/android/documentsui/selection/MotionInputHandler.java b/src/com/android/documentsui/selection/MotionInputHandler.java deleted file mode 100644 index f52a637e0..000000000 --- a/src/com/android/documentsui/selection/MotionInputHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.MotionEvent; - -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; - -/** - * Base class for handlers that can be registered w/ {@link GestureRouter}. - */ -public abstract class MotionInputHandler extends SimpleOnGestureListener { - - protected final SelectionHelper mSelectionHelper; - protected final ItemDetailsLookup mDetailsLookup; - - private final Callbacks mCallbacks; - - public MotionInputHandler( - SelectionHelper selectionHelper, - ItemDetailsLookup detailsLookup, - Callbacks callbacks) { - - checkArgument(selectionHelper != null); - checkArgument(detailsLookup != null); - checkArgument(callbacks != null); - - mSelectionHelper = selectionHelper; - mDetailsLookup = detailsLookup; - mCallbacks = callbacks; - } - - protected final boolean selectItem(ItemDetails details) { - checkArgument(details != null); - checkArgument(details.hasPosition()); - checkArgument(details.hasStableId()); - - if (mSelectionHelper.select(details.getStableId())) { - mSelectionHelper.anchorRange(details.getPosition()); - } - - // we set the focus on this doc so it will be the origin for keyboard events or shift+clicks - // if there is only a single item selected, otherwise clear focus - if (mSelectionHelper.getSelection().size() == 1) { - mCallbacks.focusItem(details); - } else { - mCallbacks.clearFocus(); - } - return true; - } - - protected final boolean focusItem(ItemDetails details) { - checkArgument(details != null); - checkArgument(details.hasStableId()); - - mSelectionHelper.clearSelection(); - mCallbacks.focusItem(details); - return true; - } - - protected final void extendSelectionRange(ItemDetails details) { - checkArgument(details.hasPosition()); - checkArgument(details.hasStableId()); - - mSelectionHelper.extendRange(details.getPosition()); - mCallbacks.focusItem(details); - } - - protected final boolean isRangeExtension(MotionEvent e) { - return MotionEvents.isShiftKeyPressed(e) && mSelectionHelper.isRangeActive(); - } - - protected boolean shouldClearSelection(MotionEvent e, ItemDetails item) { - return !MotionEvents.isCtrlKeyPressed(e) - && !item.inSelectionHotspot(e) - && !mSelectionHelper.isSelected(item.getStableId()); - } - - public static abstract class Callbacks { - public abstract void onPerformHapticFeedback(); - public void focusItem(ItemDetails item) {} - public void clearFocus() {} - } -} diff --git a/src/com/android/documentsui/selection/MouseInputHandler.java b/src/com/android/documentsui/selection/MouseInputHandler.java deleted file mode 100644 index 7ceb0f5fa..000000000 --- a/src/com/android/documentsui/selection/MouseInputHandler.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static com.android.documentsui.selection.Shared.DEBUG; -import static com.android.documentsui.selection.Shared.VERBOSE; - -import android.util.Log; -import android.view.MotionEvent; - -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.internal.widget.RecyclerView; - -import javax.annotation.Nullable; - -/** - * A MotionInputHandler that provides the high-level glue for mouse/stylus driven selection. This - * class works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper} - * to provide robust user drive selection support. - */ -public final class MouseInputHandler extends MotionInputHandler { - - private static final String TAG = "MouseInputDelegate"; - - private final Callbacks mCallbacks; - - // The event has been handled in onSingleTapUp - private boolean mHandledTapUp; - // true when the previous event has consumed a right click motion event - private boolean mHandledOnDown; - - public MouseInputHandler( - SelectionHelper selectionHelper, - ItemDetailsLookup detailsLookup, - Callbacks callbacks) { - - super(selectionHelper, detailsLookup, callbacks); - - mCallbacks = callbacks; - } - - @Override - public boolean onDown(MotionEvent e) { - if (VERBOSE) Log.v(TAG, "Delegated onDown event."); - if ((MotionEvents.isAltKeyPressed(e) && MotionEvents.isPrimaryButtonPressed(e)) - || MotionEvents.isSecondaryButtonPressed(e)) { - mHandledOnDown = true; - return onRightClick(e); - } - - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - // Don't scroll content window in response to mouse drag - // If it's two-finger trackpad scrolling, we want to scroll - return !MotionEvents.isTouchpadScroll(e2); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - // See b/27377794. Since we don't get a button state back from UP events, we have to - // explicitly save this state to know whether something was previously handled by - // DOWN events or not. - if (mHandledOnDown) { - if (VERBOSE) Log.v(TAG, "Ignoring onSingleTapUp, previously handled in onDown."); - mHandledOnDown = false; - return false; - } - - if (!mDetailsLookup.overStableItem(e)) { - if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection."); - mSelectionHelper.clearSelection(); - mCallbacks.clearFocus(); - return false; - } - - if (MotionEvents.isTertiaryButtonPressed(e)) { - if (DEBUG) Log.d(TAG, "Ignoring middle click"); - return false; - } - - ItemDetails item = mDetailsLookup.getItemDetails(e); - if (mSelectionHelper.hasSelection()) { - if (isRangeExtension(e)) { - extendSelectionRange(item); - } else { - if (shouldClearSelection(e, item)) { - mSelectionHelper.clearSelection(); - } - if (mSelectionHelper.isSelected(item.getStableId())) { - if (mSelectionHelper.deselect(item.getStableId())) { - mCallbacks.clearFocus(); - } - } else { - selectOrFocusItem(item, e); - } - } - mHandledTapUp = true; - return true; - } - - return false; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - if (mHandledTapUp) { - if (VERBOSE) Log.v(TAG, - "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp."); - mHandledTapUp = false; - return false; - } - - if (mSelectionHelper.hasSelection()) { - return false; // should have been handled by onSingleTapUp. - } - - if (!mDetailsLookup.overItem(e)) { - if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item."); - return false; - } - - if (MotionEvents.isTertiaryButtonPressed(e)) { - if (DEBUG) Log.d(TAG, "Ignoring middle click"); - return false; - } - - @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e); - if (item == null || !item.hasStableId()) { - Log.w(TAG, "Ignoring Confirmed Tap. No document details associated w/ event."); - return false; - } - - if (mCallbacks.hasFocusedItem() && MotionEvents.isShiftKeyPressed(e)) { - mSelectionHelper.startRange(mCallbacks.getFocusedPosition()); - mSelectionHelper.extendRange(item.getPosition()); - } else { - selectOrFocusItem(item, e); - } - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - mHandledTapUp = false; - - if (!mDetailsLookup.overStableItem(e)) { - if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-model-backed item."); - return false; - } - - if (MotionEvents.isTertiaryButtonPressed(e)) { - if (DEBUG) Log.d(TAG, "Ignoring middle click"); - return false; - } - - @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e); - if (item != null) { - return mCallbacks.onItemActivated(item, e); - } - - return false; - } - - private boolean onRightClick(MotionEvent e) { - if (mDetailsLookup.overStableItem(e)) { - @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e); - if (item != null && !mSelectionHelper.isSelected(item.getStableId())) { - mSelectionHelper.clearSelection(); - selectItem(item); - } - } - - // We always delegate final handling of the event, - // since the handler might want to show a context menu - // in an empty area or some other weirdo view. - return mCallbacks.onContextClick(e); - } - - private void selectOrFocusItem(ItemDetails item, MotionEvent e) { - if (item.inSelectionHotspot(e) || MotionEvents.isCtrlKeyPressed(e)) { - selectItem(item); - } else { - focusItem(item); - } - } - - public static abstract class Callbacks extends MotionInputHandler.Callbacks { - public abstract boolean onItemActivated(ItemDetails item, MotionEvent e); - public boolean onContextClick(MotionEvent e) { - return false; - } - public boolean hasFocusedItem() { - return false; - } - public int getFocusedPosition() { - return RecyclerView.NO_POSITION; - } - } - }
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/MutableSelection.java b/src/com/android/documentsui/selection/MutableSelection.java deleted file mode 100644 index 7939e14ef..000000000 --- a/src/com/android/documentsui/selection/MutableSelection.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -/** - * Subclass of Selection exposing public support for mutating the underlying selection data. - * This is useful for clients of {@link SelectionHelper} that wish to manipulate - * a copy of selection data obtained via {@link SelectionHelper#copySelection(Selection)}. - */ -public final class MutableSelection extends Selection { - - @Override - public boolean add(String id) { - return super.add(id); - } - - @Override - public boolean remove(String id) { - return super.remove(id); - } - - @Override - public void clear() { - super.clear(); - } -} diff --git a/src/com/android/documentsui/selection/Range.java b/src/com/android/documentsui/selection/Range.java deleted file mode 100644 index 001e92d28..000000000 --- a/src/com/android/documentsui/selection/Range.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; -import static com.android.documentsui.selection.Shared.DEBUG; -import static com.android.documentsui.selection.Shared.TAG; - -import android.util.Log; - -import com.android.documentsui.selection.DefaultSelectionHelper.RangeType; - -/** - * Class providing support for managing range selections. - */ -final class Range { - - private final Callbacks mCallbacks; - private final int mBegin; - private int mEnd = NO_POSITION; - - public Range(Callbacks callbacks, int begin) { - if (DEBUG) Log.d(TAG, "New Ranger created beginning @ " + begin); - mCallbacks = callbacks; - mBegin = begin; - } - - void extendSelection(int position, @RangeType int type) { - checkArgument(position != NO_POSITION, "Position cannot be NO_POSITION."); - - if (mEnd == NO_POSITION || mEnd == mBegin) { - // Reset mEnd so it can be established in establishRange. - mEnd = NO_POSITION; - establishRange(position, type); - } else { - reviseRange(position, type); - } - } - - private void establishRange(int position, @RangeType int type) { - checkArgument(mEnd == NO_POSITION, "End has already been set."); - - if (position == mBegin) { - mEnd = position; - } - - if (position > mBegin) { - updateRange(mBegin + 1, position, true, type); - } else if (position < mBegin) { - updateRange(position, mBegin - 1, true, type); - } - - mEnd = position; - } - - private void reviseRange(int position, @RangeType int type) { - checkArgument(mEnd != NO_POSITION, "End must already be set."); - checkArgument(mBegin != mEnd, "Beging and end point to same position."); - - if (position == mEnd) { - if (DEBUG) Log.v(TAG, "Ignoring no-op revision for range: " + this); - } - - if (mEnd > mBegin) { - reviseAscendingRange(position, type); - } else if (mEnd < mBegin) { - reviseDescendingRange(position, type); - } - // the "else" case is covered by checkState at beginning of method. - - mEnd = position; - } - - /** - * Updates an existing ascending selection. - * @param position - */ - private void reviseAscendingRange(int position, @RangeType int type) { - // Reducing or reversing the range.... - if (position < mEnd) { - if (position < mBegin) { - updateRange(mBegin + 1, mEnd, false, type); - updateRange(position, mBegin -1, true, type); - } else { - updateRange(position + 1, mEnd, false, type); - } - } - - // Extending the range... - else if (position > mEnd) { - updateRange(mEnd + 1, position, true, type); - } - } - - private void reviseDescendingRange(int position, @RangeType int type) { - // Reducing or reversing the range.... - if (position > mEnd) { - if (position > mBegin) { - updateRange(mEnd, mBegin - 1, false, type); - updateRange(mBegin + 1, position, true, type); - } else { - updateRange(mEnd, position - 1, false, type); - } - } - - // Extending the range... - else if (position < mEnd) { - updateRange(position, mEnd - 1, true, type); - } - } - - /** - * Try to set selection state for all elements in range. Not that callbacks can cancel - * selection of specific items, so some or even all items may not reflect the desired state - * after the update is complete. - * - * @param begin Adapter position for range start (inclusive). - * @param end Adapter position for range end (inclusive). - * @param selected New selection state. - */ - private void updateRange(int begin, int end, boolean selected, @RangeType int type) { - mCallbacks.updateForRange(begin, end, selected, type); - } - - @Override - public String toString() { - return "Range{begin=" + mBegin + ", end=" + mEnd + "}"; - } - - /* - * @see {@link DefaultSelectionHelper#updateForRange(int, int , boolean, int)}. - */ - static abstract class Callbacks { - abstract void updateForRange( - int begin, int end, boolean selected, @RangeType int type); - } -} diff --git a/src/com/android/documentsui/selection/Selection.java b/src/com/android/documentsui/selection/Selection.java deleted file mode 100644 index d929bd013..000000000 --- a/src/com/android/documentsui/selection/Selection.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import android.os.Parcel; -import android.os.Parcelable; -import androidx.annotation.VisibleForTesting; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nullable; - -/** - * Object representing the current selection and provisional selection. Provides read only public - * access, and private write access. - * <p> - * This class tracks selected items by managing two sets: - * - * <li>primary selection - * - * Primary selection consists of items tapped by a user or by lassoed by band select operation. - * - * <li>provisional selection - * - * Provisional selections are selections which have been temporarily created - * by an in-progress band select or gesture selection. Once the user releases the mouse button - * or lifts their finger the corresponding provisional selection should be converted into - * primary selection. - * - * <p>The total selection is the combination of - * both the core selection and the provisional selection. Tracking both separately is necessary to - * ensure that items in the core selection are not "erased" from the core selection when they - * are temporarily included in a secondary selection (like band selection). - */ -public class Selection implements Iterable<String>, Parcelable { - - final Set<String> mSelection; - final Set<String> mProvisionalSelection; - - public Selection() { - mSelection = new HashSet<>(); - mProvisionalSelection = new HashSet<>(); - } - - /** - * Used by CREATOR. - */ - private Selection(Set<String> selection) { - mSelection = selection; - mProvisionalSelection = new HashSet<>(); - } - - /** - * @param id - * @return true if the position is currently selected. - */ - public boolean contains(@Nullable String id) { - return mSelection.contains(id) || mProvisionalSelection.contains(id); - } - - /** - * Returns an {@link Iterator} that iterators over the selection, *excluding* - * any provisional selection. - * - * {@inheritDoc} - */ - @Override - public Iterator<String> iterator() { - return mSelection.iterator(); - } - - /** - * @return size of the selection including both final and provisional selected items. - */ - public int size() { - return mSelection.size() + mProvisionalSelection.size(); - } - - /** - * @return true if the selection is empty. - */ - public boolean isEmpty() { - return mSelection.isEmpty() && mProvisionalSelection.isEmpty(); - } - - /** - * 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<String, Boolean> setProvisionalSelection(Set<String> newSelection) { - Map<String, Boolean> delta = new HashMap<>(); - - for (String id: mProvisionalSelection) { - // Mark each item that used to be in the provisional selection - // but is not in the new provisional selection. - if (!newSelection.contains(id) && !mSelection.contains(id)) { - delta.put(id, false); - } - } - - for (String id: mSelection) { - // Mark each item that used to be in the selection but is unsaved and not in the new - // provisional selection. - if (!newSelection.contains(id)) { - delta.put(id, false); - } - } - - for (String id: newSelection) { - // Mark each item that was not previously in the selection but is in the new - // provisional selection. - if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) { - delta.put(id, true); - } - } - - // Now, iterate through the changes and actually add/remove them to/from the current - // selection. This could not be done in the previous loops because changing the size of - // the selection mid-iteration changes iteration order erroneously. - for (Map.Entry<String, Boolean> entry: delta.entrySet()) { - String id = entry.getKey(); - if (entry.getValue()) { - mProvisionalSelection.add(id); - } else { - mProvisionalSelection.remove(id); - } - } - - return delta; - } - - /** - * Saves the existing provisional selection. Once the provisional selection is saved, - * subsequent provisional selections which are different from this existing one cannot - * cause items in this existing provisional selection to become deselected. - */ - @VisibleForTesting - protected void mergeProvisionalSelection() { - mSelection.addAll(mProvisionalSelection); - mProvisionalSelection.clear(); - } - - /** - * Abandons the existing provisional selection so that all items provisionally selected are - * now deselected. - */ - @VisibleForTesting - void clearProvisionalSelection() { - mProvisionalSelection.clear(); - } - - /** - * Adds a new item to the primary selection. - * - * @return true if the operation resulted in a modification to the selection. - */ - boolean add(String id) { - if (mSelection.contains(id)) { - return false; - } - - mSelection.add(id); - return true; - } - - /** - * Removes an item from the primary selection. - * - * @return true if the operation resulted in a modification to the selection. - */ - boolean remove(String id) { - if (!mSelection.contains(id)) { - return false; - } - - mSelection.remove(id); - return true; - } - - /** - * Clears the primary selection. The provisional selection, if any, is unaffected. - */ - void clear() { - mSelection.clear(); - } - - /** - * Trims this selection to be the intersection of itself and {@code ids}. - */ - void intersect(Collection<String> ids) { - checkArgument(ids != null); - - mSelection.retainAll(ids); - mProvisionalSelection.retainAll(ids); - } - - /** - * Clones primary and provisional selection from supplied {@link Selection}. - * Does not copy active range data. - */ - @VisibleForTesting - void copyFrom(Selection source) { - mSelection.clear(); - mSelection.addAll(source.mSelection); - - mProvisionalSelection.clear(); - mProvisionalSelection.addAll(source.mProvisionalSelection); - } - - @Override - public String toString() { - if (size() <= 0) { - return "size=0, items=[]"; - } - - StringBuilder buffer = new StringBuilder(size() * 28); - buffer.append("Selection{") - .append("primary{size=" + mSelection.size()) - .append(", entries=" + mSelection) - .append("}, provisional{size=" + mProvisionalSelection.size()) - .append(", entries=" + mProvisionalSelection) - .append("}}"); - return buffer.toString(); - } - - @Override - public int hashCode() { - return mSelection.hashCode() ^ mProvisionalSelection.hashCode(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - return other instanceof Selection - ? equals((Selection) other) - : false; - } - - private boolean equals(Selection other) { - return mSelection.equals(((Selection) other).mSelection) && - mProvisionalSelection.equals(((Selection) other).mProvisionalSelection); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStringList(new ArrayList<>(mSelection)); - // We don't include provisional selection since it is - // typically coupled to some other runtime state (like a band). - } - - public static final ClassLoaderCreator<Selection> CREATOR = - new ClassLoaderCreator<Selection>() { - @Override - public Selection createFromParcel(Parcel in) { - return createFromParcel(in, null); - } - - @Override - public Selection createFromParcel(Parcel in, ClassLoader loader) { - ArrayList<String> selected = new ArrayList<>(); - in.readStringList(selected); - - return new Selection(new HashSet<>(selected)); - } - - @Override - public Selection[] newArray(int size) { - return new Selection[size]; - } - }; -}
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/SelectionHelper.java b/src/com/android/documentsui/selection/SelectionHelper.java deleted file mode 100644 index bd8b34eaa..000000000 --- a/src/com/android/documentsui/selection/SelectionHelper.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import java.util.List; -import java.util.Set; - -/** - * SelectionManager provides support for managing selection within a RecyclerView instance. - * - * @see DefaultSelectionHelper for details on instantiation. - */ -public abstract class SelectionHelper { - - /** - * This value is included in the payload when SelectionHelper implementations - * notify RecyclerView of changes. Clients can look for this in - * {@code onBindViewHolder} to know if the bind event is occurring in response - * to a selection state change. - */ - public static final String SELECTION_CHANGED_MARKER = "Selection-Changed"; - - /** - * Adds {@code observe} to be notified when changes to selection occur. - * - * @param observer - */ - public abstract void addObserver(SelectionObserver observer); - - public abstract boolean hasSelection(); - - /** - * Returns a Selection object that provides a live view on the current selection. - * - * @see #copySelection(Selection) on how to get a snapshot - * of the selection that will not reflect future changes - * to selection. - * - * @return The current selection. - */ - public abstract Selection getSelection(); - - /** - * Updates {@code dest} to reflect the current selection. - * @param dest - */ - public abstract void copySelection(Selection dest); - - /** - * @return true if the item specified by its id is selected. Shorthand for - * {@code getSelection().contains(String)}. - */ - public abstract boolean isSelected(String id); - - /** - * Restores the selected state of specified items. Used in cases such as restore the selection - * after rotation etc. Provisional selection, being provisional 'n all, isn't restored. - */ - public abstract void restoreSelection(Selection other); - - /** - * Sets the selected state of the specified items. Note that the callback will NOT - * be consulted to see if an item can be selected. - * - * @param ids - * @param selected - * @return - */ - public abstract boolean setItemsSelected(Iterable<String> ids, boolean selected); - - /** - * Clears the selection and notifies (if something changes). - */ - public abstract void clearSelection(); - - /** - * Attempts to select an item. - * - * @return true if the item was selected. False if the item was not selected, or was - * was already selected prior to the method being called. - */ - public abstract boolean select(String itemId); - - /** - * Attempts to deselect an item. - * - * @return true if the item was deselected. False if the item was not deselected, or was - * was already deselected prior to the method being called. - */ - public abstract boolean deselect(String itemId); - - /** - * Starts a range selection. If a range selection is already active, this will start a new range - * selection (which will reset the range anchor). - * - * @param pos The anchor position for the selection range. - */ - public abstract void startRange(int pos); - - /** - * Sets the end point for the active range selection. - * - * <p>This function should only be called when a range selection is active - * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be - * selected. - * - * @param pos The new end position for the selection range. - * @param type The type of selection the range should utilize. - * - * @throws IllegalStateException if a range selection is not active. Range selection - * must have been started by a call to {@link #startRange(int)}. - */ - public abstract void extendRange(int pos); - - /** - * Stops an in-progress range selection. All selection done with - * {@link #extendProvisionalRange(int)} will be lost if - * {@link Selection#mergeProvisionalSelection()} is not called beforehand. - */ - public abstract void endRange(); - - /** - * @return Whether or not there is a current range selection active. - */ - public abstract boolean isRangeActive(); - - /** - * Sets the magic location at which a selection range begins (the selection anchor). This value - * is consulted when determining how to extend, and modify selection ranges. Calling this when a - * range selection is active will reset the range selection. - */ - public abstract void anchorRange(int position); - - /** - * @param pos - */ - // TODO: This is smelly. Maybe this type of logic needs to move into range selection, - // then selection manager can have a startProvisionalRange and startRange. Or - // maybe ranges always start life as provisional. - public abstract void extendProvisionalRange(int pos); - - /** - * @param newSelection - */ - public abstract void setProvisionalSelection(Set<String> newSelection); - - /** - * - */ - public abstract void clearProvisionalSelection(); - - public abstract void mergeProvisionalSelection(); - - /** - * Observer interface providing access to information about Selection state changes. - */ - public static abstract class SelectionObserver { - - /** - * Called when state of an item has been changed. - */ - public void onItemStateChanged(String id, boolean selected) {} - - /** - * Called when the underlying data set has change. After this method is called - * the selection manager will attempt traverse the existing selection, - * calling {@link #onItemStateChanged(String, boolean)} for each selected item, - * and deselecting any items that cannot be selected given the updated dataset. - */ - public void onSelectionReset() {} - - /** - * Called immediately after completion of any set of changes, excluding - * those resulting in calls to {@link #onSelectionReset()} and - * {@link #onSelectionRestored()}. - */ - public void onSelectionChanged() {} - - /** - * Called immediately after selection is restored. - * {@link #onItemStateChanged(String, boolean)} will not be called - * for individual items in the selection. - */ - public void onSelectionRestored() {} - } - - /** - * Facilitates the use of stable ids. - */ - public static abstract class StableIdProvider { - /** - * @return The model ID of the item at the given adapter position. - */ - public abstract String getStableId(int position); - - /** - * @return the position of a stable ID, or RecyclerView.NO_POSITION. - */ - public abstract int getPosition(String id); - - /** - * @return A list of all known stable IDs. - */ - public abstract List<String> getStableIds(); - } - - /** - * Implement SelectionPredicate to control when items can be selected or unselected. - */ - public static abstract class SelectionPredicate { - - /** @return true if the item at {@code id} can be set to {@code nextState}. */ - public abstract boolean canSetStateForId(String id, boolean nextState); - - /** @return true if the item at {@code id} can be set to {@code nextState}. */ - public abstract boolean canSetStateAtPosition(int position, boolean nextState); - - /** @return true if more than a single item can be selected. */ - public boolean canSelectMultiple() { - return true; - } - } -} diff --git a/src/com/android/documentsui/selection/Shared.java b/src/com/android/documentsui/selection/Shared.java deleted file mode 100644 index bf30919dc..000000000 --- a/src/com/android/documentsui/selection/Shared.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -/** - * Shared constants used in this and descendant packages. - */ -public final class Shared { - - public static final String TAG = "SelectionHelper"; - public static final boolean DEBUG = false; - public static final boolean VERBOSE = false; - - private Shared() {} -} diff --git a/src/com/android/documentsui/selection/ToolHandlerRegistry.java b/src/com/android/documentsui/selection/ToolHandlerRegistry.java deleted file mode 100644 index 4a6aa16a1..000000000 --- a/src/com/android/documentsui/selection/ToolHandlerRegistry.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.core.util.Preconditions.checkState; - -import androidx.annotation.Nullable; -import android.view.MotionEvent; - -import java.util.Arrays; -import java.util.List; - -/** - * Registry for tool specific event handler. - */ -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 sNumInputTypes = MotionEvent.TOOL_TYPE_ERASER + 1; - - private final List<T> mHandlers = Arrays.asList(null, null, null, null, null); - private final T mDefault; - - ToolHandlerRegistry(T defaultDelegate) { - checkArgument(defaultDelegate != null); - mDefault = defaultDelegate; - - // Initialize all values to null. - for (int i = 0; i < sNumInputTypes; i++) { - mHandlers.set(i, null); - } - } - - /** - * @param toolType - * @param delegate the delegate, or null to unregister. - * @throws IllegalStateException if an tooltype handler is already registered. - */ - void set(int toolType, @Nullable T delegate) { - checkArgument(toolType >= 0 && toolType <= MotionEvent.TOOL_TYPE_ERASER); - checkState(mHandlers.get(toolType) == null); - - mHandlers.set(toolType, delegate); - } - - T get(MotionEvent e) { - T d = mHandlers.get(e.getToolType(0)); - return d != null ? d : mDefault; - } -} diff --git a/src/com/android/documentsui/selection/TouchEventRouter.java b/src/com/android/documentsui/selection/TouchEventRouter.java deleted file mode 100644 index 232b56067..000000000 --- a/src/com/android/documentsui/selection/TouchEventRouter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; -import android.view.GestureDetector; -import android.view.MotionEvent; - -/** - * 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. - */ -public final class TouchEventRouter implements OnItemTouchListener { - - private final GestureDetector mDetector; - private final ToolHandlerRegistry<OnItemTouchListener> mDelegates; - - public TouchEventRouter(GestureDetector detector, OnItemTouchListener defaultDelegate) { - checkArgument(detector != null); - checkArgument(defaultDelegate != null); - - mDetector = detector; - mDelegates = new ToolHandlerRegistry<>(defaultDelegate); - } - - public TouchEventRouter(GestureDetector detector) { - this( - detector, - // Default listener does nothing. - new OnItemTouchListener() { - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { - return false; - } - - @Override - public void onTouchEvent(RecyclerView rv, 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}. - */ - public void register(int toolType, OnItemTouchListener delegate) { - checkArgument(delegate != null); - mDelegates.set(toolType, delegate); - } - - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, 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(RecyclerView rv, 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) {} -} diff --git a/src/com/android/documentsui/selection/TouchInputHandler.java b/src/com/android/documentsui/selection/TouchInputHandler.java deleted file mode 100644 index ffb6bce8d..000000000 --- a/src/com/android/documentsui/selection/TouchInputHandler.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static com.android.documentsui.base.SharedMinimal.DEBUG; - -import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; - -/** - * A MotionInputHandler that provides the high-level glue for touch driven selection. This class - * works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper} to - * provide robust user drive selection support. - */ -public final class TouchInputHandler extends MotionInputHandler { - private static final String TAG = "TouchInputDelegate"; - - private final SelectionPredicate mSelectionPredicate; - private final Callbacks mCallbacks; - private final Runnable mGestureKicker; - - public TouchInputHandler( - SelectionHelper selectionHelper, - ItemDetailsLookup detailsLookup, - SelectionPredicate selectionPredicate, - Runnable gestureKicker, - Callbacks callbacks) { - - super(selectionHelper, detailsLookup, callbacks); - - mSelectionPredicate = selectionPredicate; - mGestureKicker = gestureKicker; - mCallbacks = callbacks; - } - - public TouchInputHandler( - SelectionHelper selectionHelper, - ItemDetailsLookup detailsLookup, - SelectionPredicate selectionPredicate, - GestureSelectionHelper gestureHelper, - Callbacks callbacks) { - - this( - selectionHelper, - detailsLookup, - selectionPredicate, - new Runnable() { - @Override - public void run() { - gestureHelper.start(); - } - }, - callbacks); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - if (!mDetailsLookup.overStableItem(e)) { - if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection."); - mSelectionHelper.clearSelection(); - return false; - } - - ItemDetails item = mDetailsLookup.getItemDetails(e); - if (mSelectionHelper.hasSelection()) { - if (isRangeExtension(e)) { - extendSelectionRange(item); - } else if (mSelectionHelper.isSelected(item.getStableId())) { - mSelectionHelper.deselect(item.getStableId()); - } else { - selectItem(item); - } - - return true; - } - - // Touch events select if they occur in the selection hotspot, - // otherwise they activate. - return item.inSelectionHotspot(e) - ? selectItem(item) - : mCallbacks.onItemActivated(item, e); - } - - @Override - public final void onLongPress(MotionEvent e) { - if (!mDetailsLookup.overStableItem(e)) { - if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item."); - return; - } - - ItemDetails item = mDetailsLookup.getItemDetails(e); - boolean handled = false; - - if (isRangeExtension(e)) { - extendSelectionRange(item); - handled = true; - } else { - if (!mSelectionHelper.isSelected(item.getStableId()) - && mSelectionPredicate.canSetStateForId(item.getStableId(), true)) { - // If we cannot select it, we didn't apply anchoring - therefore should not - // start gesture selection - if (selectItem(item)) { - // And finally if the item was selected && we can select multiple - // we kick off gesture selection. - if (mSelectionPredicate.canSelectMultiple()) { - mGestureKicker.run(); - } - handled = true; - } - } else { - // We only initiate drag and drop on long press for touch to allow regular - // touch-based scrolling - // mTouchDragListener.accept(e); - mCallbacks.onDragInitiated(e); - handled = true; - } - } - - if (handled) { - mCallbacks.onPerformHapticFeedback(); - } - } - - public static abstract class Callbacks extends MotionInputHandler.Callbacks { - public abstract boolean onItemActivated(ItemDetails item, MotionEvent e); - public boolean onDragInitiated(MotionEvent e) { - return false; - } - } -} diff --git a/src/com/android/documentsui/selection/demo/DemoDetailsLookup.java b/src/com/android/documentsui/selection/demo/DemoDetailsLookup.java deleted file mode 100644 index c49014e8a..000000000 --- a/src/com/android/documentsui/selection/demo/DemoDetailsLookup.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.view.MotionEvent; -import android.view.View; - -import com.android.documentsui.selection.ItemDetailsLookup; - -/** - * Access to details of an item associated with a {@link MotionEvent} instance. - */ -final class DemoDetailsLookup extends ItemDetailsLookup { - - private final RecyclerView mRecView; - - public DemoDetailsLookup(RecyclerView view) { - mRecView = view; - } - - @Override - public boolean overItem(MotionEvent e) { - return getItemPosition(e) != RecyclerView.NO_POSITION; - } - - @Override - public boolean overStableItem(MotionEvent e) { - return overItem(e) && getItemDetails(e).hasStableId(); - } - - @Override - public boolean inItemDragRegion(MotionEvent e) { - return overItem(e) && getItemDetails(e).inDragRegion(e); - } - - @Override - public boolean inItemSelectRegion(MotionEvent e) { - return overItem(e) && getItemDetails(e).inSelectionHotspot(e); - } - - @Override - public int getItemPosition(MotionEvent e) { - View child = mRecView.findChildViewUnder(e.getX(), e.getY()); - return (child != null) - ? mRecView.getChildAdapterPosition(child) - : RecyclerView.NO_POSITION; - } - - @Override - public ItemDetails getItemDetails(MotionEvent e) { - @Nullable DemoHolder holder = getDemoHolder(e); - return holder == null ? null : holder.getItemDetails(); - } - - private @Nullable DemoHolder getDemoHolder(MotionEvent e) { - View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); - if (childView != null) { - ViewHolder holder = mRecView.getChildViewHolder(childView); - if (holder instanceof DemoHolder) { - return (DemoHolder) holder; - } - } - return null; - } -} diff --git a/src/com/android/documentsui/selection/demo/DemoHolder.java b/src/com/android/documentsui/selection/demo/DemoHolder.java deleted file mode 100644 index ade72c7dd..000000000 --- a/src/com/android/documentsui/selection/demo/DemoHolder.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -import android.graphics.Rect; -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.documentsui.R; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; - -public final class DemoHolder extends RecyclerView.ViewHolder { - - private final LinearLayout mContainer; - public final TextView mSelector; - public final TextView mLabel; - private final Details mDetails; - private DemoItem mItem; - - DemoHolder(LinearLayout layout) { - super(layout); - mContainer = layout.findViewById(R.id.container); - mSelector = layout.findViewById(R.id.selector); - mLabel = layout.findViewById(R.id.label); - mDetails = new Details(this); - } - - public void update(DemoItem demoItem) { - mItem = demoItem; - mLabel.setText(mItem.getName()); - } - - void setSelected(boolean selected) { - mContainer.setActivated(selected); - mSelector.setActivated(selected); - } - - public String getStableId() { - return mItem != null ? mItem.getId() : null; - } - - public boolean inDragRegion(MotionEvent e) { - // If itemView is activated = selected, then whole region is interactive - if (mContainer.isActivated()) { - return true; - } - - if (inSelectRegion(e)) { - return true; - } - - Rect rect = new Rect(); - mLabel.getPaint().getTextBounds( - mLabel.getText().toString(), 0, mLabel.getText().length(), rect); - - // If the tap occurred inside the text, these are interactive spots. - return rect.contains((int) e.getRawX(), (int) e.getRawY()); - } - - public boolean inSelectRegion(MotionEvent e) { - Rect iconRect = new Rect(); - mSelector.getGlobalVisibleRect(iconRect); - return iconRect.contains((int) e.getRawX(), (int) e.getRawY()); - } - - Details getItemDetails() { - return mDetails; - } - - private static final class Details extends ItemDetails { - - private final DemoHolder mHolder; - - Details(DemoHolder holder) { - mHolder = holder; - } - - @Override - public int getPosition() { - return mHolder.getAdapterPosition(); - } - - @Override - public String getStableId() { - return mHolder.getStableId(); - } - - @Override - public int getItemViewType() { - return mHolder.getItemViewType(); - } - - @Override - public boolean inDragRegion(MotionEvent e) { - return mHolder.inDragRegion(e); - } - - @Override - public boolean inSelectionHotspot(MotionEvent e) { - return mHolder.inSelectRegion(e); - } - } - }
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/demo/DemoItem.java b/src/com/android/documentsui/selection/demo/DemoItem.java deleted file mode 100644 index 15755a0e9..000000000 --- a/src/com/android/documentsui/selection/demo/DemoItem.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -public class DemoItem { - - private final String mId; - private final String mName; - - DemoItem(String id, String name) { - mId = id; - mName = name; - } - - public String getId() { - return mId; - } - - public String getName() { - return mName; - } -} diff --git a/src/com/android/documentsui/selection/demo/DemoStableIdProvider.java b/src/com/android/documentsui/selection/demo/DemoStableIdProvider.java deleted file mode 100644 index 5b5332ec8..000000000 --- a/src/com/android/documentsui/selection/demo/DemoStableIdProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -import static androidx.core.util.Preconditions.checkArgument; - -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; - -import java.util.List; - -/** - * Provides RecyclerView selection code access to stable ids backed - * by DocumentsAdapter. - */ -final class DemoStableIdProvider extends StableIdProvider { - - private final SelectionDemoAdapter mAdapter; - - public DemoStableIdProvider(SelectionDemoAdapter adapter) { - checkArgument(adapter != null); - mAdapter = adapter; - } - - @Override - public String getStableId(int position) { - return mAdapter.getStableId(position); - } - - @Override - public int getPosition(String id) { - return mAdapter.getPosition(id); - } - - @Override - public List<String> getStableIds() { - return mAdapter.getStableIds(); - } -} diff --git a/src/com/android/documentsui/selection/demo/SelectionDemoActivity.java b/src/com/android/documentsui/selection/demo/SelectionDemoActivity.java deleted file mode 100644 index 90857745c..000000000 --- a/src/com/android/documentsui/selection/demo/SelectionDemoActivity.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -import android.content.Context; -import android.os.Bundle; -import androidx.annotation.CallSuper; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.widget.Toast; - -import com.android.documentsui.R; -import com.android.documentsui.selection.BandSelectionHelper; -import com.android.documentsui.selection.ContentLock; -import com.android.documentsui.selection.DefaultBandHost; -import com.android.documentsui.selection.DefaultBandPredicate; -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.GestureRouter; -import com.android.documentsui.selection.GestureSelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.MotionInputHandler; -import com.android.documentsui.selection.MouseInputHandler; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; -import com.android.documentsui.selection.TouchEventRouter; -import com.android.documentsui.selection.TouchInputHandler; -import com.android.documentsui.selection.demo.SelectionDemoAdapter.OnBindCallback; - -/** - * ContentPager demo activity. - */ -public class SelectionDemoActivity extends AppCompatActivity { - - private static final String EXTRA_SAVED_SELECTION = "demo-saved-selection"; - private static final String EXTRA_COLUMN_COUNT = "demo-column-count"; - - private Toolbar mToolbar; - private SelectionDemoAdapter mAdapter; - private SelectionHelper mSelectionHelper; - - private RecyclerView mRecView; - private GridLayoutManager mLayout; - private int mColumnCount = 1; // This will get updated when layout changes. - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.selection_demo_layout); - mToolbar = findViewById(R.id.toolbar); - setSupportActionBar(mToolbar); - mRecView = (RecyclerView) findViewById(R.id.list); - - mLayout = new GridLayoutManager(this, mColumnCount); - mRecView.setLayoutManager(mLayout); - - mAdapter = new SelectionDemoAdapter(this); - mRecView.setAdapter(mAdapter); - - StableIdProvider stableIds = new DemoStableIdProvider(mAdapter); - - // SelectionPredicate permits client control of which items can be selected. - SelectionPredicate canSelectAnything = new SelectionPredicate() { - @Override - public boolean canSetStateForId(String id, boolean nextState) { - return true; - } - - @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - return true; - } - }; - - // TODO: Reload content when it changes. Could use CursorLoader. - // TODO: Retain selection. Restore when content changes. - - mSelectionHelper = new DefaultSelectionHelper( - DefaultSelectionHelper.MODE_MULTIPLE, - mAdapter, - stableIds, - canSelectAnything); - - // onBind event callback that allows items to be updated to reflect - // selection status when bound by recycler view. - // This allows us to defer initialization of the SelectionHelper dependency - // which itself depends on the Adapter. - mAdapter.addOnBindCallback( - new OnBindCallback() { - @Override - void onBound(DemoHolder holder, int position) { - String id = mAdapter.getStableId(position); - holder.setSelected(mSelectionHelper.isSelected(id)); - } - }); - - ItemDetailsLookup detailsLookup = new DemoDetailsLookup(mRecView); - - // Setup basic input handling, with the touch handler as the default consumer - // of events. If mouse handling is configured as well, the mouse input - // related handlers will intercept mouse input events. - - // GestureRouter is responsible for routing GestureDetector events - // to tool-type specific handlers. - GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>(); - GestureDetector gestureDetector = new GestureDetector(this, 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); - - // Content lock provides a mechanism to block content reload while selection - // activities are active. If using a loader to load content, route - // the call through the content lock using ContentLock#runWhenUnlocked. - // This is especially useful when listening on content change notification. - ContentLock contentLock = new ContentLock(); - - // GestureSelectionHelper provides logic that interprets a combination - // of motions and gestures in order to provide gesture driven selection support - // when used in conjunction with RecyclerView. - GestureSelectionHelper gestureHelper = GestureSelectionHelper.create( - mSelectionHelper, mRecView, contentLock, detailsLookup); - - // Finally hook the framework up to listening to recycle view events. - mRecView.addOnItemTouchListener(eventRouter); - - // 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. - // Helpers contain predefined logic converting events into selection related events. - // Callbacks provide authors the ability to reponspond to other types of - // events (like "active" a tapped item). This is broken up into two main - // suites, one for "touch" and one for "mouse", though both can and should (usually) - // be configued to handle other types of input (to satisfy user expectation). - - // TOUCH (+ UNKNOWN) handeling provides gesture based selection allowing - // the user to long press on an item, then drag her finger over other - // items in order to extend the selection. - TouchCallbacks touchCallbacks = new TouchCallbacks(this, mRecView); - - // Provides high level glue for binding touch events and gestures to selection framework. - TouchInputHandler touchHandler = new TouchInputHandler( - mSelectionHelper, detailsLookup, canSelectAnything, gestureHelper, touchCallbacks); - - eventRouter.register(MotionEvent.TOOL_TYPE_FINGER, gestureHelper); - eventRouter.register(MotionEvent.TOOL_TYPE_UNKNOWN, gestureHelper); - - gestureRouter.register(MotionEvent.TOOL_TYPE_FINGER, touchHandler); - gestureRouter.register(MotionEvent.TOOL_TYPE_UNKNOWN, touchHandler); - - // MOUSE (+ STYLUS) handeling provides band based selection allowing - // the user to click down in an empty area, then drag her mouse - // to create a band that covers the items she wants selected. - // - // PRO TIP: Don't skip installing mouse/stylus support. It provides - // improved productivity and demonstrates feature maturity that users - // will appreciate. See InputManager for details on more sophisticated - // strategies on detecting the presence of input tools. - - // Provides high level glue for binding mouse/stylus events and gestures - // to selection framework. - MouseInputHandler mouseHandler = new MouseInputHandler( - mSelectionHelper, detailsLookup, new MouseCallbacks(this, mRecView)); - - DefaultBandHost host = new DefaultBandHost( - mRecView, R.drawable.selection_demo_band_overlay); - - // BandSelectionHelper provides support for band selection on-top of a RecyclerView - // instance. Given the recycling nature of RecyclerView BandSelectionController - // necessarily models and caches list/grid information as the user's pointer - // interacts with the item in the RecyclerView. Selectable items that intersect - // with the band, both on and off screen, are selected. - BandSelectionHelper bandHelper = new BandSelectionHelper( - host, - mAdapter, - stableIds, - mSelectionHelper, - canSelectAnything, - new DefaultBandPredicate(detailsLookup), - contentLock); - - - eventRouter.register(MotionEvent.TOOL_TYPE_MOUSE, bandHelper); - eventRouter.register(MotionEvent.TOOL_TYPE_STYLUS, bandHelper); - - gestureRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mouseHandler); - gestureRouter.register(MotionEvent.TOOL_TYPE_STYLUS, mouseHandler); - - // Aaaaan, all done with mouse/stylus selection setup! - - updateFromSavedState(savedInstanceState); - } - - @Override - protected void onSaveInstanceState(Bundle state) { - super.onSaveInstanceState(state); - MutableSelection selection = new MutableSelection(); - mSelectionHelper.copySelection(selection); - state.putParcelable(EXTRA_SAVED_SELECTION, selection); - state.putInt(EXTRA_COLUMN_COUNT, mColumnCount); - } - - private void updateFromSavedState(Bundle state) { - // In order to preserve selection across various lifecycle events be sure to save - // the selection in onSaveInstanceState, and to restore it when present in the Bundle - // pass in via onCreate(Bundle). - if (state != null) { - if (state.containsKey(EXTRA_SAVED_SELECTION)) { - Selection savedSelection = state.getParcelable(EXTRA_SAVED_SELECTION); - if (!savedSelection.isEmpty()) { - mSelectionHelper.restoreSelection(savedSelection); - CharSequence text = "Selection restored."; - Toast.makeText(this, "Selection restored.", Toast.LENGTH_SHORT).show(); - } - } - if (state.containsKey(EXTRA_COLUMN_COUNT)) { - mColumnCount = state.getInt(EXTRA_COLUMN_COUNT); - mLayout.setSpanCount(mColumnCount); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - boolean showMenu = super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.selection_demo_actions, menu); - return showMenu; - } - - @Override - @CallSuper - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3); - menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.option_menu_add_column: - // TODO: Add columns - mLayout.setSpanCount(++mColumnCount); - return true; - - case R.id.option_menu_remove_column: - mLayout.setSpanCount(--mColumnCount); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - - @Override - public void onBackPressed () { - if (mSelectionHelper.hasSelection()) { - mSelectionHelper.clearSelection(); - mSelectionHelper.clearProvisionalSelection(); - } else { - super.onBackPressed(); - } - } - - private static void toast(Context context, String msg) { - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); - } - - @Override - protected void onDestroy() { - mSelectionHelper.clearSelection(); - super.onDestroy(); - } - - @Override - protected void onStart() { - super.onStart(); - mAdapter.loadData(); - } - - // Implementation of MouseInputHandler.Callbacks allows handling - // of higher level events, like onActivated. - private static final class MouseCallbacks extends MouseInputHandler.Callbacks { - - private final Context mContext; - private final RecyclerView mRecView; - - MouseCallbacks(Context context, RecyclerView recView) { - mContext = context; - mRecView = recView; - } - - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - toast(mContext, "Activate item: " + item.getStableId()); - return true; - } - - @Override - public boolean onContextClick(MotionEvent e) { - toast(mContext, "Context click received."); - return true; - } - - @Override - public void onPerformHapticFeedback() { - mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - }; - - private static final class TouchCallbacks extends TouchInputHandler.Callbacks { - - private final Context mContext; - private final RecyclerView mRecView; - - private TouchCallbacks(Context context, RecyclerView recView) { - - mContext = context; - mRecView = recView; - } - - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - toast(mContext, "Activate item: " + item.getStableId()); - return true; - } - - @Override - public void onPerformHapticFeedback() { - mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - } -}
\ No newline at end of file diff --git a/src/com/android/documentsui/selection/demo/SelectionDemoAdapter.java b/src/com/android/documentsui/selection/demo/SelectionDemoAdapter.java deleted file mode 100644 index e70b335c5..000000000 --- a/src/com/android/documentsui/selection/demo/SelectionDemoAdapter.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.demo; - -import android.content.Context; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.android.documentsui.R; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -final class SelectionDemoAdapter extends RecyclerView.Adapter<DemoHolder> { - - private static final Map<String, DemoItem> sDemoData = new HashMap<>(); - static { - for (int i = 0; i < 1000; i++) { - String id = createId(i); - sDemoData.put(id, new DemoItem(id, "item" + i)); - } - } - - private final Context mContext; - private @Nullable OnBindCallback mBindCallback; - - SelectionDemoAdapter(Context context) { - mContext = context; - } - - void addOnBindCallback(OnBindCallback bindCallback) { - mBindCallback = bindCallback; - } - - void loadData() { - onDataReady(); - } - - private void onDataReady() { - notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - return sDemoData.size(); - } - - @Override - public void onBindViewHolder(DemoHolder holder, int position) { - String id = createId(position); - holder.update(sDemoData.get(id)); - if (mBindCallback != null) { - mBindCallback.onBound(holder, position); - } - } - - private static String createId(int position) { - return "id" + position; - } - - @Override - public DemoHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LinearLayout layout = inflateLayout(mContext, parent, R.layout.selection_demo_list_item); - return new DemoHolder(layout); - } - - String getStableId(int position) { - return createId(position); - } - - int getPosition(String id) { - return Integer.parseInt(id.substring(2)); - } - - List<String> getStableIds() { - return new ArrayList<>(sDemoData.keySet()); - } - - @SuppressWarnings("TypeParameterUnusedInFormals") // Convenience to avoid clumsy cast. - private static <V extends View> V inflateLayout( - Context context, ViewGroup parent, int layout) { - - return (V) LayoutInflater.from(context).inflate(layout, parent, false); - } - - static abstract class OnBindCallback { - abstract void onBound(DemoHolder holder, int position); - } -} diff --git a/tests/common/com/android/documentsui/SelectionHelpers.java b/tests/common/com/android/documentsui/SelectionHelpers.java new file mode 100644 index 000000000..2018252cb --- /dev/null +++ b/tests/common/com/android/documentsui/SelectionHelpers.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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 com.android.documentsui; + +import androidx.recyclerview.selection.DefaultSelectionTracker; +import androidx.recyclerview.selection.SelectionPredicates; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; +import androidx.recyclerview.selection.StorageStrategy; + +import com.android.documentsui.testing.TestStableIdProvider; + +import java.util.Collections; +import java.util.List; + +public class SelectionHelpers { + + public static final SelectionPredicate<String> CAN_SET_ANYTHING = + SelectionPredicates.createSelectAnything(); + + private SelectionHelpers() {} + + public static DocsSelectionHelper createTestInstance() { + return createTestInstance(Collections.emptyList()); + } + + public static DocsSelectionHelper createTestInstance(List<String> docs) { + DocsSelectionHelper manager = new DocsSelectionHelper( + new DocsSelectionHelper.DelegateFactory() { + + @Override + SelectionTracker<String> create(SelectionTracker<String> selectionTracker) { + return new DefaultSelectionTracker<String>( + Integer.toHexString(System.identityHashCode(docs)), + new TestStableIdProvider(docs), + CAN_SET_ANYTHING, + StorageStrategy.createStringStorage()); + } + }); + + manager.reset(null); + return manager; + } +} diff --git a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java index 2427dc0ca..c7ccaf99f 100644 --- a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java +++ b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java @@ -18,13 +18,13 @@ package com.android.documentsui.dirlist; import static org.junit.Assert.assertTrue; +import androidx.recyclerview.selection.SelectionTracker; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import android.view.ViewGroup; import com.android.documentsui.Model.Update; import com.android.documentsui.base.EventListener; -import com.android.documentsui.selection.SelectionHelper; import com.android.documentsui.testing.TestEventListener; import java.util.ArrayList; @@ -51,7 +51,7 @@ public class TestDocumentsAdapter extends DocumentsAdapter { @Override public void onItemRangeChanged(int startPosition, int itemCount, Object payload) { - if (SelectionHelper.SELECTION_CHANGED_MARKER.equals(payload)) { + if (SelectionTracker.SELECTION_CHANGED_MARKER.equals(payload)) { int last = startPosition + itemCount; for (int i = startPosition; i < last; i++) { mSelectionChanged.add(i); diff --git a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java index 8dfbdeaa5..565665566 100644 --- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java +++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java @@ -59,7 +59,7 @@ public final class TestFocusHandler implements FocusHandler { } @Override - public int getFocusPosition() { + public int getFocusedPosition() { return focusPos; } diff --git a/tests/common/com/android/documentsui/testing/SelectionHelpers.java b/tests/common/com/android/documentsui/testing/SelectionHelpers.java deleted file mode 100644 index 70bc44d91..000000000 --- a/tests/common/com/android/documentsui/testing/SelectionHelpers.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.testing; - -import com.android.documentsui.DocsSelectionHelper; -import com.android.documentsui.dirlist.DocsStableIdProvider; -import com.android.documentsui.dirlist.DocumentsAdapter; -import com.android.documentsui.dirlist.TestDocumentsAdapter; -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.DefaultSelectionHelper.SelectionMode; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; - -import java.util.Collections; -import java.util.List; - -public class SelectionHelpers { - - public static final SelectionPredicate CAN_SET_ANYTHING = new SelectionPredicate() { - @Override - public boolean canSetStateForId(String id, boolean nextState) { - return true; - } - - @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - return true; - } - }; - - private SelectionHelpers() {} - - public static DocsSelectionHelper createTestInstance() { - return createTestInstance(Collections.emptyList()); - } - - public static DocsSelectionHelper createTestInstance(List<String> docs) { - return createTestInstance(docs, DefaultSelectionHelper.MODE_MULTIPLE); - } - - public static DocsSelectionHelper createTestInstance( - List<String> docs, @SelectionMode int mode) { - return createTestInstance(new TestDocumentsAdapter(docs), mode, CAN_SET_ANYTHING); - } - - public static DocsSelectionHelper createTestInstance( - DocumentsAdapter adapter, @SelectionMode int mode, SelectionPredicate canSetState) { - DocsSelectionHelper manager = mode == DefaultSelectionHelper.MODE_SINGLE - ? DocsSelectionHelper.createSingleSelect() - : DocsSelectionHelper.createMultiSelect(); - - manager.reset(adapter, new DocsStableIdProvider(adapter), canSetState); - - return manager; - } -} diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java index cd3fb1acf..7b11d41f4 100644 --- a/tests/common/com/android/documentsui/testing/TestActionHandler.java +++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java @@ -18,11 +18,12 @@ package com.android.documentsui.testing; import android.content.Intent; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.TestActivity; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.RootInfo; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import java.util.function.Consumer; @@ -30,7 +31,7 @@ public class TestActionHandler extends AbstractActionHandler<TestActivity> { private final TestEnv mEnv; - public final TestEventHandler<ItemDetails> open = new TestEventHandler<>(); + public final TestEventHandler<ItemDetails<String>> open = new TestEventHandler<>(); public boolean mDeleteHappened; public DocumentInfo nextRootDocument; @@ -53,7 +54,7 @@ public class TestActionHandler extends AbstractActionHandler<TestActivity> { } @Override - public boolean openItem(ItemDetails doc, @ViewType int type, @ViewType int fallback) { + public boolean openItem(ItemDetails<String> doc, @ViewType int type, @ViewType int fallback) { return open.accept(doc); } diff --git a/tests/common/com/android/documentsui/testing/TestDocumentClipper.java b/tests/common/com/android/documentsui/testing/TestDocumentClipper.java index 3d6c9ad38..9fa4f2e55 100644 --- a/tests/common/com/android/documentsui/testing/TestDocumentClipper.java +++ b/tests/common/com/android/documentsui/testing/TestDocumentClipper.java @@ -20,10 +20,11 @@ import android.content.ClipData; import android.net.Uri; import android.util.Pair; +import androidx.recyclerview.selection.Selection; + import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.clipping.DocumentClipper; -import com.android.documentsui.selection.Selection; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations.Callback; @@ -49,8 +50,8 @@ public class TestDocumentClipper implements DocumentClipper { } @Override - public ClipData getClipDataForDocuments(Function<String, Uri> uriBuilder, Selection selection, - int opType) { + public ClipData getClipDataForDocuments( + Function<String, Uri> uriBuilder, Selection<String> selection, int opType) { return nextClip; } @@ -67,11 +68,11 @@ public class TestDocumentClipper implements DocumentClipper { } @Override - public void clipDocumentsForCopy(Function<String, Uri> uriBuilder, Selection selection) { + public void clipDocumentsForCopy(Function<String, Uri> uriBuilder, Selection<String> selection) { } @Override - public void clipDocumentsForCut(Function<String, Uri> uriBuilder, Selection selection, + public void clipDocumentsForCut(Function<String, Uri> uriBuilder, Selection<String> selection, DocumentInfo parent) { List<Uri> uris = new ArrayList<>(selection.size()); for (String id : selection) { diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java index 40b5e0aee..93cc032a1 100644 --- a/tests/common/com/android/documentsui/testing/TestEnv.java +++ b/tests/common/com/android/documentsui/testing/TestEnv.java @@ -23,6 +23,7 @@ import android.test.mock.MockContentResolver; import com.android.documentsui.DocsSelectionHelper; import com.android.documentsui.FocusManager; import com.android.documentsui.Injector; +import com.android.documentsui.SelectionHelpers; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Features; diff --git a/tests/unit/com/android/documentsui/selection/testing/TestSelectionPredicate.java b/tests/common/com/android/documentsui/testing/TestStableIdProvider.java index 578be3db3..b1acc25dd 100644 --- a/tests/unit/com/android/documentsui/selection/testing/TestSelectionPredicate.java +++ b/tests/common/com/android/documentsui/testing/TestStableIdProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 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. @@ -13,24 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.documentsui.selection.testing; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; +package com.android.documentsui.testing; -public final class TestSelectionPredicate extends SelectionPredicate { - private boolean mValue; +import com.android.documentsui.DocsSelectionHelper.StableIdProvider; - public void setReturnValue(boolean value) { - mValue = value; +import java.util.List; + +public class TestStableIdProvider extends StableIdProvider { + + private final List<String> mDocs; + + public TestStableIdProvider(List<String> docs) { + mDocs = docs; } @Override - public boolean canSetStateForId(String id, boolean nextState) { - return mValue; + public String getKey(int position) { + return mDocs.get(position); } @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - return mValue; + public int getPosition(String id) { + return mDocs.indexOf(id); } -}
\ No newline at end of file +} diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java index d9c551725..cc267bcc0 100644 --- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java @@ -28,11 +28,12 @@ import android.provider.DocumentsContract.Path; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.Shared; import com.android.documentsui.files.LauncherActivity; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; import com.android.documentsui.sorting.SortDimension; import com.android.documentsui.sorting.SortModel; import com.android.documentsui.testing.DocumentStackAsserts; @@ -77,7 +78,8 @@ public class AbstractActionHandlerTest { } @Override - public boolean openItem(ItemDetails doc, @ViewType int type, @ViewType int fallback) { + public boolean openItem( + ItemDetails<String> doc, @ViewType int type, @ViewType int fallback) { throw new UnsupportedOperationException(); } diff --git a/tests/unit/com/android/documentsui/DocsSelectionHelperTest.java b/tests/unit/com/android/documentsui/DocsSelectionHelperTest.java index f30464788..de536f581 100644 --- a/tests/unit/com/android/documentsui/DocsSelectionHelperTest.java +++ b/tests/unit/com/android/documentsui/DocsSelectionHelperTest.java @@ -23,14 +23,11 @@ import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.widget.RecyclerView.Adapter; + +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; import com.android.documentsui.DocsSelectionHelper.DelegateFactory; -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; import org.junit.Before; import org.junit.Test; @@ -40,7 +37,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; /** * Tests for the specialized behaviors provided by DocsSelectionManager. @@ -57,20 +53,16 @@ public class DocsSelectionHelperTest { public void setup() { mCreated = new ArrayList<>(); mFactory = new DelegateFactory() { - @Override - TestSelectionManager create( - int mode, - Adapter<?> adapter, - StableIdProvider stableIds, - SelectionPredicate canSetState) { + @Override + TestSelectionManager create(SelectionTracker<String> selectionTracker) { TestSelectionManager mgr = new TestSelectionManager(); mCreated.add(mgr); return mgr; } }; - mSelectionMgr = new DocsSelectionHelper(mFactory, DefaultSelectionHelper.MODE_MULTIPLE); + mSelectionMgr = new DocsSelectionHelper(mFactory); } @Test @@ -82,16 +74,16 @@ public class DocsSelectionHelperTest { @Test public void testReset_CreatesNewInstances() { - mSelectionMgr.reset(null, null, null); // nulls are passed to factory. We ignore. - mSelectionMgr.reset(null, null, null); // nulls are passed to factory. We ignore. + resetSelectionHelper(); + resetSelectionHelper(); assertCreated(2); } @Test public void testReset_ClearsPreviousSelection() { - mSelectionMgr.reset(null, null, null); // nulls are passed to factory. We ignore. - mSelectionMgr.reset(null, null, null); // nulls are passed to factory. We ignore. + resetSelectionHelper(); + resetSelectionHelper(); mCreated.get(0).assertCleared(true); mCreated.get(1).assertCleared(false); @@ -99,7 +91,7 @@ public class DocsSelectionHelperTest { @Test public void testReplaceSelection() { - mSelectionMgr.reset(null, null, null); // nulls are passed to factory. We ignore. + resetSelectionHelper(); List<String> ids = new ArrayList<>(); ids.add("poodles"); @@ -113,7 +105,11 @@ public class DocsSelectionHelperTest { assertEquals(count, mCreated.size()); } - private static final class TestSelectionManager extends SelectionHelper { + private void resetSelectionHelper() { + mSelectionMgr.reset(null); // nulls are passed to factory. We ignore. + } + + private static final class TestSelectionManager extends DummySelectionTracker<String> { private boolean mCleared; private Map<String, Boolean> mSelected = new HashMap<>(); @@ -141,12 +137,7 @@ public class DocsSelectionHelperTest { } @Override - public Selection getSelection() { - throw new UnsupportedOperationException(); - } - - @Override - public void copySelection(Selection dest) { + public Selection<String> getSelection() { throw new UnsupportedOperationException(); } @@ -156,7 +147,7 @@ public class DocsSelectionHelperTest { } @Override - public void restoreSelection(Selection other) { + public void restoreSelection(Selection<String> other) { throw new UnsupportedOperationException(); } @@ -169,8 +160,9 @@ public class DocsSelectionHelperTest { } @Override - public void clearSelection() { + public boolean clearSelection() { mCleared = true; + return true; } @Override @@ -182,50 +174,5 @@ public class DocsSelectionHelperTest { public boolean deselect(String itemId) { throw new UnsupportedOperationException(); } - - @Override - public void startRange(int pos) { - throw new UnsupportedOperationException(); - } - - @Override - public void extendRange(int pos) { - throw new UnsupportedOperationException(); - } - - @Override - public void endRange() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isRangeActive() { - throw new UnsupportedOperationException(); - } - - @Override - public void anchorRange(int position) { - throw new UnsupportedOperationException(); - } - - @Override - public void extendProvisionalRange(int pos) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearProvisionalSelection() { - throw new UnsupportedOperationException(); - } - - @Override - public void mergeProvisionalSelection() { - throw new UnsupportedOperationException(); - } - - @Override - public void setProvisionalSelection(Set<String> newSelection) { - throw new UnsupportedOperationException(); - } } } diff --git a/tests/unit/com/android/documentsui/DragScrollListenerTest.java b/tests/unit/com/android/documentsui/DragScrollListenerTest.java index dd7db92d1..029228bfa 100644 --- a/tests/unit/com/android/documentsui/DragScrollListenerTest.java +++ b/tests/unit/com/android/documentsui/DragScrollListenerTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.view.DragEvent; import android.view.View; -import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks; +import com.android.documentsui.ViewAutoScroller.ScrollerCallbacks; import com.android.documentsui.testing.DragEvents; import com.android.documentsui.testing.Views; diff --git a/tests/unit/com/android/documentsui/FocusManagerTest.java b/tests/unit/com/android/documentsui/FocusManagerTest.java index 16301b729..15f703d54 100644 --- a/tests/unit/com/android/documentsui/FocusManagerTest.java +++ b/tests/unit/com/android/documentsui/FocusManagerTest.java @@ -16,17 +16,16 @@ package com.android.documentsui; -import androidx.recyclerview.widget.RecyclerView; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; -import com.android.documentsui.base.Features; +import androidx.recyclerview.selection.SelectionTracker; +import androidx.recyclerview.widget.RecyclerView; + import com.android.documentsui.dirlist.TestData; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.testing.TestModel; -import com.android.documentsui.testing.SelectionHelpers; import com.android.documentsui.testing.TestFeatures; import com.android.documentsui.testing.TestGridLayoutManager; +import com.android.documentsui.testing.TestModel; import com.android.documentsui.testing.TestRecyclerView; import com.android.documentsui.testing.Views; @@ -43,7 +42,7 @@ public class FocusManagerTest extends AndroidTestCase { private FocusManager mManager; private TestRecyclerView mView; private TestGridLayoutManager mTestGridLayoutManager; - private SelectionHelper mSelectionMgr; + private SelectionTracker<String> mSelectionMgr; private TestFeatures mFeatures; @Override diff --git a/tests/unit/com/android/documentsui/SharedInputHandlerTest.java b/tests/unit/com/android/documentsui/SharedInputHandlerTest.java index e66c5c05f..4b6151969 100644 --- a/tests/unit/com/android/documentsui/SharedInputHandlerTest.java +++ b/tests/unit/com/android/documentsui/SharedInputHandlerTest.java @@ -25,10 +25,10 @@ import android.support.test.runner.AndroidJUnit4; import android.view.KeyEvent; import android.view.MotionEvent; +import androidx.recyclerview.selection.SelectionTracker; + import com.android.documentsui.base.Procedure; import com.android.documentsui.dirlist.TestFocusHandler; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.testing.SelectionHelpers; import com.android.documentsui.testing.TestDrawerController; import com.android.documentsui.testing.TestFeatures; @@ -41,7 +41,7 @@ import org.junit.runner.RunWith; public class SharedInputHandlerTest { private SharedInputHandler mSharedInputHandler; - private SelectionHelper mSelectionMgr = SelectionHelpers.createTestInstance(); + private SelectionTracker<String> mSelectionMgr = SelectionHelpers.createTestInstance(); private TestFeatures mFeatures = new TestFeatures(); private TestFocusHandler mFocusHandler = new TestFocusHandler(); private TestDrawerController mDrawer = TestDrawerController.create(); diff --git a/tests/unit/com/android/documentsui/selection/ViewAutoScrollerTest.java b/tests/unit/com/android/documentsui/ViewAutoScrollerTest.java index b75c3daab..b3426d30d 100644 --- a/tests/unit/com/android/documentsui/selection/ViewAutoScrollerTest.java +++ b/tests/unit/com/android/documentsui/ViewAutoScrollerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.documentsui.selection; +package com.android.documentsui; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -23,8 +23,9 @@ import android.graphics.Point; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import com.android.documentsui.selection.ViewAutoScroller.ScrollHost; -import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks; +import com.android.documentsui.ViewAutoScroller; +import com.android.documentsui.ViewAutoScroller.ScrollHost; +import com.android.documentsui.ViewAutoScroller.ScrollerCallbacks; import org.junit.Before; import org.junit.Test; diff --git a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java index f2187e478..d201a54af 100644 --- a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java @@ -30,7 +30,6 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.documentsui.R; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; @SmallTest public class DocumentHolderTest extends AndroidTestCase { @@ -99,9 +98,9 @@ public class DocumentHolderTest extends AndroidTestCase { ); } - private class TestListener extends KeyboardEventListener { + private class TestListener extends KeyboardEventListener<DocumentItemDetails> { @Override - public boolean onKey(ItemDetails item, int keyCode, KeyEvent event) { + public boolean onKey(DocumentItemDetails item, int keyCode, KeyEvent event) { return false; } diff --git a/tests/unit/com/android/documentsui/dirlist/DragHostTest.java b/tests/unit/com/android/documentsui/dirlist/DragHostTest.java index 7db69dca3..bb760ad25 100644 --- a/tests/unit/com/android/documentsui/dirlist/DragHostTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DragHostTest.java @@ -20,18 +20,18 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import android.content.ClipData; -import android.database.Cursor; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.DragEvent; import android.view.View; +import androidx.recyclerview.selection.SelectionTracker; + +import com.android.documentsui.SelectionHelpers; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.files.TestActivity; -import com.android.documentsui.selection.SelectionHelper; import com.android.documentsui.testing.ClipDatas; import com.android.documentsui.testing.DragEvents; -import com.android.documentsui.testing.SelectionHelpers; import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestDragAndDropManager; import com.android.documentsui.testing.TestEnv; @@ -55,7 +55,7 @@ public class DragHostTest { private TestDialogController mDialogs; private DragHost<?> dragHost; private TestDragAndDropManager mDragAndDropManager; - private SelectionHelper mSelectionMgr; + private SelectionTracker<String> mSelectionMgr; private boolean mIsDocumentView; private DocumentHolder mNextDocumentHolder; private DocumentInfo mNextDocumentInfo; diff --git a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java index faa86a394..b5739763b 100644 --- a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java @@ -18,7 +18,6 @@ package com.android.documentsui.dirlist; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; import android.provider.DocumentsContract; import android.support.test.filters.SmallTest; @@ -26,22 +25,21 @@ import android.support.test.runner.AndroidJUnit4; import android.view.MotionEvent; import android.view.View; +import androidx.recyclerview.selection.MutableSelection; +import androidx.recyclerview.selection.Selection; + import com.android.documentsui.DocsSelectionHelper; import com.android.documentsui.MenuManager.SelectionDetails; +import com.android.documentsui.SelectionHelpers; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Events; import com.android.documentsui.base.Providers; import com.android.documentsui.base.State; import com.android.documentsui.dirlist.DragStartListener.RuntimeDragStartListener; -import com.android.documentsui.selection.MutableSelection; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.TestItemDetailsLookup; -import com.android.documentsui.testing.SelectionHelpers; import com.android.documentsui.testing.TestDragAndDropManager; import com.android.documentsui.testing.TestEvents; import com.android.documentsui.testing.TestSelectionDetails; import com.android.documentsui.testing.Views; -import com.android.internal.widget.RecyclerView; import org.junit.Before; import org.junit.Test; @@ -79,7 +77,6 @@ public class DragStartListenerTest { mListener = new DragStartListener.RuntimeDragStartListener( null, // icon helper state, - mDocLookup, mSelectionMgr, mSelectionDetails, // view finder @@ -91,7 +88,7 @@ public class DragStartListenerTest { return mViewModelId; }, // docInfo Converter - (Selection selection) -> { + (Selection<String> selection) -> { return new ArrayList<>(); }, mManager); @@ -107,47 +104,29 @@ public class DragStartListenerTest { @Test public void testMouseEvent() { - assertTrue(Events.isMouseDragEvent(mEvent.build())); + MotionEvent e = mEvent.build(); + // Assert it is a mouse drag event. + assertTrue(Events.isMouseEvent(e)); + assertTrue(e.getActionMasked() == MotionEvent.ACTION_MOVE); + assertTrue(e.isButtonPressed(MotionEvent.BUTTON_PRIMARY)); } @Test public void testDragStarted_OnMouseMove() { - assertTrue(mListener.onMouseDragEvent(mEvent.build())); + assertTrue(mListener.onDragEvent(mEvent.build())); mManager.startDragHandler.assertCalled(); } @Test public void testDragNotStarted_NonModelBackedView() { mViewModelId = null; - assertFalse(mListener.onMouseDragEvent(mEvent.build())); + assertFalse(mListener.onDragEvent(mEvent.build())); mManager.startDragHandler.assertNotCalled(); } @Test - public void testThrows_OnNonMouseMove() { - assertThrows(mEvent.touch().build()); - } - - @Test - public void testThrows_OnNonPrimaryMove() { - mEvent.releaseButton(MotionEvent.BUTTON_PRIMARY); - assertThrows(mEvent.pressButton(MotionEvent.BUTTON_SECONDARY).build()); - } - - @Test - public void testThrows_OnNonMove() { - assertThrows(mEvent.action(MotionEvent.ACTION_UP).build()); - } - - @Test - public void testThrows_WhenNotOnItem() { - mDocLookup.initAt(RecyclerView.NO_POSITION); - assertThrows(mEvent.build()); - } - - @Test public void testDragStart_nonSelectedItem() { - Selection selection = mListener.getSelectionToBeCopied("1234", + Selection<String> selection = mListener.getSelectionToBeCopied("1234", mEvent.action(MotionEvent.ACTION_MOVE).build()); assertTrue(selection.size() == 1); assertTrue(selection.contains("1234")); @@ -155,7 +134,7 @@ public class DragStartListenerTest { @Test public void testDragStart_selectedItem() { - MutableSelection selection = new MutableSelection(); + MutableSelection<String> selection = new MutableSelection<>(); selection.add("1234"); selection.add("5678"); mSelectionMgr.replaceSelection(selection); @@ -169,7 +148,7 @@ public class DragStartListenerTest { @Test public void testDragStart_newNonSelectedItem() { - MutableSelection selection = new MutableSelection(); + MutableSelection<String> selection = new MutableSelection<>(); selection.add("5678"); mSelectionMgr.replaceSelection(selection); @@ -183,7 +162,7 @@ public class DragStartListenerTest { @Test public void testCtrlDragStart_newNonSelectedItem() { - MutableSelection selection = new MutableSelection(); + MutableSelection<String> selection = new MutableSelection<>(); selection.add("5678"); mSelectionMgr.replaceSelection(selection); @@ -193,11 +172,4 @@ public class DragStartListenerTest { assertTrue(selection.contains("1234")); assertTrue(selection.contains("5678")); } - - private void assertThrows(MotionEvent e) { - try { - mListener.onMouseDragEvent(e); - fail(); - } catch (IllegalArgumentException expected) {} - } } diff --git a/tests/unit/com/android/documentsui/dirlist/KeyInputHandlerTest.java b/tests/unit/com/android/documentsui/dirlist/KeyInputHandlerTest.java index e0313dab2..58ecf8be9 100644 --- a/tests/unit/com/android/documentsui/dirlist/KeyInputHandlerTest.java +++ b/tests/unit/com/android/documentsui/dirlist/KeyInputHandlerTest.java @@ -18,17 +18,15 @@ package com.android.documentsui.dirlist; import static org.junit.Assert.assertEquals; -import androidx.annotation.Nullable; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.KeyEvent; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.testing.SelectionPredicates; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestData; -import com.android.documentsui.testing.SelectionHelpers; +import androidx.annotation.Nullable; +import androidx.recyclerview.selection.Selection; +import androidx.recyclerview.selection.SelectionTracker; + +import com.android.documentsui.SelectionHelpers; import org.junit.Before; import org.junit.Test; @@ -43,21 +41,19 @@ public final class KeyInputHandlerTest { private static final List<String> ITEMS = TestData.create(100); private KeyInputHandler mInputHandler; - private SelectionHelper mSelectionHelper; + private SelectionTracker<String> mSelectionHelper; private TestFocusHandler mFocusHandler; - private SelectionProbe mSelection; private TestCallbacks mCallbacks; @Before public void setUp() { mSelectionHelper = SelectionHelpers.createTestInstance(ITEMS); - mSelection = new SelectionProbe(mSelectionHelper); mFocusHandler = new TestFocusHandler(); mCallbacks = new TestCallbacks(); mInputHandler = new KeyInputHandler( mSelectionHelper, - SelectionPredicates.CAN_SET_ANYTHING, + SelectionHelpers.CAN_SET_ANYTHING, mCallbacks); } @@ -69,34 +65,32 @@ public final class KeyInputHandlerTest { KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); mInputHandler.onKey(null, event.getKeyCode(), event); - mSelection.assertNoSelection(); + Selection<String> selection = mSelectionHelper.getSelection(); + assertEquals(selection.toString(), 0, selection.size()); } - private static final class TestCallbacks extends KeyInputHandler.Callbacks { + private static final class TestCallbacks + extends KeyInputHandler.Callbacks<DocumentItemDetails> { - private @Nullable ItemDetails mActivated; + private @Nullable DocumentItemDetails mActivated; @Override - public boolean isInteractiveItem(ItemDetails item, KeyEvent e) { + public boolean isInteractiveItem(DocumentItemDetails item, KeyEvent e) { return true; } @Override - public boolean onItemActivated(ItemDetails item, KeyEvent e) { + public boolean onItemActivated(DocumentItemDetails item, KeyEvent e) { mActivated = item; return false; } - private void assertActivated(ItemDetails expected) { + private void assertActivated(DocumentItemDetails expected) { assertEquals(expected, mActivated); } @Override - public void onPerformHapticFeedback() { - } - - @Override - public boolean onFocusItem(ItemDetails details, int keyCode, KeyEvent event) { + public boolean onFocusItem(DocumentItemDetails details, int keyCode, KeyEvent event) { return true; } } diff --git a/tests/unit/com/android/documentsui/selection/TestItemDetails.java b/tests/unit/com/android/documentsui/dirlist/TestItemDetails.java index b943247d5..75e8e2409 100644 --- a/tests/unit/com/android/documentsui/selection/TestItemDetails.java +++ b/tests/unit/com/android/documentsui/dirlist/TestItemDetails.java @@ -13,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.documentsui.selection; +package com.android.documentsui.dirlist; import android.view.MotionEvent; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; +import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; + import com.android.internal.widget.RecyclerView; -public final class TestItemDetails extends ItemDetails { +/** + * Test implementation of ItemDetails. + */ +public final class TestItemDetails extends ItemDetails<String> { // DocumentsAdapter.ITEM_TYPE_DOCUMENT private int mViewType = -1; @@ -60,7 +64,6 @@ public final class TestItemDetails extends ItemDetails { mInSelectionHotspot = over; } - @Override public int getItemViewType() { return mViewType; } @@ -97,7 +100,7 @@ public final class TestItemDetails extends ItemDetails { } @Override - public String getStableId() { + public String getSelectionKey() { return mStableId; } diff --git a/tests/unit/com/android/documentsui/selection/TestItemDetailsLookup.java b/tests/unit/com/android/documentsui/dirlist/TestItemDetailsLookup.java index 66014ceaa..3ae3e2663 100644 --- a/tests/unit/com/android/documentsui/selection/TestItemDetailsLookup.java +++ b/tests/unit/com/android/documentsui/dirlist/TestItemDetailsLookup.java @@ -13,47 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.documentsui.selection; +package com.android.documentsui.dirlist; -import androidx.recyclerview.widget.RecyclerView; import android.view.MotionEvent; +import com.android.documentsui.DocsSelectionHelper.DocDetailsLookup; + import javax.annotation.Nullable; /** - * Test impl of ItemDetailsLookup. + * Test implementation of ItemDetailsLookup. */ -public class TestItemDetailsLookup extends ItemDetailsLookup { +public class TestItemDetailsLookup extends DocDetailsLookup { private @Nullable TestItemDetails mDoc; @Override - public boolean overItem(MotionEvent e) { - return getItemPosition(e) != RecyclerView.NO_POSITION; - } - - @Override - public boolean overStableItem(MotionEvent e) { - return mDoc.getStableId() != null; - } - - @Override - public boolean inItemDragRegion(MotionEvent e) { - return mDoc.inDragRegion(e); - } - - @Override - public int getItemPosition(MotionEvent e) { - return mDoc.getPosition(); - } - - @Override - public boolean inItemSelectRegion(MotionEvent e) { - return mDoc.inSelectionHotspot(e); - } - - @Override - public @Nullable ItemDetails getItemDetails(MotionEvent e) { + public @Nullable ItemDetails<String> getItemDetails(MotionEvent e) { return mDoc; } diff --git a/tests/unit/com/android/documentsui/files/MenuManagerTest.java b/tests/unit/com/android/documentsui/files/MenuManagerTest.java index 957a7318b..6cb3c5013 100644 --- a/tests/unit/com/android/documentsui/files/MenuManagerTest.java +++ b/tests/unit/com/android/documentsui/files/MenuManagerTest.java @@ -24,16 +24,15 @@ import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.test.AndroidTestCase; + +import androidx.recyclerview.selection.SelectionTracker; import com.android.documentsui.R; +import com.android.documentsui.SelectionHelpers; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; -import com.android.documentsui.dirlist.TestContext; import com.android.documentsui.dirlist.TestData; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.testing.SelectionHelpers; import com.android.documentsui.testing.TestDirectoryDetails; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestFeatures; @@ -112,7 +111,7 @@ public final class MenuManagerTest { private State state = new State(); private MenuManager mgr; private TestActivity activity = TestActivity.create(TestEnv.create()); - private SelectionHelper selectionManager; + private SelectionTracker<String> selectionManager; @Before public void setUp() { diff --git a/tests/unit/com/android/documentsui/selection/BandSelectionHelperTest.java b/tests/unit/com/android/documentsui/selection/BandSelectionHelperTest.java deleted file mode 100644 index 62c6f769e..000000000 --- a/tests/unit/com/android/documentsui/selection/BandSelectionHelperTest.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.graphics.Point; -import android.graphics.Rect; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import android.view.MotionEvent; - -import com.android.documentsui.selection.BandSelectionHelper.BandHost; -import com.android.documentsui.selection.testing.SelectionPredicates; -import com.android.documentsui.selection.testing.TestAdapter; -import com.android.documentsui.selection.testing.TestBandPredicate; -import com.android.documentsui.selection.testing.TestEvents.Builder; -import com.android.documentsui.selection.testing.TestStableIdProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Collections; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class BandSelectionHelperTest { - - private List<String> mItems; - private BandSelectionHelper mBandController; - private boolean mIsActive; - private Builder mStartBuilder; - private Builder mStopBuilder; - private MotionEvent mStartEvent; - private MotionEvent mStopEvent; - private TestBandHost mBandHost; - private TestBandPredicate mBandPredicate; - - @Before - public void setup() throws Exception { - mItems = TestAdapter.createItemList(10); - mIsActive = false; - TestAdapter adapter = new TestAdapter(); - adapter.updateTestModelIds(mItems); - mBandHost = new TestBandHost(); - mBandPredicate = new TestBandPredicate(); - - SelectionHelper helper = new DefaultSelectionHelper( - DefaultSelectionHelper.MODE_SINGLE, - adapter, - new TestStableIdProvider(adapter), - SelectionPredicates.CAN_SET_ANYTHING); - - mBandController = new BandSelectionHelper( - mBandHost, - adapter, - new TestStableIdProvider(adapter), - helper, - SelectionPredicates.CAN_SET_ANYTHING, - mBandPredicate, - new ContentLock()) { - @Override - public boolean isActive() { - return mIsActive; - } - }; - - mStartBuilder = new Builder().mouse().primary().action(MotionEvent.ACTION_MOVE); - mStopBuilder = new Builder().mouse().action(MotionEvent.ACTION_UP); - mStartEvent = mStartBuilder.build(); - mStopEvent = mStopBuilder.build(); - } - - @Test - public void testGoodStart() { - assertTrue(mBandController.shouldStart(mStartEvent)); - } - - @Test - public void testBadStart_NoButtons() { - assertFalse(mBandController.shouldStart( - mStartBuilder.releaseButton(MotionEvent.BUTTON_PRIMARY).build())); - } - - @Test - public void testBadStart_SecondaryButton() { - assertFalse( - mBandController.shouldStart(mStartBuilder.secondary().build())); - } - - @Test - public void testBadStart_TertiaryButton() { - assertFalse( - mBandController.shouldStart(mStartBuilder.tertiary().build())); - } - - @Test - public void testBadStart_Touch() { - assertFalse(mBandController.shouldStart( - mStartBuilder.touch().releaseButton(MotionEvent.BUTTON_PRIMARY).build())); - } - - @Test - public void testBadStart_RespectsCanInitiateBand() { - mBandPredicate.setCanInitiate(false); - assertFalse(mBandController.shouldStart(mStartEvent)); - } - - @Test - public void testBadStart_ActionDown() { - assertFalse(mBandController - .shouldStart(mStartBuilder.action(MotionEvent.ACTION_DOWN).build())); - } - - @Test - public void testBadStart_ActionUp() { - assertFalse(mBandController - .shouldStart(mStartBuilder.action(MotionEvent.ACTION_UP).build())); - } - - @Test - public void testBadStart_ActionPointerDown() { - assertFalse(mBandController.shouldStart( - mStartBuilder.action(MotionEvent.ACTION_POINTER_DOWN).build())); - } - - @Test - public void testBadStart_ActionPointerUp() { - assertFalse(mBandController.shouldStart( - mStartBuilder.action(MotionEvent.ACTION_POINTER_UP).build())); - } - - @Test - public void testBadStart_NoItems() { - TestAdapter emptyAdapter = new TestAdapter(); - emptyAdapter.updateTestModelIds(Collections.EMPTY_LIST); - - SelectionHelper helper = new DefaultSelectionHelper( - DefaultSelectionHelper.MODE_SINGLE, - emptyAdapter, - new TestStableIdProvider(emptyAdapter), - SelectionPredicates.CAN_SET_ANYTHING); - - mBandController = new BandSelectionHelper( - new TestBandHost(), - emptyAdapter, - new TestStableIdProvider(emptyAdapter), - helper, - SelectionPredicates.CAN_SET_ANYTHING, - mBandPredicate, - new ContentLock()); - - assertFalse(mBandController.shouldStart(mStartEvent)); - } - - @Test - public void testBadStart_alreadyActive() { - mIsActive = true; - assertFalse(mBandController.shouldStart(mStartEvent)); - } - - @Test - public void testGoodStop() { - mIsActive = true; - assertTrue(mBandController.shouldStop(mStopEvent)); - } - - @Test - public void testGoodStop_PointerUp() { - mIsActive = true; - assertTrue(mBandController - .shouldStop(mStopBuilder.action(MotionEvent.ACTION_POINTER_UP).build())); - } - - @Test - public void testGoodStop_Cancel() { - mIsActive = true; - assertTrue(mBandController - .shouldStop(mStopBuilder.action(MotionEvent.ACTION_CANCEL).build())); - } - - @Test - public void testBadStop_NotActive() { - assertFalse(mBandController.shouldStop(mStopEvent)); - } - - @Test - public void testBadStop_NonMouse() { - mIsActive = true; - assertFalse(mBandController.shouldStop(mStopBuilder.touch().build())); - } - - @Test - public void testBadStop_Move() { - mIsActive = true; - assertFalse(mBandController.shouldStop( - mStopBuilder.action(MotionEvent.ACTION_MOVE).touch().build())); - } - - @Test - public void testBadStop_Down() { - mIsActive = true; - assertFalse(mBandController.shouldStop( - mStopBuilder.action(MotionEvent.ACTION_DOWN).touch().build())); - } - - private final class TestBandHost extends BandHost { - @Override - public void scrollBy(int dy) { - } - - @Override - public void runAtNextFrame(Runnable r) { - } - - @Override - public void removeCallback(Runnable r) { - } - - @Override - public void showBand(Rect rect) { - } - - @Override - public void hideBand() { - } - - @Override - public void addOnScrollListener(OnScrollListener listener) { - } - - @Override - public void removeOnScrollListener(OnScrollListener listener) { - } - - @Override - public int getHeight() { - return 0; - } - - @Override - public void invalidateView() { - } - - @Override - public Point createAbsolutePoint(Point relativePoint) { - return null; - } - - @Override - public Rect getAbsoluteRectForChildViewAt(int index) { - return null; - } - - @Override - public int getAdapterPositionAt(int index) { - return 0; - } - - @Override - public int getColumnCount() { - return 0; - } - - @Override - public int getChildCount() { - return 0; - } - - @Override - public int getVisibleChildCount() { - return 0; - } - - @Override - public boolean hasView(int adapterPosition) { - return false; - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/ContentLockTest.java b/tests/unit/com/android/documentsui/selection/ContentLockTest.java deleted file mode 100644 index 71b6b0fb0..000000000 --- a/tests/unit/com/android/documentsui/selection/ContentLockTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ContentLockTest { - - private ContentLock mLock = new ContentLock(); - private boolean mCalled; - - private Runnable mRunnable = new Runnable() { - @Override - public void run() { - mCalled = true; - } - }; - - @Before - public void setUp() { - mCalled = false; - } - - @Test - public void testNotBlocking_callbackNotBlocked() { - mLock.runWhenUnlocked(mRunnable); - assertTrue(mCalled); - } - - @Test - public void testToggleBlocking_callbackNotBlocked() { - mLock.block(); - mLock.unblock(); - mLock.runWhenUnlocked(mRunnable); - assertTrue(mCalled); - } - - @Test - public void testBlocking_callbackBlocked() { - mLock.block(); - mLock.runWhenUnlocked(mRunnable); - assertFalse(mCalled); - } - - @Test - public void testBlockthenUnblock_callbackNotBlocked() { - mLock.block(); - mLock.runWhenUnlocked(mRunnable); - mLock.unblock(); - assertTrue(mCalled); - } -} diff --git a/tests/unit/com/android/documentsui/selection/DefaultSelectionHelperTest.java b/tests/unit/com/android/documentsui/selection/DefaultSelectionHelperTest.java deleted file mode 100644 index 48c0b2af0..000000000 --- a/tests/unit/com/android/documentsui/selection/DefaultSelectionHelperTest.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; -import static org.junit.Assert.assertFalse; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.SparseBooleanArray; - -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestAdapter; -import com.android.documentsui.selection.testing.TestSelectionObserver; -import com.android.documentsui.selection.testing.TestStableIdProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DefaultSelectionHelperTest { - - private List<String> mItems; - private Set<String> mIgnored; - private TestAdapter mAdapter; - private DefaultSelectionHelper mHelper; - private TestSelectionObserver mListener; - private SelectionProbe mSelection; - - @Before - public void setUp() throws Exception { - mIgnored = new HashSet<>(); - mItems = TestAdapter.createItemList(100); - mListener = new TestSelectionObserver(); - mAdapter = new TestAdapter(); - mAdapter.updateTestModelIds(mItems); - - SelectionPredicate canSelect = new SelectionPredicate() { - - @Override - public boolean canSetStateForId(String id, boolean nextState) { - return !nextState || !mIgnored.contains(id); - } - - @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - throw new UnsupportedOperationException("Not implemented."); - } - }; - mHelper = new DefaultSelectionHelper( - DefaultSelectionHelper.MODE_MULTIPLE, - mAdapter, - new TestStableIdProvider(mAdapter), - canSelect); - - mHelper.addObserver(mListener); - - mSelection = new SelectionProbe(mHelper, mListener); - - mIgnored.clear(); - } - - @Test - public void testSelect() { - mHelper.select(mItems.get(7)); - - mSelection.assertSelection(7); - } - - @Test - public void testDeselect() { - mHelper.select(mItems.get(7)); - mHelper.deselect(mItems.get(7)); - - mSelection.assertNoSelection(); - } - - @Test - public void testSelection_DoNothingOnUnselectableItem() { - mIgnored.add(mItems.get(7)); - boolean selected = mHelper.select(mItems.get(7)); - - assertFalse(selected); - mSelection.assertNoSelection(); - } - - @Test - public void testSelect_NotifiesListenersOfChange() { - mHelper.select(mItems.get(7)); - - mListener.assertSelectionChanged(); - } - - @Test - public void testSelect_NotifiesAdapterOfSelect() { - mHelper.select(mItems.get(7)); - - mAdapter.assertNotifiedOfSelectionChange(7); - } - - @Test - public void testSelect_NotifiesAdapterOfDeselect() { - mHelper.select(mItems.get(7)); - mAdapter.resetSelectionNotifications(); - mHelper.deselect(mItems.get(7)); - mAdapter.assertNotifiedOfSelectionChange(7); - } - - @Test - public void testDeselect_NotifiesSelectionChanged() { - mHelper.select(mItems.get(7)); - mHelper.deselect(mItems.get(7)); - - mListener.assertSelectionChanged(); - } - - @Test - public void testSelection_PersistsOnUpdate() { - mHelper.select(mItems.get(7)); - mAdapter.updateTestModelIds(mItems); - - mSelection.assertSelection(7); - } - - @Test - public void testSelection_IntersectsWithNewDataSet() { - mHelper.select(mItems.get(99)); - mHelper.select(mItems.get(7)); - - mAdapter.updateTestModelIds(TestAdapter.createItemList(50)); - - mSelection.assertSelection(7); - } - - @Test - public void testSetItemsSelected() { - mHelper.setItemsSelected(getStringIds(6, 7, 8), true); - - mSelection.assertRangeSelected(6, 8); - } - - @Test - public void testSetItemsSelected_SkipUnselectableItem() { - mIgnored.add(mItems.get(7)); - - mHelper.setItemsSelected(getStringIds(6, 7, 8), true); - - mSelection.assertSelected(6); - mSelection.assertNotSelected(7); - mSelection.assertSelected(8); - } - - @Test - public void testRangeSelection() { - mHelper.startRange(15); - mHelper.extendRange(19); - mSelection.assertRangeSelection(15, 19); - } - - @Test - public void testRangeSelection_SkipUnselectableItem() { - mIgnored.add(mItems.get(17)); - - mHelper.startRange(15); - mHelper.extendRange(19); - - mSelection.assertRangeSelected(15, 16); - mSelection.assertNotSelected(17); - mSelection.assertRangeSelected(18, 19); - } - - @Test - public void testRangeSelection_snapExpand() { - mHelper.startRange(15); - mHelper.extendRange(19); - mHelper.extendRange(27); - mSelection.assertRangeSelection(15, 27); - } - - @Test - public void testRangeSelection_snapContract() { - mHelper.startRange(15); - mHelper.extendRange(27); - mHelper.extendRange(19); - mSelection.assertRangeSelection(15, 19); - } - - @Test - public void testRangeSelection_snapInvert() { - mHelper.startRange(15); - mHelper.extendRange(27); - mHelper.extendRange(3); - mSelection.assertRangeSelection(3, 15); - } - - @Test - public void testRangeSelection_multiple() { - mHelper.startRange(15); - mHelper.extendRange(27); - mHelper.endRange(); - mHelper.startRange(42); - mHelper.extendRange(57); - mSelection.assertSelectionSize(29); - mSelection.assertRangeSelected(15, 27); - mSelection.assertRangeSelected(42, 57); - } - - @Test - public void testProvisionalRangeSelection() { - mHelper.startRange(13); - mHelper.extendProvisionalRange(15); - mSelection.assertRangeSelection(13, 15); - mHelper.getSelection().mergeProvisionalSelection(); - mHelper.endRange(); - mSelection.assertSelectionSize(3); - } - - @Test - public void testProvisionalRangeSelection_endEarly() { - mHelper.startRange(13); - mHelper.extendProvisionalRange(15); - mSelection.assertRangeSelection(13, 15); - - mHelper.endRange(); - // If we end range selection prematurely for provision selection, nothing should be selected - // except the first item - mSelection.assertSelectionSize(1); - } - - @Test - public void testProvisionalRangeSelection_snapExpand() { - mHelper.startRange(13); - mHelper.extendProvisionalRange(15); - mSelection.assertRangeSelection(13, 15); - mHelper.getSelection().mergeProvisionalSelection(); - mHelper.extendRange(18); - mSelection.assertRangeSelection(13, 18); - } - - @Test - public void testCombinationRangeSelection_IntersectsOldSelection() { - mHelper.startRange(13); - mHelper.extendRange(15); - mSelection.assertRangeSelection(13, 15); - - mHelper.startRange(11); - mHelper.extendProvisionalRange(18); - mSelection.assertRangeSelected(11, 18); - mHelper.endRange(); - mSelection.assertRangeSelected(13, 15); - mSelection.assertRangeSelected(11, 11); - mSelection.assertSelectionSize(4); - } - - @Test - public void testProvisionalSelection() { - Selection s = mHelper.getSelection(); - mSelection.assertNoSelection(); - - // Mimicking band selection case -- BandController notifies item callback by itself. - mListener.onItemStateChanged(mItems.get(1), true); - mListener.onItemStateChanged(mItems.get(2), true); - - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(1, true); - provisional.append(2, true); - s.setProvisionalSelection(getItemIds(provisional)); - mSelection.assertSelection(1, 2); - } - - @Test - public void testProvisionalSelection_Replace() { - Selection s = mHelper.getSelection(); - - // Mimicking band selection case -- BandController notifies item callback by itself. - mListener.onItemStateChanged(mItems.get(1), true); - mListener.onItemStateChanged(mItems.get(2), true); - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(1, true); - provisional.append(2, true); - s.setProvisionalSelection(getItemIds(provisional)); - - mListener.onItemStateChanged(mItems.get(1), false); - mListener.onItemStateChanged(mItems.get(2), false); - provisional.clear(); - - mListener.onItemStateChanged(mItems.get(3), true); - mListener.onItemStateChanged(mItems.get(4), true); - provisional.append(3, true); - provisional.append(4, true); - s.setProvisionalSelection(getItemIds(provisional)); - mSelection.assertSelection(3, 4); - } - - @Test - public void testProvisionalSelection_IntersectsExistingProvisionalSelection() { - Selection s = mHelper.getSelection(); - - // Mimicking band selection case -- BandController notifies item callback by itself. - mListener.onItemStateChanged(mItems.get(1), true); - mListener.onItemStateChanged(mItems.get(2), true); - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(1, true); - provisional.append(2, true); - s.setProvisionalSelection(getItemIds(provisional)); - - mListener.onItemStateChanged(mItems.get(1), false); - mListener.onItemStateChanged(mItems.get(2), false); - provisional.clear(); - - mListener.onItemStateChanged(mItems.get(1), true); - provisional.append(1, true); - s.setProvisionalSelection(getItemIds(provisional)); - mSelection.assertSelection(1); - } - - @Test - public void testProvisionalSelection_Apply() { - Selection s = mHelper.getSelection(); - - // Mimicking band selection case -- BandController notifies item callback by itself. - mListener.onItemStateChanged(mItems.get(1), true); - mListener.onItemStateChanged(mItems.get(2), true); - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(1, true); - provisional.append(2, true); - s.setProvisionalSelection(getItemIds(provisional)); - s.mergeProvisionalSelection(); - - mSelection.assertSelection(1, 2); - } - - @Test - public void testProvisionalSelection_Cancel() { - mHelper.select(mItems.get(1)); - mHelper.select(mItems.get(2)); - Selection s = mHelper.getSelection(); - - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(3, true); - provisional.append(4, true); - s.setProvisionalSelection(getItemIds(provisional)); - s.clearProvisionalSelection(); - - // Original selection should remain. - mSelection.assertSelection(1, 2); - } - - @Test - public void testProvisionalSelection_IntersectsAppliedSelection() { - mHelper.select(mItems.get(1)); - mHelper.select(mItems.get(2)); - Selection s = mHelper.getSelection(); - - // Mimicking band selection case -- BandController notifies item callback by itself. - mListener.onItemStateChanged(mItems.get(3), true); - SparseBooleanArray provisional = new SparseBooleanArray(); - provisional.append(2, true); - provisional.append(3, true); - s.setProvisionalSelection(getItemIds(provisional)); - mSelection.assertSelection(1, 2, 3); - } - - @Test - public void testObserverOnChanged_NotifiesListenersOfChange() { - mAdapter.notifyDataSetChanged(); - - mListener.assertSelectionChanged(); - } - - private Set<String> getItemIds(SparseBooleanArray selection) { - Set<String> ids = new HashSet<>(); - - int count = selection.size(); - for (int i = 0; i < count; ++i) { - ids.add(mItems.get(selection.keyAt(i))); - } - - return ids; - } - - private Iterable<String> getStringIds(int... ids) { - List<String> stringIds = new ArrayList<>(ids.length); - for (int id : ids) { - stringIds.add(mItems.get(id)); - } - return stringIds; - } -} diff --git a/tests/unit/com/android/documentsui/selection/DefaultSelectionHelper_SingleSelectTest.java b/tests/unit/com/android/documentsui/selection/DefaultSelectionHelper_SingleSelectTest.java deleted file mode 100644 index 563ce87df..000000000 --- a/tests/unit/com/android/documentsui/selection/DefaultSelectionHelper_SingleSelectTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static org.junit.Assert.fail; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.documentsui.selection.testing.SelectionPredicates; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestAdapter; -import com.android.documentsui.selection.testing.TestSelectionObserver; -import com.android.documentsui.selection.testing.TestStableIdProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DefaultSelectionHelper_SingleSelectTest { - - private List<String> mItems; - private SelectionHelper mHelper; - private TestSelectionObserver mListener; - private SelectionProbe mSelection; - - @Before - public void setUp() throws Exception { - mItems = TestAdapter.createItemList(100); - mListener = new TestSelectionObserver(); - TestAdapter adapter = new TestAdapter(); - adapter.updateTestModelIds(mItems); - - mHelper = new DefaultSelectionHelper( - DefaultSelectionHelper.MODE_SINGLE, - adapter, - new TestStableIdProvider(adapter), - SelectionPredicates.CAN_SET_ANYTHING); - - mHelper.addObserver(mListener); - - mSelection = new SelectionProbe(mHelper); - } - - @Test - public void testSimpleSelect() { - mHelper.select(mItems.get(3)); - mHelper.select(mItems.get(4)); - mListener.assertSelectionChanged(); - mSelection.assertSelection(4); - } - - @Test - public void testRangeSelectionNotEstablished() { - mHelper.select(mItems.get(3)); - mListener.reset(); - - try { - mHelper.extendRange(10); - fail("Should have thrown."); - } catch (Exception expected) {} - - mListener.assertSelectionUnchanged(); - mSelection.assertSelection(3); - } -} diff --git a/tests/unit/com/android/documentsui/selection/GestureRouterTest.java b/tests/unit/com/android/documentsui/selection/GestureRouterTest.java deleted file mode 100644 index e467f0fd0..000000000 --- a/tests/unit/com/android/documentsui/selection/GestureRouterTest.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyFloat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.GestureDetector.OnDoubleTapListener; -import android.view.GestureDetector.OnGestureListener; -import android.view.MotionEvent; - -import com.android.documentsui.selection.testing.TestEvents.Mouse; -import com.android.documentsui.selection.testing.TestEvents.Touch; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class GestureRouterTest { - - private TestHandler mHandler; - private TestHandler mAlt; - private GestureRouter<TestHandler> mRouter; - - @Before - public void setUp() { - mAlt = new TestHandler(); - mHandler = new TestHandler(); - } - - @Test - public void testDelegates() { - mRouter = new GestureRouter<>(); - mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler); - mRouter.register(MotionEvent.TOOL_TYPE_FINGER, mAlt); - - mRouter.onDown(Mouse.CLICK); - mHandler.assertCalled_onDown(Mouse.CLICK); - mAlt.assertNotCalled_onDown(); - - mRouter.onShowPress(Mouse.CLICK); - mHandler.assertCalled_onShowPress(Mouse.CLICK); - mAlt.assertNotCalled_onShowPress(); - - mRouter.onSingleTapUp(Mouse.CLICK); - mHandler.assertCalled_onSingleTapUp(Mouse.CLICK); - mAlt.assertNotCalled_onSingleTapUp(); - - mRouter.onScroll(null, Mouse.CLICK, -1, -1); - mHandler.assertCalled_onScroll(null, Mouse.CLICK, -1, -1); - mAlt.assertNotCalled_onScroll(); - - mRouter.onLongPress(Mouse.CLICK); - mHandler.assertCalled_onLongPress(Mouse.CLICK); - mAlt.assertNotCalled_onLongPress(); - - mRouter.onFling(null, Mouse.CLICK, -1, -1); - mHandler.assertCalled_onFling(null, Mouse.CLICK, -1, -1); - mAlt.assertNotCalled_onFling(); - - mRouter.onSingleTapConfirmed(Mouse.CLICK); - mHandler.assertCalled_onSingleTapConfirmed(Mouse.CLICK); - mAlt.assertNotCalled_onSingleTapConfirmed(); - - mRouter.onDoubleTap(Mouse.CLICK); - mHandler.assertCalled_onDoubleTap(Mouse.CLICK); - mAlt.assertNotCalled_onDoubleTap(); - - mRouter.onDoubleTapEvent(Mouse.CLICK); - mHandler.assertCalled_onDoubleTapEvent(Mouse.CLICK); - mAlt.assertNotCalled_onDoubleTapEvent(); - } - - @Test - public void testFallsback() { - mRouter = new GestureRouter<>(mAlt); - mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler); - - mRouter.onDown(Touch.TAP); - mAlt.assertCalled_onDown(Touch.TAP); - - mRouter.onShowPress(Touch.TAP); - mAlt.assertCalled_onShowPress(Touch.TAP); - - mRouter.onSingleTapUp(Touch.TAP); - mAlt.assertCalled_onSingleTapUp(Touch.TAP); - - mRouter.onScroll(null, Touch.TAP, -1, -1); - mAlt.assertCalled_onScroll(null, Touch.TAP, -1, -1); - - mRouter.onLongPress(Touch.TAP); - mAlt.assertCalled_onLongPress(Touch.TAP); - - mRouter.onFling(null, Touch.TAP, -1, -1); - mAlt.assertCalled_onFling(null, Touch.TAP, -1, -1); - - mRouter.onSingleTapConfirmed(Touch.TAP); - mAlt.assertCalled_onSingleTapConfirmed(Touch.TAP); - - mRouter.onDoubleTap(Touch.TAP); - mAlt.assertCalled_onDoubleTap(Touch.TAP); - - mRouter.onDoubleTapEvent(Touch.TAP); - mAlt.assertCalled_onDoubleTapEvent(Touch.TAP); - } - - @Test - public void testEatsEventsWhenNoFallback() { - mRouter = new GestureRouter<>(); - // Register the the delegate on mouse so touch events don't get handled. - mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler); - - mRouter.onDown(Touch.TAP); - mAlt.assertNotCalled_onDown(); - - mRouter.onShowPress(Touch.TAP); - mAlt.assertNotCalled_onShowPress(); - - mRouter.onSingleTapUp(Touch.TAP); - mAlt.assertNotCalled_onSingleTapUp(); - - mRouter.onScroll(null, Touch.TAP, -1, -1); - mAlt.assertNotCalled_onScroll(); - - mRouter.onLongPress(Touch.TAP); - mAlt.assertNotCalled_onLongPress(); - - mRouter.onFling(null, Touch.TAP, -1, -1); - mAlt.assertNotCalled_onFling(); - - mRouter.onSingleTapConfirmed(Touch.TAP); - mAlt.assertNotCalled_onSingleTapConfirmed(); - - mRouter.onDoubleTap(Touch.TAP); - mAlt.assertNotCalled_onDoubleTap(); - - mRouter.onDoubleTapEvent(Touch.TAP); - mAlt.assertNotCalled_onDoubleTapEvent(); - } - - private static final class TestHandler implements OnGestureListener, OnDoubleTapListener { - - private final Spy mSpy = Mockito.mock(Spy.class); - - @Override - public boolean onDown(MotionEvent e) { - return mSpy.onDown(e); - } - - @Override - public void onShowPress(MotionEvent e) { - mSpy.onShowPress(e); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - return mSpy.onSingleTapUp(e); - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - return mSpy.onScroll(e1, e2, distanceX, distanceY); - } - - @Override - public void onLongPress(MotionEvent e) { - mSpy.onLongPress(e); - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mSpy.onFling(e1, e2, velocityX, velocityY); - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return mSpy.onSingleTapConfirmed(e); - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - return mSpy.onDoubleTap(e); - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - return mSpy.onDoubleTapEvent(e); - } - - void assertCalled_onDown(MotionEvent e) { - verify(mSpy).onDown(e); - } - - void assertCalled_onShowPress(MotionEvent e) { - verify(mSpy).onShowPress(e); - } - - void assertCalled_onSingleTapUp(MotionEvent e) { - verify(mSpy).onSingleTapUp(e); - } - - void assertCalled_onScroll(MotionEvent e1, MotionEvent e2, float x, float y) { - verify(mSpy).onScroll(e1, e2, x, y); - } - - void assertCalled_onLongPress(MotionEvent e) { - verify(mSpy).onLongPress(e); - } - - void assertCalled_onFling(MotionEvent e1, MotionEvent e2, float x, float y) { - Mockito.verify(mSpy).onFling(e1, e2, x, y); - } - - void assertCalled_onSingleTapConfirmed(MotionEvent e) { - Mockito.verify(mSpy).onSingleTapConfirmed(e); - } - - void assertCalled_onDoubleTap(MotionEvent e) { - Mockito.verify(mSpy).onDoubleTap(e); - } - - void assertCalled_onDoubleTapEvent(MotionEvent e) { - Mockito.verify(mSpy).onDoubleTapEvent(e); - } - - void assertNotCalled_onDown() { - verify(mSpy, never()).onDown(any()); - } - - void assertNotCalled_onShowPress() { - verify(mSpy, never()).onShowPress(any()); - } - - void assertNotCalled_onSingleTapUp() { - verify(mSpy, never()).onSingleTapUp(any()); - } - - void assertNotCalled_onScroll() { - verify(mSpy, never()).onScroll(any(), any(), anyFloat(), anyFloat()); - } - - void assertNotCalled_onLongPress() { - verify(mSpy, never()).onLongPress(any()); - } - - void assertNotCalled_onFling() { - Mockito.verify(mSpy, never()).onFling(any(), any(), anyFloat(), anyFloat()); - } - - void assertNotCalled_onSingleTapConfirmed() { - Mockito.verify(mSpy, never()).onSingleTapConfirmed(any()); - } - - void assertNotCalled_onDoubleTap() { - Mockito.verify(mSpy, never()).onDoubleTap(any()); - } - - void assertNotCalled_onDoubleTapEvent() { - Mockito.verify(mSpy, never()).onDoubleTapEvent(any()); - } - } - - private static interface Spy extends OnGestureListener, OnDoubleTapListener {} -} diff --git a/tests/unit/com/android/documentsui/selection/GestureSelectionHelperTest.java b/tests/unit/com/android/documentsui/selection/GestureSelectionHelperTest.java deleted file mode 100644 index 144fa7aa6..000000000 --- a/tests/unit/com/android/documentsui/selection/GestureSelectionHelperTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import androidx.recyclerview.widget.RecyclerView; - -import android.view.MotionEvent; - -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestData; -import com.android.documentsui.testing.SelectionHelpers; -import com.android.documentsui.testing.TestEvents; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class GestureSelectionHelperTest { - - private static final List<String> ITEMS = TestData.create(100); - private static final MotionEvent DOWN = TestEvents.builder() - .action(MotionEvent.ACTION_DOWN) - .location(1, 1) - .build(); - - private static final MotionEvent MOVE = TestEvents.builder() - .action(MotionEvent.ACTION_MOVE) - .location(1, 1) - .build(); - - private static final MotionEvent UP = TestEvents.builder() - .action(MotionEvent.ACTION_UP) - .location(1, 1) - .build(); - - private GestureSelectionHelper mHelper; - private SelectionHelper mSelectionHelper; - private SelectionProbe mSelection; - private ContentLock mLock; - private TestItemDetailsLookup mItemLookup; - private TestViewDelegate mView; - - @Before - public void setUp() { - mSelectionHelper = SelectionHelpers.createTestInstance(ITEMS); - mSelection = new SelectionProbe(mSelectionHelper); - mLock = new ContentLock(); - mItemLookup = new TestItemDetailsLookup(); - mItemLookup.initAt(3); - mView = new TestViewDelegate(); - mHelper = new GestureSelectionHelper(mSelectionHelper, mView, mLock, mItemLookup); - } - - @Test - public void testIgnoresDown_NoPosition() { - mView.mNextPosition = RecyclerView.NO_POSITION; - assertFalse(mHelper.onInterceptTouchEvent(null, DOWN)); - } - - @Test - public void testIgnoresDown_NoItemDetails() { - mItemLookup.reset(); - assertFalse(mHelper.onInterceptTouchEvent(null, DOWN)); - } - - @Test - public void testNoStartOnIllegalPosition() { - mView.mNextPosition = -1; - mHelper.onInterceptTouchEvent(null, DOWN); - mHelper.start(); - assertFalse(mLock.isLocked()); - } - - @Test - public void testDoesNotClaimDownOnItem() { - mView.mNextPosition = 0; - assertFalse(mHelper.onInterceptTouchEvent(null, DOWN)); - } - - @Test - public void testClaimsMoveIfStarted() { - mView.mNextPosition = 0; - // TODO(b/109808552): This should be removed with that bug is fixed because it will be a - // no-op at that time. - mHelper.onInterceptTouchEvent(null, DOWN); - - // Normally, this is controller by the TouchSelectionHelper via a a long press gesture. - mSelectionHelper.select("1"); - mSelectionHelper.anchorRange(1); - mHelper.start(); - assertTrue(mHelper.onInterceptTouchEvent(null, MOVE)); - } - - @Test - public void testCreatesRangeSelection() { - mView.mNextPosition = 1; - mHelper.onInterceptTouchEvent(null, DOWN); - // Another way we are implicitly coupled to TouchInputHandler, is that we depend on - // long press to establish the initial anchor point. Without that we'll get an - // error when we try to extend the range. - - mSelectionHelper.select("1"); - mSelectionHelper.anchorRange(1); - mHelper.start(); - - mHelper.onTouchEvent(null, MOVE); - - mView.mNextPosition = 9; - mHelper.onTouchEvent(null, MOVE); - mHelper.onTouchEvent(null, UP); - - mSelection.assertRangeSelected(1, 9); - } - - private static final class TestViewDelegate extends GestureSelectionHelper.ViewDelegate { - - private int mNextPosition = RecyclerView.NO_POSITION; - - @Override - int getHeight() { - return 1000; - } - - @Override - int getItemUnder(MotionEvent e) { - return mNextPosition; - } - - @Override - int getLastGlidedItemPosition(MotionEvent e) { - return mNextPosition; - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java b/tests/unit/com/android/documentsui/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java deleted file mode 100644 index 9e05d0c50..000000000 --- a/tests/unit/com/android/documentsui/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.MotionEvent; -import android.view.View; - -import com.android.documentsui.selection.GestureSelectionHelper.RecyclerViewDelegate; -import com.android.documentsui.testing.TestEvents; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class GestureSelectionHelper_RecyclerViewDelegateTest { - - // Simulate a (20, 20) box locating at (20, 20) - static final int LEFT_BORDER = 20; - static final int RIGHT_BORDER = 40; - static final int TOP_BORDER = 20; - static final int BOTTOM_BORDER = 40; - - @Test - public void testLtrPastLastItem() { - MotionEvent event = createEvent(100, 100); - assertTrue(RecyclerViewDelegate.isPastLastItem( - TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_LTR)); - } - - @Test - public void testLtrPastLastItem_Inverse() { - MotionEvent event = createEvent(10, 10); - assertFalse(RecyclerViewDelegate.isPastLastItem( - TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_LTR)); - } - - @Test - public void testRtlPastLastItem() { - MotionEvent event = createEvent(10, 30); - assertTrue(RecyclerViewDelegate.isPastLastItem( - TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_RTL)); - } - - @Test - public void testRtlPastLastItem_Inverse() { - MotionEvent event = createEvent(100, 100); - assertFalse(RecyclerViewDelegate.isPastLastItem( - TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_RTL)); - } - - private static MotionEvent createEvent(int x, int y) { - return TestEvents.builder().action(MotionEvent.ACTION_MOVE).location(x, y).build(); - } -} diff --git a/tests/unit/com/android/documentsui/selection/GridModelTest.java b/tests/unit/com/android/documentsui/selection/GridModelTest.java deleted file mode 100644 index 9b1523561..000000000 --- a/tests/unit/com/android/documentsui/selection/GridModelTest.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static com.android.documentsui.selection.GridModel.NOT_SET; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.graphics.Point; -import android.graphics.Rect; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; - -import com.android.documentsui.selection.BandSelectionHelper.BandHost; -import com.android.documentsui.selection.testing.SelectionPredicates; -import com.android.documentsui.selection.testing.TestAdapter; -import com.android.documentsui.selection.testing.TestStableIdProvider; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javax.annotation.Nullable; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class GridModelTest { - - private static final int VIEW_PADDING_PX = 5; - private static final int CHILD_VIEW_EDGE_PX = 100; - private static final int VIEWPORT_HEIGHT = 500; - - private GridModel mModel; - private TestHost mHost; - private TestAdapter mAdapter; - private Set<String> mLastSelection; - private int mViewWidth; - - // TLDR: Don't call model.{start|resize}Selection; use the local #startSelection and - // #resizeSelection methods instead. - // - // The reason for this is that selection is stateful and involves operations that take the - // current UI state (e.g scrolling) into account. This test maintains its own copy of the - // selection bounds as control data for verifying selections. Keep this data in sync by calling - // #startSelection and - // #resizeSelection. - private Point mSelectionOrigin; - private Point mSelectionPoint; - - @After - public void tearDown() { - mModel = null; - mHost = null; - mLastSelection = null; - } - - @Test - public void testSelectionLeftOfItems() { - initData(20, 5); - startSelection(new Point(0, 10)); - resizeSelection(new Point(1, 11)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testSelectionRightOfItems() { - initData(20, 4); - startSelection(new Point(mViewWidth - 1, 10)); - resizeSelection(new Point(mViewWidth - 2, 11)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testSelectionAboveItems() { - initData(20, 4); - startSelection(new Point(10, 0)); - resizeSelection(new Point(11, 1)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testSelectionBelowItems() { - initData(5, 4); - startSelection(new Point(10, VIEWPORT_HEIGHT - 1)); - resizeSelection(new Point(11, VIEWPORT_HEIGHT - 2)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testVerticalSelectionBetweenItems() { - initData(20, 4); - startSelection(new Point(106, 0)); - resizeSelection(new Point(107, 200)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testHorizontalSelectionBetweenItems() { - initData(20, 4); - startSelection(new Point(0, 105)); - resizeSelection(new Point(200, 106)); - assertNoSelection(); - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testGrowingAndShrinkingSelection() { - initData(20, 4); - startSelection(new Point(0, 0)); - - resizeSelection(new Point(5, 5)); - verifySelection(); - - resizeSelection(new Point(109, 109)); - verifySelection(); - - resizeSelection(new Point(110, 109)); - verifySelection(); - - resizeSelection(new Point(110, 110)); - verifySelection(); - - resizeSelection(new Point(214, 214)); - verifySelection(); - - resizeSelection(new Point(215, 214)); - verifySelection(); - - resizeSelection(new Point(214, 214)); - verifySelection(); - - resizeSelection(new Point(110, 110)); - verifySelection(); - - resizeSelection(new Point(110, 109)); - verifySelection(); - - resizeSelection(new Point(109, 109)); - verifySelection(); - - resizeSelection(new Point(5, 5)); - verifySelection(); - - resizeSelection(new Point(0, 0)); - verifySelection(); - - assertEquals(NOT_SET, mModel.getPositionNearestOrigin()); - } - - @Test - public void testSelectionMovingAroundOrigin() { - initData(16, 4); - - startSelection(new Point(210, 210)); - resizeSelection(new Point(mViewWidth - 1, 0)); - verifySelection(); - - resizeSelection(new Point(0, 0)); - verifySelection(); - - resizeSelection(new Point(0, 420)); - verifySelection(); - - resizeSelection(new Point(mViewWidth - 1, 420)); - verifySelection(); - - // This is manually figured and will need to be adjusted if the separator position is - // changed. - assertEquals(7, mModel.getPositionNearestOrigin()); - } - - @Test - public void testScrollingBandSelect() { - initData(40, 4); - - startSelection(new Point(0, 0)); - resizeSelection(new Point(100, VIEWPORT_HEIGHT - 1)); - verifySelection(); - - scroll(CHILD_VIEW_EDGE_PX); - verifySelection(); - - resizeSelection(new Point(200, VIEWPORT_HEIGHT - 1)); - verifySelection(); - - scroll(CHILD_VIEW_EDGE_PX); - verifySelection(); - - scroll(-2 * CHILD_VIEW_EDGE_PX); - verifySelection(); - - resizeSelection(new Point(100, VIEWPORT_HEIGHT - 1)); - verifySelection(); - - assertEquals(0, mModel.getPositionNearestOrigin()); - } - - private void initData(final int numChildren, int numColumns) { - mHost = new TestHost(numChildren, numColumns); - mAdapter = new TestAdapter() { - @Override - public String getStableId(int position) { - return Integer.toString(position); - } - - @Override - public int getItemCount() { - return numChildren; - } - }; - - mViewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX); - - mModel = new GridModel( - mHost, - new TestStableIdProvider(mAdapter), - SelectionPredicates.CAN_SET_ANYTHING); - - mModel.addOnSelectionChangedListener( - new GridModel.SelectionObserver() { - @Override - public void onSelectionChanged(Set<String> updatedSelection) { - mLastSelection = updatedSelection; - } - }); - } - - /** Returns the current selection area as a Rect. */ - private Rect getSelectionArea() { - // Construct a rect from the two selection points. - Rect selectionArea = new Rect( - mSelectionOrigin.x, mSelectionOrigin.y, mSelectionOrigin.x, mSelectionOrigin.y); - selectionArea.union(mSelectionPoint.x, mSelectionPoint.y); - // Rect intersection tests are exclusive of bounds, while the MSM's selection code is - // inclusive. Expand the rect by 1 pixel in all directions to account for this. - selectionArea.inset(-1, -1); - - return selectionArea; - } - - /** Asserts that the selection is currently empty. */ - private void assertNoSelection() { - assertEquals("Unexpected items " + mLastSelection + " in selection " + getSelectionArea(), - 0, mLastSelection.size()); - } - - /** Verifies the selection using actual bbox checks. */ - private void verifySelection() { - Rect selectionArea = getSelectionArea(); - for (TestHost.Item item: mHost.items) { - if (Rect.intersects(selectionArea, item.rect)) { - assertTrue("Expected item " + item + " was not in selection " + selectionArea, - mLastSelection.contains(item.name)); - } else { - assertFalse("Unexpected item " + item + " in selection" + selectionArea, - mLastSelection.contains(item.name)); - } - } - } - - private void startSelection(Point p) { - mModel.startCapturing(p); - mSelectionOrigin = mHost.createAbsolutePoint(p); - } - - private void resizeSelection(Point p) { - mModel.resizeSelection(p); - mSelectionPoint = mHost.createAbsolutePoint(p); - } - - private void scroll(int dy) { - assertTrue(mHost.verticalOffset + VIEWPORT_HEIGHT + dy <= mHost.getTotalHeight()); - mHost.verticalOffset += dy; - // Correct the cached selection point as well. - mSelectionPoint.y += dy; - mHost.mScrollListener.onScrolled(null, 0, dy); - } - - private static final class TestHost extends BandHost { - - private final int mNumColumns; - private final int mNumRows; - private final int mNumChildren; - private final int mSeparatorPosition; - - public int horizontalOffset = 0; - public int verticalOffset = 0; - private List<Item> items = new ArrayList<>(); - - // Installed by GridModel on construction. - private @Nullable OnScrollListener mScrollListener; - - public TestHost(int numChildren, int numColumns) { - mNumChildren = numChildren; - mNumColumns = numColumns; - mSeparatorPosition = mNumColumns + 1; - mNumRows = setupGrid(); - } - - private int setupGrid() { - // Split the input set into folders and documents. Do this such that there is a - // partially-populated row in the middle of the grid, to test corner cases in layout - // code. - int y = VIEW_PADDING_PX; - int i = 0; - int numRows = 0; - while (i < mNumChildren) { - int top = y; - int height = CHILD_VIEW_EDGE_PX; - int width = CHILD_VIEW_EDGE_PX; - for (int j = 0; j < mNumColumns && i < mNumChildren; j++) { - int left = VIEW_PADDING_PX + (j * (width + VIEW_PADDING_PX)); - items.add(new Item( - Integer.toString(i), - new Rect( - left, - top, - left + width - 1, - top + height - 1))); - - // Create a partially populated row at the separator position. - if (++i == mSeparatorPosition) { - break; - } - } - y += height + VIEW_PADDING_PX; - numRows++; - } - - return numRows; - } - - private int getTotalHeight() { - return CHILD_VIEW_EDGE_PX * mNumRows + VIEW_PADDING_PX * (mNumRows + 1); - } - - private int getFirstVisibleRowIndex() { - return verticalOffset / (CHILD_VIEW_EDGE_PX + VIEW_PADDING_PX); - } - - private int getLastVisibleRowIndex() { - int lastVisibleRowUncapped = - (VIEWPORT_HEIGHT + verticalOffset - 1) / (CHILD_VIEW_EDGE_PX + VIEW_PADDING_PX); - return Math.min(lastVisibleRowUncapped, mNumRows - 1); - } - - private int getNumItemsInRow(int index) { - assertTrue(index >= 0 && index < mNumRows); - int mod = mSeparatorPosition % mNumColumns; - if (index == (mSeparatorPosition / mNumColumns)) { - // The row containing the separator may be incomplete - return mod > 0 ? mod : mNumColumns; - } - // Account for the partial separator row in the final row tally. - if (index == mNumRows - 1) { - // The last row may be incomplete - int finalRowCount = (mNumChildren - mod) % mNumColumns; - return finalRowCount > 0 ? finalRowCount : mNumColumns; - } - - return mNumColumns; - } - - @Override - public void addOnScrollListener(OnScrollListener listener) { - mScrollListener = listener; - } - - @Override - public void removeOnScrollListener(OnScrollListener listener) {} - - @Override - public Point createAbsolutePoint(Point relativePoint) { - return new Point( - relativePoint.x + horizontalOffset, relativePoint.y + verticalOffset); - } - - @Override - public int getVisibleChildCount() { - int childCount = 0; - for (int i = getFirstVisibleRowIndex(); i <= getLastVisibleRowIndex(); i++) { - childCount += getNumItemsInRow(i); - } - return childCount; - } - - @Override - public int getAdapterPositionAt(int index) { - // Account for partial rows by actually tallying up the items in hidden rows. - int hiddenCount = 0; - for (int i = 0; i < getFirstVisibleRowIndex(); i++) { - hiddenCount += getNumItemsInRow(i); - } - return index + hiddenCount; - } - - @Override - public Rect getAbsoluteRectForChildViewAt(int index) { - int adapterPosition = getAdapterPositionAt(index); - return items.get(adapterPosition).rect; - } - - @Override - public int getChildCount() { - return mNumChildren; - } - - @Override - public int getColumnCount() { - return mNumColumns; - } - - @Override - public void showBand(Rect rect) { - throw new UnsupportedOperationException(); - } - - @Override - public void hideBand() { - throw new UnsupportedOperationException(); - } - - @Override - public void scrollBy(int dy) { - throw new UnsupportedOperationException(); - } - - @Override - public int getHeight() { - throw new UnsupportedOperationException(); - } - - @Override - public void invalidateView() { - throw new UnsupportedOperationException(); - } - - @Override - public void runAtNextFrame(Runnable r) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeCallback(Runnable r) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasView(int adapterPosition) { - return true; - } - - public static final class Item { - public String name; - public Rect rect; - - public Item(String n, Rect r) { - name = n; - rect = r; - } - - @Override - public String toString() { - return name + ": " + rect; - } - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/MouseInputHandlerTest.java b/tests/unit/com/android/documentsui/selection/MouseInputHandlerTest.java deleted file mode 100644 index c4d66d866..000000000 --- a/tests/unit/com/android/documentsui/selection/MouseInputHandlerTest.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static com.android.documentsui.testing.TestEvents.Mouse.ALT_CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.CTRL_CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.SECONDARY_CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.SHIFT_CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.TERTIARY_CLICK; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; - -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.testing.SelectionHelpers; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestData; -import com.android.documentsui.selection.testing.TestEvents; -import com.android.documentsui.selection.testing.TestMouseCallbacks; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class MouseInputHandlerTest { - - private static final List<String> ITEMS = TestData.create(100); - - private MouseInputHandler mInputDelegate; - private TestMouseCallbacks mCallbacks; - private TestItemDetailsLookup mDetailsLookup; - private SelectionProbe mSelection; - private SelectionHelper mSelectionMgr; - - private TestEvents.Builder mEvent; - - @Before - public void setUp() { - - mSelectionMgr = SelectionHelpers.createTestInstance(ITEMS); - mDetailsLookup = new TestItemDetailsLookup(); - mSelection = new SelectionProbe(mSelectionMgr); - - mCallbacks = new TestMouseCallbacks(); - mInputDelegate = new MouseInputHandler(mSelectionMgr, mDetailsLookup, mCallbacks); - - mEvent = TestEvents.builder().mouse(); - mDetailsLookup.initAt(RecyclerView.NO_POSITION); - } - - @Test - public void testConfirmedClick_StartsSelection() { - mDetailsLookup.initAt(11).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mSelection.assertSelection(11); - } - - @Test - public void testClickOnSelectRegion_AddsToSelection() { - mDetailsLookup.initAt(11).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(10).setInItemSelectRegion(true); - mInputDelegate.onSingleTapUp(CLICK); - - mSelection.assertSelected(10, 11); - } - - @Test - public void testClickOnIconOfSelectedItem_RemovesFromSelection() { - mDetailsLookup.initAt(8).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - mSelection.assertSelected(8, 9, 10, 11); - - mDetailsLookup.initAt(9); - mInputDelegate.onSingleTapUp(CLICK); - mSelection.assertSelected(8, 10, 11); - } - - @Test - public void testRightClickDown_StartsContextMenu() { - mInputDelegate.onDown(SECONDARY_CLICK); - - mCallbacks.assertLastEvent(SECONDARY_CLICK); - } - - @Test - public void testAltClickDown_StartsContextMenu() { - mInputDelegate.onDown(ALT_CLICK); - - mCallbacks.assertLastEvent(ALT_CLICK); - } - - @Test - public void testScroll_shouldTrap() { - mDetailsLookup.initAt(0); - assertTrue(mInputDelegate.onScroll( - null, - mEvent.action(MotionEvent.ACTION_MOVE).primary().build(), - -1, - -1)); - } - - @Test - public void testScroll_NoTrapForTwoFinger() { - mDetailsLookup.initAt(0); - assertFalse(mInputDelegate.onScroll( - null, - mEvent.action(MotionEvent.ACTION_MOVE).build(), - -1, - -1)); - } - - @Test - public void testUnconfirmedCtrlClick_AddsToExistingSelection() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(CTRL_CLICK); - - mSelection.assertSelection(7, 11); - } - - @Test - public void testUnconfirmedShiftClick_ExtendsSelection() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertSelection(7, 8, 9, 10, 11); - } - - @Test - public void testConfirmedShiftClick_ExtendsSelectionFromOriginFocus() { - TestItemDetails item = mDetailsLookup.initAt(7); - mCallbacks.focusItem(item); - - // This is a hack-y test, since the real FocusManager would've set range begin itself. - mSelectionMgr.anchorRange(7); - mSelection.assertNoSelection(); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapConfirmed(SHIFT_CLICK); - mSelection.assertSelection(7, 8, 9, 10, 11); - } - - @Test - public void testUnconfirmedShiftClick_RotatesAroundOrigin() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - mSelection.assertSelection(7, 8, 9, 10, 11); - - mDetailsLookup.initAt(5); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertSelection(5, 6, 7); - mSelection.assertNotSelected(8, 9, 10, 11); - } - - @Test - public void testUnconfirmedShiftCtrlClick_Combination() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - mSelection.assertSelection(7, 8, 9, 10, 11); - - mDetailsLookup.initAt(5); - mInputDelegate.onSingleTapUp(CTRL_CLICK); - - mSelection.assertSelection(5, 7, 8, 9, 10, 11); - } - - @Test - public void testUnconfirmedShiftCtrlClick_ShiftTakesPriority() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(mEvent.ctrl().shift().build()); - - mSelection.assertSelection(7, 8, 9, 10, 11); - } - - // TODO: Add testSpaceBar_Previews, but we need to set a system property - // to have a deterministic state. - - @Test - public void testDoubleClick_Opens() { - TestItemDetails doc = mDetailsLookup.initAt(11); - mInputDelegate.onDoubleTap(CLICK); - - mCallbacks.assertActivated(doc); - } - - @Test - public void testMiddleClick_DoesNothing() { - mDetailsLookup.initAt(11).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(TERTIARY_CLICK); - - mSelection.assertNoSelection(); - } - - @Test - public void testClickOff_ClearsSelection() { - mDetailsLookup.initAt(11).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(RecyclerView.NO_POSITION); - mInputDelegate.onSingleTapUp(CLICK); - - mSelection.assertNoSelection(); - } - - @Test - public void testClick_Focuses() { - mDetailsLookup.initAt(11).setInItemSelectRegion(false); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mCallbacks.assertHasFocus(true); - mCallbacks.assertFocused("11"); - } - - @Test - public void testClickOff_ClearsFocus() { - mDetailsLookup.initAt(11).setInItemSelectRegion(false); - mInputDelegate.onSingleTapConfirmed(CLICK); - mCallbacks.assertHasFocus(true); - - mDetailsLookup.initAt(RecyclerView.NO_POSITION); - mInputDelegate.onSingleTapUp(CLICK); - mCallbacks.assertHasFocus(false); - } - - @Test - public void testClickOffSelection_RemovesSelectionAndFocuses() { - mDetailsLookup.initAt(1).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(5); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertSelection(1, 2, 3, 4, 5); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(CLICK); - - mCallbacks.assertFocused("11"); - mSelection.assertNoSelection(); - } -} diff --git a/tests/unit/com/android/documentsui/selection/MouseInputHandler_RangeTest.java b/tests/unit/com/android/documentsui/selection/MouseInputHandler_RangeTest.java deleted file mode 100644 index 939e77892..000000000 --- a/tests/unit/com/android/documentsui/selection/MouseInputHandler_RangeTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static com.android.documentsui.testing.TestEvents.Mouse.CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.SECONDARY_CLICK; -import static com.android.documentsui.testing.TestEvents.Mouse.SHIFT_CLICK; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.testing.SelectionHelpers; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestData; -import com.android.documentsui.selection.testing.TestMouseCallbacks; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -/** - * MouseInputDelegate / SelectHelper integration test covering the shared - * responsibility of range selection. - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class MouseInputHandler_RangeTest { - - private static final List<String> ITEMS = TestData.create(100); - - private MouseInputHandler mInputDelegate; - private SelectionHelper mSelectionMgr; - private SelectionProbe mSelection; - private TestMouseCallbacks mCallbacks; - private TestItemDetailsLookup mDetailsLookup; - - @Before - public void setUp() { - mSelectionMgr = SelectionHelpers.createTestInstance(ITEMS); - mDetailsLookup = new TestItemDetailsLookup(); - mSelection = new SelectionProbe(mSelectionMgr); - - mCallbacks = new TestMouseCallbacks(); - mInputDelegate = new MouseInputHandler(mSelectionMgr, mDetailsLookup, mCallbacks); - } - - @Test - public void testExtendRange() { - // uni-click just focuses. - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(7, 11); - } - - @Test - public void testExtendRangeContinues() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mDetailsLookup.initAt(21); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(7, 21); - } - - @Test - public void testMultipleContiguousRanges() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - // click without shift sets a new range start point. - TestItemDetails item = mDetailsLookup.initAt(20); - mInputDelegate.onSingleTapUp(CLICK); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mCallbacks.focusItem(item); - - mDetailsLookup.initAt(25); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - mInputDelegate.onSingleTapConfirmed(SHIFT_CLICK); - - mSelection.assertRangeNotSelected(7, 11); - mSelection.assertRangeSelected(20, 25); - mSelection.assertSelectionSize(6); - } - - @Test - public void testReducesSelectionRange() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(17); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mDetailsLookup.initAt(10); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(7, 10); - } - - @Test - public void testReducesSelectionRange_Reverse() { - mDetailsLookup.initAt(17).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(7); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mDetailsLookup.initAt(14); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(14, 17); - } - - @Test - public void testExtendsRange_Reverse() { - mDetailsLookup.initAt(12).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(5); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(5, 12); - } - - @Test - public void testExtendsRange_ReversesAfterForwardClick() { - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapConfirmed(CLICK); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mDetailsLookup.initAt(0); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(0, 7); - } - - @Test - public void testRightClickEstablishesRange() { - - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onDown(SECONDARY_CLICK); - // This next method call simulates the behavior of the system event dispatch code. - // UserInputHandler depends on a specific sequence of events for internal - // state to remain valid. It's not an awesome arrangement, but it is currently - // necessary. - // - // See: UserInputHandler.MouseDelegate#mHandledOnDown; - mInputDelegate.onSingleTapUp(SECONDARY_CLICK); - - mDetailsLookup.initAt(11); - // Now we can send a subsequent event that should extend selection. - mInputDelegate.onDown(SHIFT_CLICK); - mInputDelegate.onSingleTapUp(SHIFT_CLICK); - - mSelection.assertRangeSelection(7, 11); - } -} diff --git a/tests/unit/com/android/documentsui/selection/SelectionTest.java b/tests/unit/com/android/documentsui/selection/SelectionTest.java deleted file mode 100644 index fcf525bba..000000000 --- a/tests/unit/com/android/documentsui/selection/SelectionTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.documentsui.selection; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.google.common.collect.Sets; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashSet; -import java.util.Set; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class SelectionTest { - - private Selection selection; - - private String[] ids = new String[]{ - "foo", - "43", - "auth|id=@53di*/f3#d" - }; - - @Before - public void setUp() throws Exception { - selection = new Selection(); - selection.add(ids[0]); - selection.add(ids[1]); - selection.add(ids[2]); - } - - @Test - public void testAdd() { - // We added in setUp. - assertEquals(3, selection.size()); - assertContains(ids[0]); - assertContains(ids[1]); - assertContains(ids[2]); - } - - @Test - public void testRemove() { - selection.remove(ids[0]); - selection.remove(ids[2]); - assertEquals(1, selection.size()); - assertContains(ids[1]); - } - - @Test - public void testClear() { - selection.clear(); - assertEquals(0, selection.size()); - } - - @Test - public void testIsEmpty() { - assertTrue(new Selection().isEmpty()); - selection.clear(); - assertTrue(selection.isEmpty()); - } - - @Test - public void testSize() { - Selection other = new Selection(); - for (int i = 0; i < selection.size(); i++) { - other.add(ids[i]); - } - assertEquals(selection.size(), other.size()); - } - - @Test - public void testEqualsSelf() { - assertEquals(selection, selection); - } - - @Test - public void testEqualsOther() { - Selection other = new Selection(); - other.add(ids[0]); - other.add(ids[1]); - other.add(ids[2]); - assertEquals(selection, other); - assertEquals(selection.hashCode(), other.hashCode()); - } - - @Test - public void testEqualsCopy() { - Selection other = new Selection(); - other.copyFrom(selection); - assertEquals(selection, other); - assertEquals(selection.hashCode(), other.hashCode()); - } - - @Test - public void testNotEquals() { - Selection other = new Selection(); - other.add("foobar"); - assertFalse(selection.equals(other)); - } - - @Test - public void testIntersection_empty0() { - Selection testSelection = new Selection(); - testSelection.intersect(new HashSet<String>()); - assertTrue(testSelection.isEmpty()); - } - - @Test - public void testIntersection_empty1() { - Selection testSelection = new Selection(); - testSelection.intersect(Sets.newHashSet("foo")); - assertTrue(testSelection.isEmpty()); - } - - @Test - public void testIntersection_empty2() { - assertFalse(selection.isEmpty()); - selection.intersect(new HashSet<String>()); - assertTrue(selection.isEmpty()); - } - - @Test - public void testIntersection_exclusive() { - String[] ids0 = new String[]{"foo", "bar", "baz"}; - String[] ids1 = new String[]{"0", "1", "2"}; - - Selection testSelection = new Selection(); - testSelection.add(ids0[0]); - testSelection.add(ids0[1]); - testSelection.add(ids0[2]); - - Set<String> set = Sets.newHashSet(ids1); - testSelection.intersect(set); - - assertTrue(testSelection.isEmpty()); - } - - @Test - public void testIntersection_subset() { - String[] ids0 = new String[]{"foo", "bar", "baz"}; - String[] ids1 = new String[]{"0", "baz", "1", "foo", "2"}; - - Selection testSelection = new Selection(); - testSelection.add(ids0[0]); - testSelection.add(ids0[1]); - testSelection.add(ids0[2]); - - testSelection.intersect(Sets.newHashSet(ids1)); - - assertTrue(testSelection.contains("foo")); - assertFalse(testSelection.contains("bar")); - assertTrue(testSelection.contains("baz")); - } - - @Test - public void testIntersection_all() { - String[] ids0 = new String[]{"foo", "bar", "baz"}; - String[] ids1 = new String[]{"0", "baz", "1", "foo", "2", "bar"}; - - Selection testSelection = new Selection(); - testSelection.add(ids0[0]); - testSelection.add(ids0[1]); - testSelection.add(ids0[2]); - - Selection control = new Selection(); - control.copyFrom(testSelection); - - testSelection.intersect(Sets.newHashSet(ids1)); - - assertTrue(testSelection.equals(control)); - } - - private void assertContains(String id) { - String err = String.format("Selection %s does not contain %s", selection, id); - assertTrue(err, selection.contains(id)); - } -} diff --git a/tests/unit/com/android/documentsui/selection/TouchInputHandlerTest.java b/tests/unit/com/android/documentsui/selection/TouchInputHandlerTest.java deleted file mode 100644 index 919f42564..000000000 --- a/tests/unit/com/android/documentsui/selection/TouchInputHandlerTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection; - -import static com.android.documentsui.testing.TestEvents.Touch.TAP; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import androidx.annotation.Nullable; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; - -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; -import com.android.documentsui.selection.TouchInputHandler.Callbacks; -import com.android.documentsui.selection.testing.SelectionHelpers; -import com.android.documentsui.selection.testing.SelectionProbe; -import com.android.documentsui.selection.testing.TestData; -import com.android.documentsui.selection.testing.TestSelectionPredicate; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class TouchInputHandlerTest { - - private static final List<String> ITEMS = TestData.create(100); - - private TouchInputHandler mInputDelegate; - private SelectionHelper mSelectionMgr; - private TestSelectionPredicate mSelectionPredicate; - private TestRunnable mGestureStarted; - private TestCallbacks mCallbacks; - private TestItemDetailsLookup mDetailsLookup; - private SelectionProbe mSelection; - - @Before - public void setUp() { - mSelectionMgr = SelectionHelpers.createTestInstance(ITEMS); - mDetailsLookup = new TestItemDetailsLookup(); - mSelectionPredicate = new TestSelectionPredicate(); - mSelection = new SelectionProbe(mSelectionMgr); - mGestureStarted = new TestRunnable(); - mCallbacks = new TestCallbacks(); - - mInputDelegate = new TouchInputHandler( - mSelectionMgr, - mDetailsLookup, - mSelectionPredicate, - mGestureStarted, - mCallbacks); - } - - @Test - public void testTap_ActivatesWhenNoExistingSelection() { - ItemDetails doc = mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(TAP); - - mCallbacks.assertActivated(doc); - } - - @Test - public void testScroll_shouldNotBeTrapped() { - assertFalse(mInputDelegate.onScroll(null, TAP, -1, -1)); - } - - @Test - public void testLongPress_SelectsItem() { - mSelectionPredicate.setReturnValue(true); - - mDetailsLookup.initAt(7); - mInputDelegate.onLongPress(TAP); - - mSelection.assertSelection(7); - } - - @Test - public void testLongPress_StartsGestureSelection() { - mSelectionPredicate.setReturnValue(true); - - mDetailsLookup.initAt(7); - mInputDelegate.onLongPress(TAP); - mGestureStarted.assertRan(); - } - - @Test - public void testSelectHotspot_StartsSelectionMode() { - mSelectionPredicate.setReturnValue(true); - - mDetailsLookup.initAt(7).setInItemSelectRegion(true); - mInputDelegate.onSingleTapUp(TAP); - - mSelection.assertSelection(7); - } - - @Test - public void testSelectionHotspot_UnselectsSelectedItem() { - mSelectionMgr.select("11"); - - mDetailsLookup.initAt(11).setInItemSelectRegion(true); - mInputDelegate.onSingleTapUp(TAP); - - mSelection.assertNoSelection(); - } - - @Test - public void testStartsSelection_PerformsHapticFeedback() { - mSelectionPredicate.setReturnValue(true); - - mDetailsLookup.initAt(7); - mInputDelegate.onLongPress(TAP); - - mCallbacks.assertVibrated(); - } - - @Test - public void testLongPress_AddsToSelection() { - mSelectionPredicate.setReturnValue(true); - - mDetailsLookup.initAt(7); - mInputDelegate.onLongPress(TAP); - - mDetailsLookup.initAt(99); - mInputDelegate.onLongPress(TAP); - - mDetailsLookup.initAt(13); - mInputDelegate.onLongPress(TAP); - - mSelection.assertSelection(7, 13, 99); - } - - @Test - public void testTap_UnselectsSelectedItem() { - mSelectionMgr.select("11"); - - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(TAP); - - mSelection.assertNoSelection(); - } - - @Test - public void testTapOff_ClearsSelection() { - mSelectionMgr.select("7"); - mDetailsLookup.initAt(7); - - mInputDelegate.onLongPress(TAP); - - mSelectionMgr.select("11"); - mDetailsLookup.initAt(11); - mInputDelegate.onSingleTapUp(TAP); - - mDetailsLookup.initAt(RecyclerView.NO_POSITION).setInItemSelectRegion(false); - mInputDelegate.onSingleTapUp(TAP); - - mSelection.assertNoSelection(); - } - - private static final class TestCallbacks extends TouchInputHandler.Callbacks { - - private @Nullable ItemDetails mActivated; - private boolean mVibrated; - - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - mActivated = item; - return false; - } - - @Override - public boolean onDragInitiated(MotionEvent e) { - return false; - } - - @Override - public void onPerformHapticFeedback() { - mVibrated = true; - } - - private void assertActivated(ItemDetails expected) { - assertEquals(expected, mActivated); - } - - private void assertVibrated() { - assertTrue(mVibrated); - } - } - - private static final class TestRunnable implements Runnable { - - private boolean mWasRun; - - @Override - public void run() { - mWasRun = true; - } - - void assertRan() { - assertTrue(mWasRun); - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/SelectionHelpers.java b/tests/unit/com/android/documentsui/selection/testing/SelectionHelpers.java deleted file mode 100644 index 6bdd18a2c..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/SelectionHelpers.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection.testing; - -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.DefaultSelectionHelper.SelectionMode; -import com.android.documentsui.selection.SelectionHelper; -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; - -import java.util.Collections; -import java.util.List; - -public class SelectionHelpers { - - public static final SelectionPredicate CAN_SET_ANYTHING = new SelectionPredicate() { - @Override - public boolean canSetStateForId(String id, boolean nextState) { - return true; - } - - @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - return true; - } - }; - - private SelectionHelpers() {} - - public static SelectionHelper createTestInstance() { - return createTestInstance(Collections.emptyList()); - } - - public static SelectionHelper createTestInstance(List<String> docs) { - return createTestInstance(docs, DefaultSelectionHelper.MODE_MULTIPLE); - } - - public static SelectionHelper createTestInstance( - List<String> items, @SelectionMode int mode) { - return createTestInstance(new TestAdapter(items), mode, CAN_SET_ANYTHING); - } - - public static SelectionHelper createTestInstance( - TestAdapter adapter, @SelectionMode int mode, SelectionPredicate selPredicate) { - return new DefaultSelectionHelper( - mode, adapter, new TestStableIdProvider(adapter), selPredicate); - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/SelectionPredicates.java b/tests/unit/com/android/documentsui/selection/testing/SelectionPredicates.java deleted file mode 100644 index 020091922..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/SelectionPredicates.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; - -/** - * Test friendly {@link SelectionPredicate} instances. - */ -public final class SelectionPredicates { - - private SelectionPredicates() {} - - public static final SelectionPredicate CAN_SET_ANYTHING = new SelectionPredicate() { - @Override - public boolean canSetStateForId(String id, boolean nextState) { - return true; - } - - @Override - public boolean canSetStateAtPosition(int position, boolean nextState) { - return true; - } - }; -} diff --git a/tests/unit/com/android/documentsui/selection/testing/SelectionProbe.java b/tests/unit/com/android/documentsui/selection/testing/SelectionProbe.java deleted file mode 100644 index 4d5790bf0..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/SelectionProbe.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection.testing; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import com.android.documentsui.selection.DefaultSelectionHelper; -import com.android.documentsui.selection.Selection; -import com.android.documentsui.selection.SelectionHelper; - -/** - * Helper class for making assertions against the state of a {@link DefaultSelectionHelper} instance - * and the consistency of states between {@link DefaultSelectionHelper} and - * {@link DefaultSelectionHelper.ItemEventCallback}. - */ -public final class SelectionProbe { - - private final SelectionHelper mMgr; - private final TestSelectionObserver mSelectionListener; - - public SelectionProbe(SelectionHelper mgr) { - mMgr = mgr; - mSelectionListener = new TestSelectionObserver(); - mMgr.addObserver(mSelectionListener); - } - - public SelectionProbe(SelectionHelper mgr, TestSelectionObserver selectionListener) { - mMgr = mgr; - mSelectionListener = selectionListener; - } - - public void assertRangeSelected(int begin, int end) { - for (int i = begin; i <= end; i++) { - assertSelected(i); - } - } - - public void assertRangeNotSelected(int begin, int end) { - for (int i = begin; i <= end; i++) { - assertNotSelected(i); - } - } - - public void assertRangeSelection(int begin, int end) { - assertSelectionSize(end - begin + 1); - assertRangeSelected(begin, end); - } - - public void assertSelectionSize(int expected) { - Selection selection = mMgr.getSelection(); - assertEquals(selection.toString(), expected, selection.size()); - - mSelectionListener.assertSelectionSize(expected); - } - - public void assertNoSelection() { - assertSelectionSize(0); - - mSelectionListener.assertNoSelection(); - } - - public void assertSelection(int... ids) { - assertSelected(ids); - assertEquals(ids.length, mMgr.getSelection().size()); - - mSelectionListener.assertSelectionSize(ids.length); - } - - public void assertSelected(int... ids) { - Selection sel = mMgr.getSelection(); - for (int id : ids) { - String sid = String.valueOf(id); - assertTrue(sid + " is not in selection " + sel, sel.contains(sid)); - - mSelectionListener.assertSelected(sid); - } - } - - public void assertNotSelected(int... ids) { - Selection sel = mMgr.getSelection(); - for (int id : ids) { - String sid = String.valueOf(id); - assertFalse(sid + " is in selection " + sel, sel.contains(sid)); - - mSelectionListener.assertNotSelected(sid); - } - } - - public void select(int...positions) { - for (int position : positions) { - mMgr.select(String.valueOf(position)); - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestAdapter.java b/tests/unit/com/android/documentsui/selection/testing/TestAdapter.java deleted file mode 100644 index 4a2d3e542..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestAdapter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import static org.junit.Assert.assertTrue; - -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; -import android.view.ViewGroup; - -import com.android.documentsui.selection.SelectionHelper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class TestAdapter extends Adapter<TestHolder> { - - private final List<String> mItems = new ArrayList<>(); - private final List<Integer> mNotifiedOfSelection = new ArrayList<>(); - private final AdapterDataObserver mAdapterObserver; - - public TestAdapter() { - this(Collections.EMPTY_LIST); - } - - public TestAdapter(List<String> items) { - mItems.addAll(items); - mAdapterObserver = new RecyclerView.AdapterDataObserver() { - - @Override - public void onChanged() { - } - - @Override - public void onItemRangeChanged(int startPosition, int itemCount, Object payload) { - if (SelectionHelper.SELECTION_CHANGED_MARKER.equals(payload)) { - int last = startPosition + itemCount; - for (int i = startPosition; i < last; i++) { - mNotifiedOfSelection.add(i); - } - } - } - - @Override - public void onItemRangeInserted(int startPosition, int itemCount) { - } - - @Override - public void onItemRangeRemoved(int startPosition, int itemCount) { - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - throw new UnsupportedOperationException(); - } - }; - - registerAdapterDataObserver(mAdapterObserver); - } - - @Override - public TestHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new TestHolder(parent); - } - - @Override - public void onBindViewHolder(TestHolder holder, int position) { - throw new UnsupportedOperationException(); - } - - @Override - public int getItemCount() { - return mItems.size(); - } - - public void updateTestModelIds(List<String> items) { - mItems.clear(); - mItems.addAll(items); - - notifyDataSetChanged(); - } - - public List<String> getStableIds() { - return mItems; - } - - public int getPosition(String id) { - return mItems.indexOf(id); - } - - public String getStableId(int position) { - return mItems.get(position); - } - - - public void resetSelectionNotifications() { - mNotifiedOfSelection.clear(); - } - - public void assertNotifiedOfSelectionChange(int position) { - assertTrue(mNotifiedOfSelection.contains(position)); - } - - public static List<String> createItemList(int num) { - List<String> items = new ArrayList<>(num); - for (int i = 0; i < num; ++i) { - items.add(Integer.toString(i)); - } - return items; - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestBandPredicate.java b/tests/unit/com/android/documentsui/selection/testing/TestBandPredicate.java deleted file mode 100644 index 2e901a013..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestBandPredicate.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import android.view.MotionEvent; - -import com.android.documentsui.selection.BandPredicate; - -public class TestBandPredicate extends BandPredicate { - - private boolean mCanInitiate = true; - - public void setCanInitiate(boolean canInitiate) { - mCanInitiate = canInitiate; - } - - @Override - public boolean canInitiate(MotionEvent e) { - return mCanInitiate; - } - -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestData.java b/tests/unit/com/android/documentsui/selection/testing/TestData.java deleted file mode 100644 index 1a0599072..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestData.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection.testing; - -import java.util.ArrayList; -import java.util.List; - -public class TestData { - public static List<String> create(int num) { - List<String> items = new ArrayList<String>(num); - for (int i = 0; i < num; ++i) { - items.add(Integer.toString(i)); - } - return items; - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestEvents.java b/tests/unit/com/android/documentsui/selection/testing/TestEvents.java deleted file mode 100644 index 828704ffc..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestEvents.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2016 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 com.android.documentsui.selection.testing; - -import androidx.annotation.IntDef; -import android.graphics.Point; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.HashSet; -import java.util.Set; - -/** - * Handy-dandy wrapper class to facilitate the creation of MotionEvents. - */ -public final class TestEvents { - - /** - * Common mouse event types...for your convenience. - */ - public static final class Mouse { - public static final MotionEvent CLICK = - TestEvents.builder().mouse().primary().build(); - public static final MotionEvent CTRL_CLICK = - TestEvents.builder().mouse().primary().ctrl().build(); - public static final MotionEvent ALT_CLICK = - TestEvents.builder().mouse().primary().alt().build(); - public static final MotionEvent SHIFT_CLICK = - TestEvents.builder().mouse().primary().shift().build(); - public static final MotionEvent SECONDARY_CLICK = - TestEvents.builder().mouse().secondary().build(); - public static final MotionEvent TERTIARY_CLICK = - TestEvents.builder().mouse().tertiary().build(); - } - - /** - * Common touch event types...for your convenience. - */ - public static final class Touch { - public static final MotionEvent TAP = - TestEvents.builder().touch().build(); - } - - static final int ACTION_UNSET = -1; - - // Add other actions from MotionEvent.ACTION_ as needed. - @IntDef(flag = true, value = { - MotionEvent.ACTION_DOWN, - MotionEvent.ACTION_MOVE, - MotionEvent.ACTION_UP - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Action {} - - // Add other types from MotionEvent.TOOL_TYPE_ as needed. - @IntDef(flag = true, value = { - MotionEvent.TOOL_TYPE_FINGER, - MotionEvent.TOOL_TYPE_MOUSE, - MotionEvent.TOOL_TYPE_STYLUS, - MotionEvent.TOOL_TYPE_UNKNOWN - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ToolType {} - - @IntDef(flag = true, value = { - MotionEvent.BUTTON_PRIMARY, - MotionEvent.BUTTON_SECONDARY - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Button {} - - @IntDef(flag = true, value = { - KeyEvent.META_SHIFT_ON, - KeyEvent.META_CTRL_ON - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Key {} - - private static final class State { - private @Action int mAction = ACTION_UNSET; - private @ToolType int mToolType = MotionEvent.TOOL_TYPE_UNKNOWN; - private int mPointerCount = 1; - private Set<Integer> mButtons = new HashSet<>(); - private Set<Integer> mKeys = new HashSet<>(); - private Point mLocation = new Point(0, 0); - private Point mRawLocation = new Point(0, 0); - } - - public static final Builder builder() { - return new Builder(); - } - - /** - * Test event builder with convenience methods for common event attrs. - */ - public static final class Builder { - - private State mState = new State(); - - /** - * @param action Any action specified in {@link MotionEvent}. - * @return - */ - public Builder action(int action) { - mState.mAction = action; - return this; - } - - public Builder type(@ToolType int type) { - mState.mToolType = type; - return this; - } - - public Builder location(int x, int y) { - mState.mLocation = new Point(x, y); - return this; - } - - public Builder rawLocation(int x, int y) { - mState.mRawLocation = new Point(x, y); - return this; - } - - public Builder pointerCount(int count) { - mState.mPointerCount = count; - return this; - } - - /** - * Adds one or more button press attributes. - */ - public Builder pressButton(@Button int... buttons) { - for (int button : buttons) { - mState.mButtons.add(button); - } - return this; - } - - /** - * Removes one or more button press attributes. - */ - public Builder releaseButton(@Button int... buttons) { - for (int button : buttons) { - mState.mButtons.remove(button); - } - return this; - } - - /** - * Adds one or more key press attributes. - */ - public Builder pressKey(@Key int... keys) { - for (int key : keys) { - mState.mKeys.add(key); - } - return this; - } - - /** - * Removes one or more key press attributes. - */ - public Builder releaseKey(@Button int... keys) { - for (int key : keys) { - mState.mKeys.remove(key); - } - return this; - } - - public Builder touch() { - type(MotionEvent.TOOL_TYPE_FINGER); - return this; - } - - public Builder mouse() { - type(MotionEvent.TOOL_TYPE_MOUSE); - return this; - } - - public Builder shift() { - pressKey(KeyEvent.META_SHIFT_ON); - return this; - } - - /** - * Use {@link #remove(@Attribute int...)} - */ - public Builder unshift() { - releaseKey(KeyEvent.META_SHIFT_ON); - return this; - } - - public Builder ctrl() { - pressKey(KeyEvent.META_CTRL_ON); - return this; - } - - public Builder alt() { - pressKey(KeyEvent.META_ALT_ON); - return this; - } - - public Builder primary() { - pressButton(MotionEvent.BUTTON_PRIMARY); - releaseButton(MotionEvent.BUTTON_SECONDARY); - releaseButton(MotionEvent.BUTTON_TERTIARY); - return this; - } - - public Builder secondary() { - pressButton(MotionEvent.BUTTON_SECONDARY); - releaseButton(MotionEvent.BUTTON_PRIMARY); - releaseButton(MotionEvent.BUTTON_TERTIARY); - return this; - } - - public Builder tertiary() { - pressButton(MotionEvent.BUTTON_TERTIARY); - releaseButton(MotionEvent.BUTTON_PRIMARY); - releaseButton(MotionEvent.BUTTON_SECONDARY); - return this; - } - - public MotionEvent build() { - - PointerProperties[] pointers = new PointerProperties[1]; - pointers[0] = new PointerProperties(); - pointers[0].id = 0; - pointers[0].toolType = mState.mToolType; - - PointerCoords[] coords = new PointerCoords[1]; - coords[0] = new PointerCoords(); - coords[0].x = mState.mLocation.x; - coords[0].y = mState.mLocation.y; - - int buttons = 0; - for (Integer button : mState.mButtons) { - buttons |= button; - } - - int keys = 0; - for (Integer key : mState.mKeys) { - keys |= key; - } - - return MotionEvent.obtain( - 0, // down time - 1, // event time - mState.mAction, - 1, // pointerCount, - pointers, - coords, - keys, - buttons, - 1.0f, // x precision - 1.0f, // y precision - 0, // device id - 0, // edge flags - 0, // int source, - 0 // int flags - ); - } - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestHolder.java b/tests/unit/com/android/documentsui/selection/testing/TestHolder.java deleted file mode 100644 index 071f808a3..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestHolder.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import android.view.View; - -public class TestHolder extends ViewHolder { - public TestHolder(View itemView) { - super(itemView); - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestMouseCallbacks.java b/tests/unit/com/android/documentsui/selection/testing/TestMouseCallbacks.java deleted file mode 100644 index 32b5bfeef..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestMouseCallbacks.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import androidx.recyclerview.widget.RecyclerView; -import android.view.MotionEvent; - -import com.android.documentsui.selection.MouseInputHandler; -import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; - -public final class TestMouseCallbacks extends MouseInputHandler.Callbacks { - - private String mFocusItemId; - private int mFocusPosition; - private MotionEvent mLastContextEvent; - private ItemDetails mActivated; - - @Override - public boolean onItemActivated(ItemDetails item, MotionEvent e) { - mActivated = item; - return true; - } - - @Override - public boolean onContextClick(MotionEvent e) { - mLastContextEvent = e; - return false; - } - - @Override - public void onPerformHapticFeedback() { - } - - @Override - public void clearFocus() { - mFocusPosition = RecyclerView.NO_POSITION; - mFocusItemId = null; - } - - @Override - public void focusItem(ItemDetails item) { - mFocusItemId = item.getStableId(); - mFocusPosition = item.getPosition(); - } - - @Override - public int getFocusedPosition() { - return mFocusPosition; - } - - @Override - public boolean hasFocusedItem() { - return mFocusItemId != null; - } - - public void assertLastEvent(MotionEvent expected) { - // sadly, MotionEvent doesn't implement equals, so we compare references. - assertTrue(expected == mLastContextEvent); - } - - public void assertActivated(ItemDetails expected) { - assertEquals(expected, mActivated); - } - - public void assertHasFocus(boolean focused) { - assertEquals(focused, hasFocusedItem()); - } - - public void assertFocused(String expectedId) { - assertEquals(expectedId, mFocusItemId); - } -}
\ No newline at end of file diff --git a/tests/unit/com/android/documentsui/selection/testing/TestSelectionObserver.java b/tests/unit/com/android/documentsui/selection/testing/TestSelectionObserver.java deleted file mode 100644 index e75bde3de..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestSelectionObserver.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import com.android.documentsui.selection.SelectionHelper.SelectionObserver; - -import java.util.HashSet; -import java.util.Set; - -public class TestSelectionObserver extends SelectionObserver { - - private final Set<String> mSelected = new HashSet<>(); - private boolean mSelectionChanged = false; - private boolean mSelectionReset = false; - private boolean mSelectionRestored = false; - - public void reset() { - mSelected.clear(); - mSelectionChanged = false; - mSelectionReset = false; - } - - @Override - public void onItemStateChanged(String id, boolean selected) { - if (selected) { - assertNotSelected(id); - mSelected.add(id); - } else { - assertSelected(id); - mSelected.remove(id); - } - } - - @Override - public void onSelectionReset() { - mSelectionReset = true; - mSelected.clear(); - } - - @Override - public void onSelectionChanged() { - mSelectionChanged = true; - } - - @Override - public void onSelectionRestored() { - mSelectionRestored = true; - } - - void assertNoSelection() { - assertTrue(mSelected.isEmpty()); - } - - void assertSelectionSize(int expected) { - assertEquals(expected, mSelected.size()); - } - - void assertSelected(String id) { - assertTrue(id + " is not selected.", mSelected.contains(id)); - } - - void assertNotSelected(String id) { - assertFalse(id + " is already selected", mSelected.contains(id)); - } - - public void assertSelectionChanged() { - assertTrue(mSelectionChanged); - } - - public void assertSelectionUnchanged() { - assertFalse(mSelectionChanged); - } - - public void assertSelectionReset() { - assertTrue(mSelectionReset); - } - - public void assertSelectionRestored() { - assertTrue(mSelectionRestored); - } -} diff --git a/tests/unit/com/android/documentsui/selection/testing/TestStableIdProvider.java b/tests/unit/com/android/documentsui/selection/testing/TestStableIdProvider.java deleted file mode 100644 index 37c4665c8..000000000 --- a/tests/unit/com/android/documentsui/selection/testing/TestStableIdProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 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 com.android.documentsui.selection.testing; - -import static androidx.core.util.Preconditions.checkArgument; - -import com.android.documentsui.selection.SelectionHelper.StableIdProvider; - -import java.util.List; - -/** - * Provides RecyclerView selection code access to stable ids backed - * by TestAdapter. - */ -public final class TestStableIdProvider extends StableIdProvider { - - private final TestAdapter mAdapter; - - public TestStableIdProvider(TestAdapter adapter) { - checkArgument(adapter != null); - mAdapter = adapter; - } - - @Override - public String getStableId(int position) { - return mAdapter.getStableId(position); - } - - @Override - public int getPosition(String id) { - return mAdapter.getPosition(id); - } - - @Override - public List<String> getStableIds() { - return mAdapter.getStableIds(); - } -} |