Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | |
| 17 | package android.view.inputmethod; |
| 18 | |
| 19 | import android.graphics.RectF; |
| 20 | import android.os.Parcel; |
| 21 | import android.os.Parcelable; |
| 22 | |
| 23 | import 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 | */ |
| 35 | public 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 | |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 53 | /** |
| 54 | * Stores visibility information. |
| 55 | */ |
| 56 | private final int[] mFlagsArray; |
| 57 | |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 58 | public SparseRectFArray(final Parcel source) { |
| 59 | mKeys = source.createIntArray(); |
| 60 | mCoordinates = source.createFloatArray(); |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 61 | mFlagsArray = source.createIntArray(); |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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); |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 74 | dest.writeIntArray(mFlagsArray); |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | } |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 89 | hash *= 31; |
| 90 | hash += mFlagsArray[0]; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 107 | return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates) |
| 108 | && Arrays.equals(mFlagsArray, that.mFlagsArray); |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | @Override |
| 112 | public String toString() { |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 113 | if (mKeys == null || mCoordinates == null || mFlagsArray == null) { |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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]); |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 132 | sb.append("]:flagsArray="); |
| 133 | sb.append(mFlagsArray[i]); |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | } |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 167 | if (mFlagsArray == null) { |
| 168 | mFlagsArray = new int[INITIAL_SIZE]; |
| 169 | } |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | } |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 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 | } |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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. |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 198 | * @param flags an arbitrary integer value to be associated with this rectangle. |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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, |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 203 | final float left, final float top, final float right, final float bottom, |
| 204 | final int flags) { |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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; |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 212 | final int flagsIndex = mCount; |
| 213 | mFlagsArray[flagsIndex] = flags; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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; |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 221 | private int[] mFlagsArray = null; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 222 | private static int INITIAL_SIZE = 16; |
| 223 | |
Yohei Yukawa | b5268dc | 2014-06-27 16:16:47 +0900 | [diff] [blame] | 224 | public boolean isEmpty() { |
| 225 | return mCount <= 0; |
| 226 | } |
| 227 | |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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; |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 239 | mFlagsArray = null; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 240 | } |
| 241 | mCount = 0; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | private SparseRectFArray(final SparseRectFArrayBuilder builder) { |
| 246 | if (builder.mCount == 0) { |
| 247 | mKeys = null; |
| 248 | mCoordinates = null; |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 249 | mFlagsArray = null; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 250 | } else { |
| 251 | mKeys = new int[builder.mCount]; |
| 252 | mCoordinates = new float[builder.mCount * 4]; |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 253 | mFlagsArray = new int[builder.mCount]; |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 254 | System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount); |
| 255 | System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4); |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 256 | System.arraycopy(builder.mFlagsArray, 0, mFlagsArray, 0, builder.mCount); |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | |
Yohei Yukawa | 0b01e7f | 2014-07-08 15:29:51 +0900 | [diff] [blame] | 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 | |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 292 | /** |
| 293 | * Used to make this class parcelable. |
| 294 | */ |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame] | 295 | public static final @android.annotation.NonNull Parcelable.Creator<SparseRectFArray> CREATOR = |
Yohei Yukawa | c2ddd60 | 2014-05-06 21:22:49 +0900 | [diff] [blame] | 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 | |