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 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