14f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay/* 24f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Copyright (C) 2015 The Android Open Source Project 34f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 44f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Licensed under the Apache License, Version 2.0 (the "License"); 54f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * you may not use this file except in compliance with the License. 64f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * You may obtain a copy of the License at 74f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 84f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * http://www.apache.org/licenses/LICENSE-2.0 94f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 104f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Unless required by applicable law or agreed to in writing, software 114f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * distributed under the License is distributed on an "AS IS" BASIS, 124f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * See the License for the specific language governing permissions and 144f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * limitations under the License. 154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKaypackage com.android.documentsui.selection; 184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport static com.android.documentsui.base.Shared.DEBUG; 204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport android.annotation.IntDef; 22ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mannimport android.support.annotation.VisibleForTesting; 234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport android.support.v7.widget.RecyclerView; 244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport android.util.Log; 254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport com.android.documentsui.dirlist.DocumentsAdapter; 274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport java.lang.annotation.Retention; 294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport java.lang.annotation.RetentionPolicy; 304f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport java.util.ArrayList; 314f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport java.util.List; 324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport javax.annotation.Nullable; 344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay/** 364f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * MultiSelectManager provides support traditional multi-item selection support to RecyclerView. 374f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Additionally it can be configured to restrict selection to a single element, @see 384f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * #setSelectMode. 394f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 404f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKaypublic final class SelectionManager { 414f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 424f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @IntDef(flag = true, value = { 434f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay MODE_MULTIPLE, 444f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay MODE_SINGLE 454f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay }) 464f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Retention(RetentionPolicy.SOURCE) 474f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public @interface SelectionMode {} 484f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public static final int MODE_MULTIPLE = 0; 494f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public static final int MODE_SINGLE = 1; 504f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 514f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @IntDef({ 524f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay RANGE_REGULAR, 534f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay RANGE_PROVISIONAL 544f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay }) 554f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Retention(RetentionPolicy.SOURCE) 564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public @interface RangeType {} 574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public static final int RANGE_REGULAR = 0; 584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public static final int RANGE_PROVISIONAL = 1; 594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay static final String TAG = "SelectionManager"; 614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private final Selection mSelection = new Selection(); 634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private final List<Callback> mCallbacks = new ArrayList<>(1); 654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private final List<ItemCallback> mItemCallbacks = new ArrayList<>(1); 664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private @Nullable DocumentsAdapter mAdapter; 684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private @Nullable Range mRanger; 694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private boolean mSingleSelect; 704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private RecyclerView.AdapterDataObserver mAdapterObserver; 724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private SelectionPredicate mCanSetState; 734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public SelectionManager(@SelectionMode int mode) { 754f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSingleSelect = mode == MODE_SINGLE; 764f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 774f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 784f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public SelectionManager reset(DocumentsAdapter adapter, SelectionPredicate canSetState) { 794f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mCallbacks.clear(); 814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mItemCallbacks.clear(); 824f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (mAdapter != null && mAdapterObserver != null) { 834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mAdapter.unregisterAdapterDataObserver(mAdapterObserver); 844f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 854f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 864f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay clearSelectionQuietly(); 874f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 884f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(adapter != null); 894f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(canSetState != null); 904f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mAdapter = adapter; 924f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mCanSetState = canSetState; 934f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 944f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mAdapterObserver = new RecyclerView.AdapterDataObserver() { 954f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 964f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private List<String> mModelIds; 974f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 984f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Override 994f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void onChanged() { 1004f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mModelIds = mAdapter.getModelIds(); 1014f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1024f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // Update the selection to remove any disappeared IDs. 1034f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.cancelProvisionalSelection(); 1044f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.intersect(mModelIds); 105ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann 106ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann notifyDataChanged(); 1074f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1084f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1094f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Override 1104f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void onItemRangeChanged( 1114f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay int startPosition, int itemCount, Object payload) { 1124f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // No change in position. Ignoring. 1134f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1144f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Override 1164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void onItemRangeInserted(int startPosition, int itemCount) { 1174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.cancelProvisionalSelection(); 1184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Override 1214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void onItemRangeRemoved(int startPosition, int itemCount) { 1224f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(startPosition >= 0); 1234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(itemCount > 0); 1244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.cancelProvisionalSelection(); 1264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // Remove any disappeared IDs from the selection. 1274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.intersect(mModelIds); 1284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1304f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @Override 1314f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 1324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay throw new UnsupportedOperationException(); 1334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay }; 1354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1364f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mAdapter.registerAdapterDataObserver(mAdapterObserver); 1374f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return this; 1384f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1394f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1404f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void bindContoller(BandController controller) { 1414f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // Provides BandController with access to private mSelection state. 1424f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay controller.bindSelection(mSelection); 1434f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1444f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1454f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 1464f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Adds {@code callback} such that it will be notified when {@code MultiSelectManager} 1474f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * events occur. 1484f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 1494f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param callback 1504f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 1514f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void addCallback(Callback callback) { 1524f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(callback != null); 1534f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mCallbacks.add(callback); 1544f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1554f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void addItemCallback(ItemCallback itemCallback) { 1574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(itemCallback != null); 1584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mItemCallbacks.add(itemCallback); 1594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public boolean hasSelection() { 1624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return !mSelection.isEmpty(); 1634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 1664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Returns a Selection object that provides a live view 1674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * on the current selection. 1684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 1694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @see #getSelection(Selection) on how to get a snapshot 1704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * of the selection that will not reflect future changes 1714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * to selection. 1724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 1734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return The current selection. 1744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 1754f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public Selection getSelection() { 1764f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return mSelection; 1774f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1784f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1794f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 1804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Updates {@code dest} to reflect the current selection. 1814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param dest 1824f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 1834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return The Selection instance passed in, for convenience. 1844f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 1854f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public Selection getSelection(Selection dest) { 1864f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay dest.copyFrom(mSelection); 1874f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return dest; 1884f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1894f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 190ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann @VisibleForTesting 1914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void replaceSelection(Iterable<String> ids) { 1924f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay clearSelection(); 1934f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay setItemsSelected(ids, true); 1944f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 1954f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 1964f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 1974f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Restores the selected state of specified items. Used in cases such as restore the selection 1984f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * after rotation etc. 1994f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2004f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void restoreSelection(Selection other) { 2014f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay setItemsSelectedQuietly(other.mSelection, true); 2024f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // NOTE: We intentionally don't restore provisional selection. It's provisional. 2034f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionRestored(); 2044f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2054f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2064f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 2074f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Sets the selected state of the specified items. Note that the callback will NOT 2084f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * be consulted to see if an item can be selected. 2094f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 2104f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param ids 2114f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param selected 2124f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return 2134f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2144f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public boolean setItemsSelected(Iterable<String> ids, boolean selected) { 2154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay final boolean changed = setItemsSelectedQuietly(ids, selected); 2164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionChanged(); 2174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return changed; 2184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private boolean setItemsSelectedQuietly(Iterable<String> ids, boolean selected) { 2214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean changed = false; 2224f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (String id: ids) { 2234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay final boolean itemChanged = 2244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay selected 2254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay ? canSetState(id, true) && mSelection.add(id) 2264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay : canSetState(id, false) && mSelection.remove(id); 2274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (itemChanged) { 2284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifyItemStateChanged(id, selected); 2294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2304f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay changed |= itemChanged; 2314f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return changed; 2334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 2364f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Clears the selection and notifies (if something changes). 2374f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2384f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void clearSelection() { 2394f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (!hasSelection()) { 2404f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return; 2414f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2424f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2434f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay clearSelectionQuietly(); 2444f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionChanged(); 2454f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2464f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2474f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 2484f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Clears the selection, without notifying selection listeners. UI elements still need to be 2494f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * notified about state changes so that they can update their appearance. 2504f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2514f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private void clearSelectionQuietly() { 2524f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mRanger = null; 2534f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2544f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (!hasSelection()) { 2554f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return; 2564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay Selection oldSelection = getSelection(new Selection()); 2594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.clear(); 2604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (String id: oldSelection.mSelection) { 2624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifyItemStateChanged(id, false); 2634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (String id: oldSelection.mProvisionalSelection) { 2654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifyItemStateChanged(id, false); 2664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 2704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Toggles selection on the item with the given model ID. 2714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 2724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param modelId 2734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void toggleSelection(String modelId) { 2754f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(modelId != null); 2764f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2774f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay final boolean changed = mSelection.contains(modelId) 2784f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay ? attemptDeselect(modelId) 2794f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay : attemptSelect(modelId); 2804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (changed) { 2824f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionChanged(); 2834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2844f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2854f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2864f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 2874f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Starts a range selection. If a range selection is already active, this will start a new range 2884f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * selection (which will reset the range anchor). 2894f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 2904f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param pos The anchor position for the selection range. 2914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 2924f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void startRangeSelection(int pos) { 2934f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay attemptSelect(mAdapter.getModelId(pos)); 2944f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay setSelectionRangeBegin(pos); 2954f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 2964f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 2974f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void snapRangeSelection(int pos) { 2984f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay snapRangeSelection(pos, RANGE_REGULAR); 2994f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3004f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3014f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void snapProvisionalRangeSelection(int pos) { 3024f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay snapRangeSelection(pos, RANGE_PROVISIONAL); 3034f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3044f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 30575b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin /* 30675b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin * Starts and extends range selection in one go. This assumes item at startPos is not selected 30775b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin * beforehand. 30875b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin */ 30975b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin public void formNewSelectionRange(int startPos, int endPos) { 31075b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin assert(!mSelection.contains(mAdapter.getModelId(startPos))); 31175b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin startRangeSelection(startPos); 31275b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin snapRangeSelection(endPos); 31375b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin } 31475b7b9039cf0efcb188e916c6f510328bfe099a8Ben Lin 3154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Sets the end point for the current range selection, started by a call to 3174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * {@link #startRangeSelection(int)}. This function should only be called when a range selection 3184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be 3194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * selected or in provisional select, depending on the type supplied. Note that if the type is 3204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * provisional select, one should do {@link Selection#applyProvisionalSelection()} at some point 3214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * before calling on {@link #endRangeSelection()}. 3224f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * 3234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param pos The new end position for the selection range. 3244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param type The type of selection the range should utilize. 3254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private void snapRangeSelection(int pos, @RangeType int type) { 3274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (!isRangeSelectionActive()) { 3284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay throw new IllegalStateException("Range start point not set."); 3294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3304f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3314f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mRanger.snapSelection(pos, type); 3324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // We're being lazy here notifying even when something might not have changed. 3344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // To make this more correct, we'd need to update the Ranger class to return 3354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // information about what has changed. 3364f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionChanged(); 3374f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3384f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 339f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan void cancelProvisionalSelection() { 340f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan for (String id : mSelection.mProvisionalSelection) { 341f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan notifyItemStateChanged(id, false); 342f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan } 343f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan mSelection.cancelProvisionalSelection(); 344f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan } 345f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan 3464f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3474f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Stops an in-progress range selection. All selection done with 3484f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * {@link #snapRangeSelection(int, int)} with type RANGE_PROVISIONAL will be lost if 3494f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * {@link Selection#applyProvisionalSelection()} is not called beforehand. 3504f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3514f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void endRangeSelection() { 3524f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mRanger = null; 3534f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay // Clean up in case there was any leftover provisional selection 354f7df715eb33216405ccd338056b876b02d3911e8Garfield Tan cancelProvisionalSelection(); 3554f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return Whether or not there is a current range selection active. 3594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public boolean isRangeSelectionActive() { 3614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return mRanger != null; 3624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Sets the magic location at which a selection range begins (the selection anchor). This value 3664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * is consulted when determining how to extend, and modify selection ranges. Calling this when a 3674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * range selection is active will reset the range selection. 3684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public void setSelectionRangeBegin(int position) { 3704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (position == RecyclerView.NO_POSITION) { 3714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return; 3724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (mSelection.contains(mAdapter.getModelId(position))) { 3754f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mRanger = new Range(this::updateForRange, position); 3764f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3774f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3784f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3794f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param modelId 3814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return True if the update was applied. 3824f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private boolean selectAndNotify(String modelId) { 3844f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean changed = mSelection.add(modelId); 3854f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (changed) { 3864f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifyItemStateChanged(modelId, true); 3874f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3884f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return changed; 3894f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 3904f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 3914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 3924f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param id 3934f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return True if the update was applied. 3944f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 3954f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private boolean attemptDeselect(String id) { 3964f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(id != null); 3974f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (canSetState(id, false)) { 3984f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.remove(id); 3994f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifyItemStateChanged(id, false); 400ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann 401ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann // if there's nothing in the selection and there is an active ranger it results 402ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann // in unexpected behavior when the user tries to start range selection: the item 403ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann // which the ranger 'thinks' is the already selected anchor becomes unselectable 404ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann if (mSelection.isEmpty() && isRangeSelectionActive()) { 405ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann endRangeSelection(); 406ed895580275101312d7d6fa6ebf78b79b4905a1eJon Mann } 4074f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (DEBUG) Log.d(TAG, "Selection after deselect: " + mSelection); 4084f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return true; 4094f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } else { 4104f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (DEBUG) Log.d(TAG, "Select cancelled by listener."); 4114f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return false; 4124f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4134f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4144f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 4164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @param id 4174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * @return True if the update was applied. 4184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 4194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private boolean attemptSelect(String id) { 4204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(id != null); 4214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean canSelect = canSetState(id, true); 4224f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (!canSelect) { 4234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return false; 4244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (mSingleSelect && hasSelection()) { 4264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay clearSelectionQuietly(); 4274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay selectAndNotify(id); 4304f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return true; 4314f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean canSetState(String id, boolean nextState) { 4344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay return mCanSetState.test(id, nextState); 4354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4364f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 437ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann private void notifyDataChanged() { 438b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan final int lastListener = mItemCallbacks.size() - 1; 439b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan 440b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan for (int i = lastListener; i >= 0; i--) { 441b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan mItemCallbacks.get(i).onSelectionReset(); 442b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan } 443b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan 444ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann for (String id : mSelection) { 445ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann if (!canSetState(id, true)) { 446ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann attemptDeselect(id); 447ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann } else { 448ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann for (int i = lastListener; i >= 0; i--) { 449ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann mItemCallbacks.get(i).onItemStateChanged(id, true); 450ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann } 451ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann } 452ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann } 453ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann } 454ee9d85ca6b8ed9f330951eeadfc1107caa8f55a2Jon Mann 4554f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 4564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Notifies registered listeners when the selection status of a single item 4574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * (identified by {@code position}) changes. 4584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 4594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void notifyItemStateChanged(String id, boolean selected) { 4604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(id != null); 4614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay int lastListener = mItemCallbacks.size() - 1; 4624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (int i = lastListener; i >= 0; i--) { 4634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mItemCallbacks.get(i).onItemStateChanged(id, selected); 4644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mAdapter.onItemSelectionChanged(id); 4664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 4694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Notifies registered listeners when the selection has changed. This 4704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * notification should be sent only once a full series of changes 4714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * is complete, e.g. clearingSelection, or updating the single 4724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * selection from one item to another. 4734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 4744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void notifySelectionChanged() { 4754f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay int lastListener = mCallbacks.size() - 1; 4764f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (int i = lastListener; i > -1; i--) { 4774f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mCallbacks.get(i).onSelectionChanged(); 4784f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4794f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private void notifySelectionRestored() { 4824f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay int lastListener = mCallbacks.size() - 1; 4834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (int i = lastListener; i > -1; i--) { 4844f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mCallbacks.get(i).onSelectionRestored(); 4854f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4864f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4874f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 4884f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void updateForRange(int begin, int end, boolean selected, @RangeType int type) { 4894f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay switch (type) { 4904f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay case RANGE_REGULAR: 4914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay updateForRegularRange(begin, end, selected); 4924f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay break; 4934f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay case RANGE_PROVISIONAL: 4944f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay updateForProvisionalRange(begin, end, selected); 4954f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay break; 4964f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay default: 4974f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay throw new IllegalArgumentException("Invalid range type: " + type); 4984f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 4994f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5004f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5014f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private void updateForRegularRange(int begin, int end, boolean selected) { 5024f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert(end >= begin); 5034f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (int i = begin; i <= end; i++) { 5044f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay String id = mAdapter.getModelId(i); 5054f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (id == null) { 5064f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay continue; 5074f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5084f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5094f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (selected) { 5104f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean canSelect = canSetState(id, true); 5114f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (canSelect) { 5124f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (mSingleSelect && hasSelection()) { 5134f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay clearSelectionQuietly(); 5144f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5154f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay selectAndNotify(id); 5164f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5174f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } else { 5184f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay attemptDeselect(id); 5194f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5204f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5214f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5224f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5234f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private void updateForProvisionalRange(int begin, int end, boolean selected) { 5244f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay assert (end >= begin); 5254f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay for (int i = begin; i <= end; i++) { 5264f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay String id = mAdapter.getModelId(i); 5274f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (id == null) { 5284f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay continue; 5294f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 530b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan 531b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan boolean changedState = false; 5324f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay if (selected) { 5334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean canSelect = canSetState(id, true); 53495422be069897f419e7c6ca03eb21259540372d2Jon Mann if (canSelect && !mSelection.mSelection.contains(id)) { 5354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.mProvisionalSelection.add(id); 536b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan changedState = true; 5374f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5384f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } else { 5394f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay mSelection.mProvisionalSelection.remove(id); 540b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan changedState = true; 541b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan } 542b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan 543b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan // Only notify item callbacks when something's state is actually changed in provisional 544b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan // selection. 545b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan if (changedState) { 546b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan notifyItemStateChanged(id, selected); 5474f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5484f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5494f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay notifySelectionChanged(); 5504f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5514f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5524f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public interface ItemCallback { 5534f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void onItemStateChanged(String id, boolean selected); 554b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan 555b8874fd0df55113d472c6704b91bd493c577caebGarfield Tan void onSelectionReset(); 5564f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5574f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5584f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public interface Callback { 5594f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 5604f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Called immediately after completion of any set of changes. 5614f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 5624f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void onSelectionChanged(); 5634f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5644f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay /** 5654f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay * Called immediately after selection is restored. 5664f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay */ 5674f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay void onSelectionRestored(); 5684f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5694f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay 5704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay @FunctionalInterface 5714f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay public interface SelectionPredicate { 5724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay boolean test(String id, boolean nextState); 5734f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay } 5744f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay} 575