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