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 53 /** 54 * Stores visibility information. 55 */ 56 private final int[] mFlagsArray; 57 SparseRectFArray(final Parcel source)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 writeToParcel(Parcel dest, int flags)71 public void writeToParcel(Parcel dest, int flags) { 72 dest.writeIntArray(mKeys); 73 dest.writeFloatArray(mCoordinates); 74 dest.writeIntArray(mFlagsArray); 75 } 76 77 @Override hashCode()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 equals(Object obj)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 toString()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 */ checkIndex(final int key)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 */ ensureBufferSize()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 */ append(final int key, final float left, final float top, final float right, final float bottom, final int flags)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 isEmpty()224 public boolean isEmpty() { 225 return mCount <= 0; 226 } 227 228 /** 229 * @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}. 230 */ build()231 public SparseRectFArray build() { 232 return new SparseRectFArray(this); 233 } 234 reset()235 public void reset() { 236 if (mCount == 0) { 237 mKeys = null; 238 mCoordinates = null; 239 mFlagsArray = null; 240 } 241 mCount = 0; 242 } 243 } 244 SparseRectFArray(final SparseRectFArrayBuilder builder)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 get(final int index)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 getFlags(final int index, final int valueIfKeyNotFound)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 @android.annotation.NonNull 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 describeContents()308 public int describeContents() { 309 return 0; 310 } 311 } 312 313