1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.documentsui.selection;
17 
18 import static com.android.documentsui.base.Shared.DEBUG;
19 import static com.android.documentsui.base.Shared.VERBOSE;
20 
21 import android.support.v7.widget.RecyclerView;
22 import android.util.Log;
23 
24 import com.android.documentsui.selection.SelectionManager.RangeType;
25 
26 /**
27  * Class providing support for managing range selections.
28  */
29 final class Range {
30     private static final int UNDEFINED = -1;
31 
32     private final Range.RangeUpdater mUpdater;
33     private final int mBegin;
34     private int mEnd = UNDEFINED;
35 
Range(Range.RangeUpdater updater, int begin)36     public Range(Range.RangeUpdater updater, int begin) {
37         if (DEBUG) Log.d(SelectionManager.TAG, "New Ranger created beginning @ " + begin);
38         mUpdater = updater;
39         mBegin = begin;
40     }
41 
snapSelection(int position, @RangeType int type)42     void snapSelection(int position, @RangeType int type) {
43         assert(position != RecyclerView.NO_POSITION);
44 
45         if (mEnd == UNDEFINED || mEnd == mBegin) {
46             // Reset mEnd so it can be established in establishRange.
47             mEnd = UNDEFINED;
48             establishRange(position, type);
49         } else {
50             reviseRange(position, type);
51         }
52     }
53 
establishRange(int position, @RangeType int type)54     private void establishRange(int position, @RangeType int type) {
55         assert(mEnd == UNDEFINED);
56 
57         if (position == mBegin) {
58             mEnd = position;
59         }
60 
61         if (position > mBegin) {
62             updateRange(mBegin + 1, position, true, type);
63         } else if (position < mBegin) {
64             updateRange(position, mBegin - 1, true, type);
65         }
66 
67         mEnd = position;
68     }
69 
reviseRange(int position, @RangeType int type)70     private void reviseRange(int position, @RangeType int type) {
71         assert(mEnd != UNDEFINED);
72         assert(mBegin != mEnd);
73 
74         if (position == mEnd) {
75             if (VERBOSE) Log.v(SelectionManager.TAG, "Ignoring no-op revision for range: " + this);
76         }
77 
78         if (mEnd > mBegin) {
79             reviseAscendingRange(position, type);
80         } else if (mEnd < mBegin) {
81             reviseDescendingRange(position, type);
82         }
83         // the "else" case is covered by checkState at beginning of method.
84 
85         mEnd = position;
86     }
87 
88     /**
89      * Updates an existing ascending seleciton.
90      * @param position
91      */
reviseAscendingRange(int position, @RangeType int type)92     private void reviseAscendingRange(int position, @RangeType int type) {
93         // Reducing or reversing the range....
94         if (position < mEnd) {
95             if (position < mBegin) {
96                 updateRange(mBegin + 1, mEnd, false, type);
97                 updateRange(position, mBegin -1, true, type);
98             } else {
99                 updateRange(position + 1, mEnd, false, type);
100             }
101         }
102 
103         // Extending the range...
104         else if (position > mEnd) {
105             updateRange(mEnd + 1, position, true, type);
106         }
107     }
108 
reviseDescendingRange(int position, @RangeType int type)109     private void reviseDescendingRange(int position, @RangeType int type) {
110         // Reducing or reversing the range....
111         if (position > mEnd) {
112             if (position > mBegin) {
113                 updateRange(mEnd, mBegin - 1, false, type);
114                 updateRange(mBegin + 1, position, true, type);
115             } else {
116                 updateRange(mEnd, position - 1, false, type);
117             }
118         }
119 
120         // Extending the range...
121         else if (position < mEnd) {
122             updateRange(position, mEnd - 1, true, type);
123         }
124     }
125 
126     /**
127      * Try to set selection state for all elements in range. Not that callbacks can cancel
128      * selection of specific items, so some or even all items may not reflect the desired state
129      * after the update is complete.
130      *
131      * @param begin Adapter position for range start (inclusive).
132      * @param end Adapter position for range end (inclusive).
133      * @param selected New selection state.
134      */
updateRange(int begin, int end, boolean selected, @RangeType int type)135     private void updateRange(int begin, int end, boolean selected, @RangeType int type) {
136         mUpdater.updateForRange(begin, end, selected, type);
137     }
138 
139     @Override
toString()140     public String toString() {
141         return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
142     }
143 
144     /*
145      * @see {@link MultiSelectManager#updateForRegularRange(int, int , boolean)} and {@link
146      * MultiSelectManager#updateForProvisionalRange(int, int, boolean)}
147      */
148     @FunctionalInterface
149     interface RangeUpdater {
updateForRange(int begin, int end, boolean selected, @RangeType int type)150         void updateForRange(int begin, int end, boolean selected, @RangeType int type);
151     }
152 }
153