1fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay/*
2fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * Copyright (C) 2015 The Android Open Source Project
3fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay *
4fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * Licensed under the Apache License, Version 2.0 (the "License");
5fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * you may not use this file except in compliance with the License.
6fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * You may obtain a copy of the License at
7fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay *
8fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay *      http://www.apache.org/licenses/LICENSE-2.0
9fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay *
10fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * Unless required by applicable law or agreed to in writing, software
11fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * distributed under the License is distributed on an "AS IS" BASIS,
12fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * See the License for the specific language governing permissions and
14fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * limitations under the License.
15fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay */
16fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
170aa720752bc87d2d387b41a3016f0d533a8fc7f1Steve McKaypackage com.android.documentsui.selection;
18fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
19365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKayimport static android.support.v4.util.Preconditions.checkArgument;
20365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay
21fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.graphics.Point;
22fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.graphics.Rect;
23fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.support.annotation.VisibleForTesting;
24fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.support.v7.widget.RecyclerView;
25fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.support.v7.widget.RecyclerView.OnScrollListener;
26fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.util.Log;
27fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.util.SparseArray;
28fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.util.SparseBooleanArray;
29fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport android.util.SparseIntArray;
30fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
310aa720752bc87d2d387b41a3016f0d533a8fc7f1Steve McKayimport com.android.documentsui.selection.BandSelectionHelper.BandHost;
32365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKayimport com.android.documentsui.selection.SelectionHelper.SelectionPredicate;
33365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKayimport com.android.documentsui.selection.SelectionHelper.StableIdProvider;
34fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
35fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport java.util.ArrayList;
36fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport java.util.Collections;
37fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport java.util.HashSet;
38fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport java.util.List;
39fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayimport java.util.Set;
40fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
41fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay/**
42fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * Provides a band selection item model for views within a RecyclerView. This class queries the
43fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * RecyclerView to determine where its items are placed; then, once band selection is underway,
44fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay * it alerts listeners of which items are covered by the selections.
45fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay */
46fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKayfinal class GridModel {
47fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
48fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // Magical value indicating that a value has not been previously set. primitive null :)
49fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    static final int NOT_SET = -1;
50fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
51fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // Enum values used to determine the corner at which the origin is located within the
52fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int UPPER = 0x00;
53fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int LOWER = 0x01;
54fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int LEFT = 0x00;
55fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int RIGHT = 0x02;
56fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int UPPER_LEFT = UPPER | LEFT;
57fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int UPPER_RIGHT = UPPER | RIGHT;
58fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int LOWER_LEFT = LOWER | LEFT;
59fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static final int LOWER_RIGHT = LOWER | RIGHT;
60fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
615a62037989cd8c30a48273389456f3f90920448dSteve McKay    private final BandHost mHost;
62fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final StableIdProvider mStableIds;
63fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final SelectionPredicate mSelectionPredicate;
64fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
655a62037989cd8c30a48273389456f3f90920448dSteve McKay    private final List<SelectionObserver> mOnSelectionChangedListeners =
66fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            new ArrayList<>();
67fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
68fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed
69fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // by their y-offset. For example, if the first column of the view starts at an x-value of 5,
70fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // mColumns.get(5) would return an array of positions in that column. Within that array, the
71fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // value for key y is the adapter position for the item whose y-offset is y.
72fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final SparseArray<SparseIntArray> mColumns = new SparseArray<>();
73fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
74fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // List of limits along the x-axis (columns).
75fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // This list is sorted from furthest left to furthest right.
76fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final List<Limits> mColumnBounds = new ArrayList<>();
77fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
78fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // List of limits along the y-axis (rows). Note that this list only contains items which
79fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // have been in the viewport.
80fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final List<Limits> mRowBounds = new ArrayList<>();
81fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
82fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // The adapter positions which have been recorded so far.
83fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final SparseBooleanArray mKnownPositions = new SparseBooleanArray();
84fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
85fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // Array passed to registered OnSelectionChangedListeners. One array is created and reused
86fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // throughout the lifetime of the object.
87fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final Set<String> mSelection = new HashSet<>();
88fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
89fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // The current pointer (in absolute positioning from the top of the view).
90fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private Point mPointer = null;
91fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
92fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // The bounds of the band selection.
93fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private RelativePoint mRelativeOrigin;
94fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private RelativePoint mRelativePointer;
95fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
96fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean mIsActive;
97fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
98fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // Tracks where the band select originated from. This is used to determine where selections
99fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    // should expand from when Shift+click is used.
100fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private int mPositionNearestOrigin = NOT_SET;
101fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
102fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private final OnScrollListener mScrollListener;
103fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
104fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    GridModel(
1055a62037989cd8c30a48273389456f3f90920448dSteve McKay            BandHost host,
106fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            StableIdProvider stableIds,
107fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            SelectionPredicate selectionPredicate) {
108fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
109fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mHost = host;
110fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mStableIds = stableIds;
111fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mSelectionPredicate = selectionPredicate;
112fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
113fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mScrollListener = new OnScrollListener() {
114fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            @Override
115fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
116fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                GridModel.this.onScrolled(recyclerView, dx, dy);
117fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
118fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        };
119fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
120fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mHost.addOnScrollListener(mScrollListener);
121fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
122fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
123fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
124fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Stops listening to the view's scrolls. Call this function before discarding a
125fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * GridModel object to prevent memory leaks.
126fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
127fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    void stopListening() {
128fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mHost.removeOnScrollListener(mScrollListener);
129fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
130fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
131fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
132fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Start a band select operation at the given point.
133fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @param relativeOrigin The origin of the band select operation, relative to the viewport.
134fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     For example, if the view is scrolled to the bottom, the top-left of the viewport
135fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     would have a relative origin of (0, 0), even though its absolute point has a higher
136fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     y-value.
137fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
1385bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay    void startCapturing(Point relativeOrigin) {
139fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        recordVisibleChildren();
140fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (isEmpty()) {
141fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            // The selection band logic works only if there is at least one visible child.
142fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return;
143fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
144fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
145fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mIsActive = true;
146fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mPointer = mHost.createAbsolutePoint(relativeOrigin);
147fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mRelativeOrigin = new RelativePoint(mPointer);
148fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mRelativePointer = new RelativePoint(mPointer);
149fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        computeCurrentSelection();
1505a62037989cd8c30a48273389456f3f90920448dSteve McKay        notifySelectionChanged();
151fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
152fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
153fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
1545bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay     * Ends the band selection.
1555bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay     */
1565bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay    void stopCapturing() {
1575bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay        mIsActive = false;
1585bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay    }
1595bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay
1605bf51ec2ce8ba84ab56ab866ac8539c42d87a2f0Steve McKay    /**
161fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Resizes the selection by adjusting the pointer (i.e., the corner of the selection
162fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * opposite the origin.
163fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @param relativePointer The pointer (opposite of the origin) of the band select operation,
164fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     relative to the viewport. For example, if the view is scrolled to the bottom, the
165fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     top-left of the viewport would have a relative origin of (0, 0), even though its
166fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     absolute point has a higher y-value.
167fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
168fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    @VisibleForTesting
169fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    void resizeSelection(Point relativePointer) {
170fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mPointer = mHost.createAbsolutePoint(relativePointer);
171fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        updateModel();
172fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
173fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
174fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
175fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @return The adapter position for the item nearest the origin corresponding to the latest
176fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *         band select operation, or NOT_SET if the selection did not cover any items.
177fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
178fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    int getPositionNearestOrigin() {
179fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return mPositionNearestOrigin;
180fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
181fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
182fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
183fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (!mIsActive) {
184fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return;
185fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
186fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
187fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mPointer.x += dx;
188fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mPointer.y += dy;
189fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        recordVisibleChildren();
190fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        updateModel();
191fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
192fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
193fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
194fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Queries the view for all children and records their location metadata.
195fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
196fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void recordVisibleChildren() {
197fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        for (int i = 0; i < mHost.getVisibleChildCount(); i++) {
198fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            int adapterPosition = mHost.getAdapterPositionAt(i);
199fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            // Sometimes the view is not attached, as we notify the multi selection manager
200fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            // synchronously, while views are attached asynchronously. As a result items which
201fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            // are in the adapter may not actually have a corresponding view (yet).
202fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (mHost.hasView(adapterPosition) &&
203fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    mSelectionPredicate.canSetStateAtPosition(adapterPosition, true) &&
204fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    !mKnownPositions.get(adapterPosition)) {
205fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                mKnownPositions.put(adapterPosition, true);
206fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                recordItemData(mHost.getAbsoluteRectForChildViewAt(i), adapterPosition);
207fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
208fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
209fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
210fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
211fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
212fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Checks if there are any recorded children.
213fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
214fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean isEmpty() {
215fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return mColumnBounds.size() == 0 || mRowBounds.size() == 0;
216fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
217fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
218fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
219fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Updates the limits lists and column map with the given item metadata.
220fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @param absoluteChildRect The absolute rectangle for the child view being processed.
221fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @param adapterPosition The position of the child view being processed.
222fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
223fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void recordItemData(Rect absoluteChildRect, int adapterPosition) {
224fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (mColumnBounds.size() != mHost.getColumnCount()) {
225fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            // If not all x-limits have been recorded, record this one.
226fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            recordLimits(
227fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right));
228fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
229fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
230fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        recordLimits(mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom));
231fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
232fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        SparseIntArray columnList = mColumns.get(absoluteChildRect.left);
233fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (columnList == null) {
234fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            columnList = new SparseIntArray();
235fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            mColumns.put(absoluteChildRect.left, columnList);
236fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
237fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        columnList.put(absoluteChildRect.top, adapterPosition);
238fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
239fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
240fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
241fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Ensures limits exists within the sorted list limitsList, and adds it to the list if it
242fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * does not exist.
243fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
244fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void recordLimits(List<Limits> limitsList, Limits limits) {
245fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int index = Collections.binarySearch(limitsList, limits);
246fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (index < 0) {
247fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            limitsList.add(~index, limits);
248fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
249fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
250fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
251fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
252fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Handles a moved pointer; this function determines whether the pointer movement resulted
253fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * in a selection change and, if it has, notifies listeners of this change.
254fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
255fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void updateModel() {
256fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        RelativePoint old = mRelativePointer;
257fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mRelativePointer = new RelativePoint(mPointer);
258fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (old != null && mRelativePointer.equals(old)) {
259fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return;
260fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
261fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
262fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        computeCurrentSelection();
2635a62037989cd8c30a48273389456f3f90920448dSteve McKay        notifySelectionChanged();
264fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
265fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
266fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
267fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Computes the currently-selected items.
268fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
269fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void computeCurrentSelection() {
270fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (areItemsCoveredByBand(mRelativePointer, mRelativeOrigin)) {
271fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            updateSelection(computeBounds());
272fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        } else {
273fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            mSelection.clear();
274fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            mPositionNearestOrigin = NOT_SET;
275fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
276fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
277fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
278fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
279fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Notifies all listeners of a selection change. Note that this function simply passes
280fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * mSelection, so computeCurrentSelection() should be called before this
281fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * function.
282fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
2835a62037989cd8c30a48273389456f3f90920448dSteve McKay    private void notifySelectionChanged() {
2845a62037989cd8c30a48273389456f3f90920448dSteve McKay        for (SelectionObserver listener : mOnSelectionChangedListeners) {
285fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            listener.onSelectionChanged(mSelection);
286fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
287fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
288fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
289fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
290fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @param rect Rectangle including all covered items.
291fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
292fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void updateSelection(Rect rect) {
293fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int columnStart =
294fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
295365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay
296365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        checkArgument(columnStart >= 0, "Rect doesn't intesect any known column.");
297365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay
298fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int columnEnd = columnStart;
299fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
300fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        for (int i = columnStart; i < mColumnBounds.size()
301fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                && mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
302fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            columnEnd = i;
303fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
304fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
305fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
306fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (rowStart < 0) {
307fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            mPositionNearestOrigin = NOT_SET;
308fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return;
309fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
310fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
311fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int rowEnd = rowStart;
312fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        for (int i = rowStart; i < mRowBounds.size()
313fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
314fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            rowEnd = i;
315fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
316fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
317fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        updateSelection(columnStart, columnEnd, rowStart, rowEnd);
318fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
319fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
320fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
321fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Computes the selection given the previously-computed start- and end-indices for each
322fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * row and column.
323fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
324fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private void updateSelection(
325fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
326365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay
327365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        if (BandSelectionHelper.DEBUG) {
328365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay            Log.d(BandSelectionHelper.TAG, String.format(
329365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay                    "updateSelection: %d, %d, %d, %d",
330365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay                    columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
331365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        }
332fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
333fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mSelection.clear();
334fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        for (int column = columnStartIndex; column <= columnEndIndex; column++) {
335fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
336fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            for (int row = rowStartIndex; row <= rowEndIndex; row++) {
337fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // The default return value for SparseIntArray.get is 0, which is a valid
338fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // position. Use a sentry value to prevent erroneously selecting item 0.
339fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                final int rowKey = mRowBounds.get(row).lowerLimit;
340fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                int position = items.get(rowKey, NOT_SET);
341fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                if (position != NOT_SET) {
342fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    String id = mStableIds.getStableId(position);
343fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    if (id != null) {
344fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        // The adapter inserts items for UI layout purposes that aren't
345fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        // associated with files. Those will have a null model ID.
346fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        // Don't select them.
347fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        if (canSelect(id)) {
348fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                            mSelection.add(id);
349fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        }
350fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    }
351fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
352fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                            row, rowStartIndex, rowEndIndex)) {
353fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        // If this is the position nearest the origin, record it now so that it
354fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        // can be returned by endSelection() later.
355fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        mPositionNearestOrigin = position;
356fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    }
357fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                }
358fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
359fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
360fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
361fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
362fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean canSelect(String id) {
363365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        return mSelectionPredicate.canSetStateForId(id, true);
364fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
365fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
366fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
367fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @return Returns true if the position is the nearest to the origin, or, in the case of the
368fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     lower-right corner, whether it is possible that the position is the nearest to the
369fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     origin. See comment below for reasoning for this special case.
370fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
371fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean isPossiblePositionNearestOrigin(int columnIndex, int columnStartIndex,
372fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            int columnEndIndex, int rowIndex, int rowStartIndex, int rowEndIndex) {
373fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int corner = computeCornerNearestOrigin();
374fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        switch (corner) {
375fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case UPPER_LEFT:
376fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return columnIndex == columnStartIndex && rowIndex == rowStartIndex;
377fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case UPPER_RIGHT:
378fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return columnIndex == columnEndIndex && rowIndex == rowStartIndex;
379fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case LOWER_LEFT:
380fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return columnIndex == columnStartIndex && rowIndex == rowEndIndex;
381fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case LOWER_RIGHT:
382fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // Note that in some cases, the last row will not have as many items as there
383fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // are columns (e.g., if there are 4 items and 3 columns, the second row will
384fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // only have one item in the first column). This function is invoked for each
385fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // position from left to right, so return true for any position in the bottom
386fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                // row and only the right-most position in the bottom row will be recorded.
387fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return rowIndex == rowEndIndex;
388fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            default:
389fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                throw new RuntimeException("Invalid corner type.");
390fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
391fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
392fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
393fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
394fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Listener for changes in which items have been band selected.
395fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
3965a62037989cd8c30a48273389456f3f90920448dSteve McKay    public static abstract class SelectionObserver {
3975a62037989cd8c30a48273389456f3f90920448dSteve McKay        abstract void onSelectionChanged(Set<String> updatedSelection);
398fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
399fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
4005a62037989cd8c30a48273389456f3f90920448dSteve McKay    void addOnSelectionChangedListener(SelectionObserver listener) {
401fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        mOnSelectionChangedListeners.add(listener);
402fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
403fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
404365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay    /**
405365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay     * Called when {@link BandSelectionHelper} is finished with a GridModel.
406365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay     */
407365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay    void onDestroy() {
408365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        mOnSelectionChangedListeners.clear();
409365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay        stopListening();
410fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
411fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
412fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
413fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Limits of a view item. For example, if an item's left side is at x-value 5 and its right side
414fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * is at x-value 10, the limits would be from 5 to 10. Used to record the left- and right sides
415fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * of item columns and the top- and bottom sides of item rows so that it can be determined
416fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * whether the pointer is located within the bounds of an item.
417fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
418fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static class Limits implements Comparable<Limits> {
419fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int lowerLimit;
420fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int upperLimit;
421fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
422fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Limits(int lowerLimit, int upperLimit) {
423fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            this.lowerLimit = lowerLimit;
424fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            this.upperLimit = upperLimit;
425fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
426fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
427fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
428fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public int compareTo(Limits other) {
429fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return lowerLimit - other.lowerLimit;
430fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
431fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
432fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
433fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public int hashCode() {
434fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return lowerLimit ^ upperLimit;
435fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
436fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
437fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
438fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public boolean equals(Object other) {
439fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (!(other instanceof Limits)) {
440fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return false;
441fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
442fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
443fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return ((Limits) other).lowerLimit == lowerLimit &&
444fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    ((Limits) other).upperLimit == upperLimit;
445fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
446fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
447fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
448fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public String toString() {
449fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return "(" + lowerLimit + ", " + upperLimit + ")";
450fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
451fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
452fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
453fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
454fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * The location of a coordinate relative to items. This class represents a general area of the
455fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * view as it relates to band selection rather than an explicit point. For example, two
456fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * different points within an item are considered to have the same "location" because band
457fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * selection originating within the item would select the same items no matter which point
458fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * was used. Same goes for points between items as well as those at the very beginning or end
459fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * of the view.
460fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *
461fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Tracking a coordinate (e.g., an x-value) as a CoordinateLocation instead of as an int has the
462fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * advantage of tying the value to the Limits of items along that axis. This allows easy
463fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * selection of items within those Limits as opposed to a search through every item to see if a
464fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * given coordinate value falls within those Limits.
465fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
466fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private static class RelativeCoordinate
467fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            implements Comparable<RelativeCoordinate> {
468fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
469fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * Location describing points after the last known item.
470fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
471fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        static final int AFTER_LAST_ITEM = 0;
472fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
473fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
474fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * Location describing points before the first known item.
475fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
476fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        static final int BEFORE_FIRST_ITEM = 1;
477fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
478fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
479fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * Location describing points between two items.
480fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
481fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        static final int BETWEEN_TWO_ITEMS = 2;
482fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
483fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
484fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * Location describing points within the limits of one item.
485fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
486fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        static final int WITHIN_LIMITS = 3;
487fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
488fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
489fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * The type of this coordinate, which is one of AFTER_LAST_ITEM, BEFORE_FIRST_ITEM,
490fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * BETWEEN_TWO_ITEMS, or WITHIN_LIMITS.
491fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
492fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        final int type;
493fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
494fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
495fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * The limits before the coordinate; only populated when type == WITHIN_LIMITS or type ==
496fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * BETWEEN_TWO_ITEMS.
497fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
498fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Limits limitsBeforeCoordinate;
499fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
500fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
501fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * The limits after the coordinate; only populated when type == BETWEEN_TWO_ITEMS.
502fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
503fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Limits limitsAfterCoordinate;
504fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
505fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        // Limits of the first known item; only populated when type == BEFORE_FIRST_ITEM.
506fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Limits mFirstKnownItem;
507fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        // Limits of the last known item; only populated when type == AFTER_LAST_ITEM.
508fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Limits mLastKnownItem;
509fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
510fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        /**
511fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * @param limitsList The sorted limits list for the coordinate type. If this
512fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         *     CoordinateLocation is an x-value, mXLimitsList should be passed; otherwise,
513fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         *     mYLimitsList should be pased.
514fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         * @param value The coordinate value.
515fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay         */
516fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        RelativeCoordinate(List<Limits> limitsList, int value) {
517fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            int index = Collections.binarySearch(limitsList, new Limits(value, value));
518fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
519fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (index >= 0) {
520fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                this.type = WITHIN_LIMITS;
521fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                this.limitsBeforeCoordinate = limitsList.get(index);
522fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else if (~index == 0) {
523fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                this.type = BEFORE_FIRST_ITEM;
524fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                this.mFirstKnownItem = limitsList.get(0);
525fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else if (~index == limitsList.size()) {
526fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                Limits lastLimits = limitsList.get(limitsList.size() - 1);
527fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                if (lastLimits.lowerLimit <= value && value <= lastLimits.upperLimit) {
528fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.type = WITHIN_LIMITS;
529fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.limitsBeforeCoordinate = lastLimits;
530fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                } else {
531fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.type = AFTER_LAST_ITEM;
532fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.mLastKnownItem = lastLimits;
533fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                }
534fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else {
535fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                Limits limitsBeforeIndex = limitsList.get(~index - 1);
536365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay                if (limitsBeforeIndex.lowerLimit <= value
537365e3cb1e103bff2303aa98fc8699e5e2eab1eb5Steve McKay                        && value <= limitsBeforeIndex.upperLimit) {
538fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.type = WITHIN_LIMITS;
539fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
540fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                } else {
541fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.type = BETWEEN_TWO_ITEMS;
542fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
543fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    this.limitsAfterCoordinate = limitsList.get(~index);
544fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                }
545fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
546fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
547fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
548fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int toComparisonValue() {
549fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (type == BEFORE_FIRST_ITEM) {
550fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return mFirstKnownItem.lowerLimit - 1;
551fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else if (type == AFTER_LAST_ITEM) {
552fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return mLastKnownItem.upperLimit + 1;
553fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else if (type == BETWEEN_TWO_ITEMS) {
554fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return limitsBeforeCoordinate.upperLimit + 1;
555fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            } else {
556fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return limitsBeforeCoordinate.lowerLimit;
557fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
558fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
559fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
560fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
561fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public int hashCode() {
562fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return mFirstKnownItem.lowerLimit
563fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    ^ mLastKnownItem.upperLimit
564fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    ^ limitsBeforeCoordinate.upperLimit
565fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    ^ limitsBeforeCoordinate.lowerLimit;
566fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
567fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
568fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
569fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public boolean equals(Object other) {
570fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (!(other instanceof RelativeCoordinate)) {
571fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return false;
572fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
573fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
574fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            RelativeCoordinate otherCoordinate = (RelativeCoordinate) other;
575fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return toComparisonValue() == otherCoordinate.toComparisonValue();
576fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
577fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
578fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
579fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public int compareTo(RelativeCoordinate other) {
580fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return toComparisonValue() - other.toComparisonValue();
581fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
582fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
583fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
584fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
585fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * The location of a point relative to the Limits of nearby items; consists of both an x- and
586fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * y-RelativeCoordinateLocation.
587fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
588fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private class RelativePoint {
589fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        final RelativeCoordinate xLocation;
590fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        final RelativeCoordinate yLocation;
591fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
592fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        RelativePoint(Point point) {
593fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            this.xLocation = new RelativeCoordinate(mColumnBounds, point.x);
594fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            this.yLocation = new RelativeCoordinate(mRowBounds, point.y);
595fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
596fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
597fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
598fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public int hashCode() {
599fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return xLocation.toComparisonValue()
600fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    ^ yLocation.toComparisonValue();
601fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
602fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
603fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        @Override
604fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        public boolean equals(Object other) {
605fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            if (!(other instanceof RelativePoint)) {
606fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return false;
607fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            }
608fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
609fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            RelativePoint otherPoint = (RelativePoint) other;
610fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return xLocation.equals(otherPoint.xLocation) && yLocation.equals(otherPoint.yLocation);
611fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
612fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
613fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
614fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
615fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Generates a rectangle which contains the items selected by the pointer and origin.
616fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @return The rectangle, or null if no items were selected.
617fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
618fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private Rect computeBounds() {
619fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        Rect rect = new Rect();
620fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        rect.left = getCoordinateValue(
621fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                min(mRelativeOrigin.xLocation, mRelativePointer.xLocation),
622fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                mColumnBounds,
623fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                true);
624fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        rect.right = getCoordinateValue(
625fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                max(mRelativeOrigin.xLocation, mRelativePointer.xLocation),
626fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                mColumnBounds,
627fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                false);
628fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        rect.top = getCoordinateValue(
629fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                min(mRelativeOrigin.yLocation, mRelativePointer.yLocation),
630fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                mRowBounds,
631fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                true);
632fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        rect.bottom = getCoordinateValue(
633fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                max(mRelativeOrigin.yLocation, mRelativePointer.yLocation),
634fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                mRowBounds,
635fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                false);
636fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return rect;
637fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
638fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
639fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
640fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * Computes the corner of the selection nearest the origin.
641fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @return
642fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
643fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private int computeCornerNearestOrigin() {
644fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        int cornerValue = 0;
645fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
646fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (mRelativeOrigin.yLocation ==
647fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                min(mRelativeOrigin.yLocation, mRelativePointer.yLocation)) {
648fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            cornerValue |= UPPER;
649fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        } else {
650fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            cornerValue |= LOWER;
651fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
652fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
653fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (mRelativeOrigin.xLocation ==
654fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                min(mRelativeOrigin.xLocation, mRelativePointer.xLocation)) {
655fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            cornerValue |= LEFT;
656fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        } else {
657fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            cornerValue |= RIGHT;
658fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
659fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
660fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return cornerValue;
661fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
662fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
663fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private RelativeCoordinate min(RelativeCoordinate first, RelativeCoordinate second) {
664fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return first.compareTo(second) < 0 ? first : second;
665fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
666fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
667fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private RelativeCoordinate max(RelativeCoordinate first, RelativeCoordinate second) {
668fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return first.compareTo(second) > 0 ? first : second;
669fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
670fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
671fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    /**
672fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     * @return The absolute coordinate (i.e., the x- or y-value) of the given relative
673fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     *     coordinate.
674fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay     */
675fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private int getCoordinateValue(
676fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            RelativeCoordinate coordinate, List<Limits> limitsList, boolean isStartOfRange) {
677fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
678fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        switch (coordinate.type) {
679fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case RelativeCoordinate.BEFORE_FIRST_ITEM:
680fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return limitsList.get(0).lowerLimit;
681fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case RelativeCoordinate.AFTER_LAST_ITEM:
682fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return limitsList.get(limitsList.size() - 1).upperLimit;
683fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case RelativeCoordinate.BETWEEN_TWO_ITEMS:
684fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                if (isStartOfRange) {
685fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    return coordinate.limitsAfterCoordinate.lowerLimit;
686fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                } else {
687fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                    return coordinate.limitsBeforeCoordinate.upperLimit;
688fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                }
689fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            case RelativeCoordinate.WITHIN_LIMITS:
690fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                return coordinate.limitsBeforeCoordinate.lowerLimit;
691fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
692fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
693fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        throw new RuntimeException("Invalid coordinate value.");
694fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
695fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
696fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean areItemsCoveredByBand(
697fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            RelativePoint first, RelativePoint second) {
698fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
699fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return doesCoordinateLocationCoverItems(first.xLocation, second.xLocation) &&
700fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                doesCoordinateLocationCoverItems(first.yLocation, second.yLocation);
701fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
702fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
703fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    private boolean doesCoordinateLocationCoverItems(
704fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            RelativeCoordinate pointerCoordinate, RelativeCoordinate originCoordinate) {
705fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
706fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (pointerCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM &&
707fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                originCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM) {
708fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return false;
709fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
710fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
711fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (pointerCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM &&
712fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                originCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM) {
713fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return false;
714fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
715fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
716fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        if (pointerCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS &&
717fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                originCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS &&
718fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                pointerCoordinate.limitsBeforeCoordinate.equals(
719fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        originCoordinate.limitsBeforeCoordinate) &&
720fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                pointerCoordinate.limitsAfterCoordinate.equals(
721fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay                        originCoordinate.limitsAfterCoordinate)) {
722fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay            return false;
723fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        }
724fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay
725fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay        return true;
726fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay    }
727fcff0b0a5ce5f9ff4944883a05d6f89ea8ff6787Steve McKay}