1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.view.inputmethod;
18
19import android.graphics.RectF;
20import android.os.Parcel;
21import android.os.Parcelable;
22
23import java.util.Arrays;
24
25/**
26 * An implementation of SparseArray specialized for {@link android.graphics.RectF}.
27 * <p>
28 * As this is a sparse array, it represents an array of {@link RectF} most of which are null. This
29 * class could be in some other packages like android.graphics or android.util but currently
30 * belong to android.view.inputmethod because this class is hidden and used only in input method
31 * framework.
32 * </p>
33 * @hide
34 */
35public final class SparseRectFArray implements Parcelable {
36    /**
37     * The keys, in ascending order, of those {@link RectF} that are not null. For example,
38     * {@code [null, null, null, Rect1, null, Rect2]} would be represented by {@code [3,5]}.
39     * @see #mCoordinates
40     */
41    private final int[] mKeys;
42
43    /**
44     * Stores coordinates of the rectangles, in the order of
45     * {@code rects[mKeys[0]].left}, {@code rects[mKeys[0]].top},
46     * {@code rects[mKeys[0]].right}, {@code rects[mKeys[0]].bottom},
47     * {@code rects[mKeys[1]].left}, {@code rects[mKeys[1]].top},
48     * {@code rects[mKeys[1]].right}, {@code rects[mKeys[1]].bottom},
49     * {@code rects[mKeys[2]].left}, {@code rects[mKeys[2]].top}, ....
50     */
51    private final float[] mCoordinates;
52
53    /**
54     * Stores visibility information.
55     */
56    private final int[] mFlagsArray;
57
58    public SparseRectFArray(final Parcel source) {
59        mKeys = source.createIntArray();
60        mCoordinates = source.createFloatArray();
61        mFlagsArray = source.createIntArray();
62    }
63
64    /**
65     * Used to package this object into a {@link Parcel}.
66     *
67     * @param dest The {@link Parcel} to be written.
68     * @param flags The flags used for parceling.
69     */
70    @Override
71    public void writeToParcel(Parcel dest, int flags) {
72        dest.writeIntArray(mKeys);
73        dest.writeFloatArray(mCoordinates);
74        dest.writeIntArray(mFlagsArray);
75    }
76
77    @Override
78    public int hashCode() {
79        // TODO: Improve the hash function.
80        if (mKeys == null || mKeys.length == 0) {
81            return 0;
82        }
83        int hash = mKeys.length;
84        // For performance reasons, only the first rectangle is used for the hash code now.
85        for (int i = 0; i < 4; i++) {
86            hash *= 31;
87            hash += mCoordinates[i];
88        }
89        hash *= 31;
90        hash += mFlagsArray[0];
91        return hash;
92    }
93
94    @Override
95    public boolean equals(Object obj){
96        if (obj == null) {
97            return false;
98        }
99        if (this == obj) {
100            return true;
101        }
102        if (!(obj instanceof SparseRectFArray)) {
103            return false;
104        }
105        final SparseRectFArray that = (SparseRectFArray) obj;
106
107        return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates)
108                && Arrays.equals(mFlagsArray, that.mFlagsArray);
109    }
110
111    @Override
112    public String toString() {
113        if (mKeys == null || mCoordinates == null || mFlagsArray == null) {
114            return "SparseRectFArray{}";
115        }
116        final StringBuilder sb = new StringBuilder();
117        sb.append("SparseRectFArray{");
118        for (int i = 0; i < mKeys.length; i++) {
119            if (i != 0) {
120                sb.append(", ");
121            }
122            final int baseIndex = i * 4;
123            sb.append(mKeys[i]);
124            sb.append(":[");
125            sb.append(mCoordinates[baseIndex + 0]);
126            sb.append(",");
127            sb.append(mCoordinates[baseIndex + 1]);
128            sb.append("],[");
129            sb.append(mCoordinates[baseIndex + 2]);
130            sb.append(",");
131            sb.append(mCoordinates[baseIndex + 3]);
132            sb.append("]:flagsArray=");
133            sb.append(mFlagsArray[i]);
134        }
135        sb.append("}");
136        return sb.toString();
137    }
138
139    /**
140     * Builder for {@link SparseRectFArray}. This class is not designed to be thread-safe.
141     * @hide
142     */
143    public static final class SparseRectFArrayBuilder {
144        /**
145         * Throws {@link IllegalArgumentException} to make sure that this class is correctly used.
146         * @param key key to be checked.
147         */
148        private void checkIndex(final int key) {
149            if (mCount == 0) {
150                return;
151            }
152            if (mKeys[mCount - 1] >= key) {
153                throw new IllegalArgumentException("key must be greater than all existing keys.");
154            }
155        }
156
157        /**
158         * Extends the internal array if necessary.
159         */
160        private void ensureBufferSize() {
161            if (mKeys == null) {
162                mKeys = new int[INITIAL_SIZE];
163            }
164            if (mCoordinates == null) {
165                mCoordinates = new float[INITIAL_SIZE * 4];
166            }
167            if (mFlagsArray == null) {
168                mFlagsArray = new int[INITIAL_SIZE];
169            }
170            final int requiredIndexArraySize = mCount + 1;
171            if (mKeys.length <= requiredIndexArraySize) {
172                final int[] newArray = new int[requiredIndexArraySize * 2];
173                System.arraycopy(mKeys, 0, newArray, 0, mCount);
174                mKeys = newArray;
175            }
176            final int requiredCoordinatesArraySize = (mCount + 1) * 4;
177            if (mCoordinates.length <= requiredCoordinatesArraySize) {
178                final float[] newArray = new float[requiredCoordinatesArraySize * 2];
179                System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
180                mCoordinates = newArray;
181            }
182            final int requiredFlagsArraySize = requiredIndexArraySize;
183            if (mFlagsArray.length <= requiredFlagsArraySize) {
184                final int[] newArray = new int[requiredFlagsArraySize * 2];
185                System.arraycopy(mFlagsArray, 0, newArray, 0, mCount);
186                mFlagsArray = newArray;
187            }
188        }
189
190        /**
191         * Puts the rectangle with an integer key.
192         * @param key the key to be associated with the rectangle. It must be greater than all
193         * existing keys that have been previously specified.
194         * @param left left of the rectangle.
195         * @param top top of the rectangle.
196         * @param right right of the rectangle.
197         * @param bottom bottom of the rectangle.
198         * @param flags an arbitrary integer value to be associated with this rectangle.
199         * @return the receiver object itself for chaining method calls.
200         * @throws IllegalArgumentException If the index is not greater than all of existing keys.
201         */
202        public SparseRectFArrayBuilder append(final int key,
203                final float left, final float top, final float right, final float bottom,
204                final int flags) {
205            checkIndex(key);
206            ensureBufferSize();
207            final int baseCoordinatesIndex = mCount * 4;
208            mCoordinates[baseCoordinatesIndex + 0] = left;
209            mCoordinates[baseCoordinatesIndex + 1] = top;
210            mCoordinates[baseCoordinatesIndex + 2] = right;
211            mCoordinates[baseCoordinatesIndex + 3] = bottom;
212            final int flagsIndex = mCount;
213            mFlagsArray[flagsIndex] = flags;
214            mKeys[mCount] = key;
215            ++mCount;
216            return this;
217        }
218        private int mCount = 0;
219        private int[] mKeys = null;
220        private float[] mCoordinates = null;
221        private int[] mFlagsArray = null;
222        private static int INITIAL_SIZE = 16;
223
224        public boolean isEmpty() {
225            return mCount <= 0;
226        }
227
228        /**
229         * @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}.
230         */
231        public SparseRectFArray build() {
232            return new SparseRectFArray(this);
233        }
234
235        public void reset() {
236            if (mCount == 0) {
237                mKeys = null;
238                mCoordinates = null;
239                mFlagsArray = null;
240            }
241            mCount = 0;
242        }
243    }
244
245    private SparseRectFArray(final SparseRectFArrayBuilder builder) {
246        if (builder.mCount == 0) {
247            mKeys = null;
248            mCoordinates = null;
249            mFlagsArray = null;
250        } else {
251            mKeys = new int[builder.mCount];
252            mCoordinates = new float[builder.mCount * 4];
253            mFlagsArray = new int[builder.mCount];
254            System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
255            System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
256            System.arraycopy(builder.mFlagsArray, 0, mFlagsArray, 0, builder.mCount);
257        }
258    }
259
260    public RectF get(final int index) {
261        if (mKeys == null) {
262            return null;
263        }
264        if (index < 0) {
265            return null;
266        }
267        final int arrayIndex = Arrays.binarySearch(mKeys, index);
268        if (arrayIndex < 0) {
269            return null;
270        }
271        final int baseCoordIndex = arrayIndex * 4;
272        return new RectF(mCoordinates[baseCoordIndex],
273                mCoordinates[baseCoordIndex + 1],
274                mCoordinates[baseCoordIndex + 2],
275                mCoordinates[baseCoordIndex + 3]);
276    }
277
278    public int getFlags(final int index, final int valueIfKeyNotFound) {
279        if (mKeys == null) {
280            return valueIfKeyNotFound;
281        }
282        if (index < 0) {
283            return valueIfKeyNotFound;
284        }
285        final int arrayIndex = Arrays.binarySearch(mKeys, index);
286        if (arrayIndex < 0) {
287            return valueIfKeyNotFound;
288        }
289        return mFlagsArray[arrayIndex];
290    }
291
292    /**
293     * Used to make this class parcelable.
294     */
295    public static final Parcelable.Creator<SparseRectFArray> CREATOR =
296            new Parcelable.Creator<SparseRectFArray>() {
297                @Override
298                public SparseRectFArray createFromParcel(Parcel source) {
299                    return new SparseRectFArray(source);
300                }
301                @Override
302                public SparseRectFArray[] newArray(int size) {
303                    return new SparseRectFArray[size];
304                }
305            };
306
307    @Override
308    public int describeContents() {
309        return 0;
310    }
311}
312
313