diff --git a/api/current.txt b/api/current.txt index 838c890c7064f..e5092ff4ffbd0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12217,6 +12217,14 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraMetadata.Key TONEMAP_MODE; } + public final class ColorSpaceTransform { + ctor public ColorSpaceTransform(android.hardware.camera2.Rational[]); + ctor public ColorSpaceTransform(int[]); + method public void copyElements(android.hardware.camera2.Rational[], int); + method public void copyElements(int[], int); + method public android.hardware.camera2.Rational getElement(int, int); + } + public final class Face { method public android.graphics.Rect getBounds(); method public int getId(); @@ -12229,6 +12237,21 @@ package android.hardware.camera2 { field public static final int SCORE_MIN = 1; // 0x1 } + public final class MeteringRectangle { + ctor public MeteringRectangle(int, int, int, int, int); + ctor public MeteringRectangle(android.graphics.Point, android.util.Size, int); + ctor public MeteringRectangle(android.graphics.Rect, int); + method public boolean equals(android.hardware.camera2.MeteringRectangle); + method public int getHeight(); + method public int getMeteringWeight(); + method public android.graphics.Rect getRect(); + method public android.util.Size getSize(); + method public android.graphics.Point getUpperLeftPoint(); + method public int getWidth(); + method public int getX(); + method public int getY(); + } + public final class Rational { ctor public Rational(int, int); method public int getDenominator(); @@ -28998,6 +29021,25 @@ package android.util { method public void set(T, V); } + public final class Range { + ctor public Range(T, T); + method public static android.util.Range create(T, T); + method public T getLower(); + method public T getUpper(); + } + + public final class Size { + ctor public Size(int, int); + method public int getHeight(); + method public int getWidth(); + } + + public final class SizeF { + ctor public SizeF(float, float); + method public float getHeight(); + method public float getWidth(); + } + public class SparseArray implements java.lang.Cloneable { ctor public SparseArray(); ctor public SparseArray(int); diff --git a/core/java/android/hardware/camera2/ColorSpaceTransform.java b/core/java/android/hardware/camera2/ColorSpaceTransform.java new file mode 100644 index 0000000000000..9912e4ba5c19f --- /dev/null +++ b/core/java/android/hardware/camera2/ColorSpaceTransform.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; +import android.hardware.camera2.impl.HashCodeHelpers; + +import java.util.Arrays; + +/** + * Immutable class for describing a 3x3 matrix of {@link Rational} values in row-major order. + * + *

This matrix maps a transform from one color space to another. For the particular color space + * source and target, see the appropriate camera metadata documentation for the key that provides + * this value.

+ * + * @see CameraMetadata + */ +public final class ColorSpaceTransform { + + /** The number of rows in this matrix. */ + private static final int ROWS = 3; + + /** The number of columns in this matrix. */ + private static final int COLUMNS = 3; + + /** The number of total Rational elements in this matrix. */ + private static final int COUNT = ROWS * COLUMNS; + + /** Number of int elements in a rational. */ + private static final int RATIONAL_SIZE = 2; + + /** Numerator offset inside a rational (pair). */ + private static final int OFFSET_NUMERATOR = 0; + + /** Denominator offset inside a rational (pair). */ + private static final int OFFSET_DENOMINATOR = 1; + + /** Number of int elements in this matrix. */ + private static final int COUNT_INT = ROWS * COLUMNS * RATIONAL_SIZE; + + /** + * Create a new immutable {@link ColorSpaceTransform} instance from a {@link Rational} array. + * + *

The elements must be stored in a row-major order.

+ * + * @param elements An array of {@code 9} elements + * + * @throws IllegalArgumentException + * if the count of {@code elements} is not {@code 9} + * @throws NullPointerException + * if {@code elements} or any sub-element is {@code null} + */ + public ColorSpaceTransform(Rational[] elements) { + + checkNotNull(elements, "elements must not be null"); + if (elements.length != COUNT) { + throw new IllegalArgumentException("elements must be " + COUNT + " length"); + } + + mElements = new int[COUNT_INT]; + + for (int i = 0; i < elements.length; ++i) { + checkNotNull(elements, "element[" + i + "] must not be null"); + mElements[i * RATIONAL_SIZE + OFFSET_NUMERATOR] = elements[i].getNumerator(); + mElements[i * RATIONAL_SIZE + OFFSET_DENOMINATOR] = elements[i].getDenominator(); + } + } + + /** + * Create a new immutable {@link ColorSpaceTransform} instance from an {@code int} array. + * + *

The elements must be stored in a row-major order. Each rational is stored + * contiguously as a {@code (numerator, denominator)} pair.

+ * + *

In particular:

{@code
+     * int[] elements = new int[
+     *     N11, D11, N12, D12, N13, D13,
+     *     N21, D21, N22, D22, N23, D23,
+     *     N31, D31, N32, D32, N33, D33
+     * ];
+     *
+     * new ColorSpaceTransform(elements)}
+ * + * where {@code Nij} and {@code Dij} is the numerator and denominator for row {@code i} and + * column {@code j}.

+ * + * @param elements An array of {@code 18} elements + * + * @throws IllegalArgumentException + * if the count of {@code elements} is not {@code 18} + * @throws NullPointerException + * if {@code elements} is {@code null} + */ + public ColorSpaceTransform(int[] elements) { + checkNotNull(elements, "elements must not be null"); + if (elements.length != COUNT_INT) { + throw new IllegalArgumentException("elements must be " + COUNT_INT + " length"); + } + + for (int i = 0; i < elements.length; ++i) { + checkNotNull(elements, "element " + i + " must not be null"); + } + + mElements = Arrays.copyOf(elements, elements.length); + } + + /** + * Get an element of this matrix by its row and column. + * + *

The rows must be within the range [0, 3), + * and the column must be within the range [0, 3).

+ * + * @return element (non-{@code null}) + * + * @throws IllegalArgumentException if column or row was out of range + */ + public Rational getElement(int column, int row) { + if (column < 0 || column >= COLUMNS) { + throw new IllegalArgumentException("column out of range"); + } else if (row < 0 || row >= ROWS) { + throw new IllegalArgumentException("row out of range"); + } + + int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR]; + int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR]; + + return new Rational(numerator, denominator); + } + + /** + * Copy the {@link Rational} elements in row-major order from this matrix into the destination. + * + * @param destination + * an array big enough to hold at least {@code 9} elements after the + * {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + */ + public void copyElements(Rational[] destination, int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < COUNT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + for (int i = 0, j = 0; i < COUNT; ++i, j += RATIONAL_SIZE) { + int numerator = mElements[j + OFFSET_NUMERATOR]; + int denominator = mElements[j + OFFSET_DENOMINATOR]; + + destination[i + offset] = new Rational(numerator, denominator); + } + } + + /** + * Copy the {@link Rational} elements in row-major order from this matrix into the destination. + * + *

Each element is stored as a contiguous rational packed as a + * {@code (numerator, denominator)} pair of ints, identical to the + * {@link ColorSpaceTransform#ColorSpaceTransform(int[]) constructor}.

+ * + * @param destination + * an array big enough to hold at least {@code 18} elements after the + * {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + * + * @see ColorSpaceTransform#ColorSpaceTransform(int[]) + */ + public void copyElements(int[] destination, int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < COUNT_INT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + // Manual copy faster than System#arraycopy for very small loops + for (int i = 0; i < COUNT_INT; ++i) { + destination[i + offset] = mElements[i]; + } + } + + /** + * Check if this {@link ColorSpaceTransform} is equal to another {@link ColorSpaceTransform}. + * + *

Two color space transforms are equal if and only if all of their elements are + * {@link Object#equals equal}.

+ * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof ColorSpaceTransform) { + final ColorSpaceTransform other = (ColorSpaceTransform) obj; + return Arrays.equals(mElements, other.mElements); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mElements); + } + + private final int[] mElements; +}; diff --git a/core/java/android/hardware/camera2/MeteringRectangle.java b/core/java/android/hardware/camera2/MeteringRectangle.java new file mode 100644 index 0000000000000..ff7a745b75a69 --- /dev/null +++ b/core/java/android/hardware/camera2/MeteringRectangle.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.camera2; + +import android.util.Size; +import static com.android.internal.util.Preconditions.*; + +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.impl.HashCodeHelpers; + +/** + * An immutable class to represent a rectangle {@code (x,y, width, height)} with an + * additional weight component. + * + *

The rectangle is defined to be inclusive of the specified coordinates.

+ * + *

When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel + * array, with {@code (0,0)} being the top-left pixel in the + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and + * {@code (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1)} + * being the bottom-right pixel in the active pixel array. + *

+ * + *

The metering weight is nonnegative.

+ */ +public final class MeteringRectangle { + + private final int mX; + private final int mY; + private final int mWidth; + private final int mHeight; + private final int mWeight; + + /** + * Create a new metering rectangle. + * + * @param x coordinate >= 0 + * @param y coordinate >= 0 + * @param width width >= 0 + * @param height height >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + */ + public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) { + mX = checkArgumentNonnegative(x, "x must be nonnegative"); + mY = checkArgumentNonnegative(y, "y must be nonnegative"); + mWidth = checkArgumentNonnegative(width, "width must be nonnegative"); + mHeight = checkArgumentNonnegative(height, "height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Create a new metering rectangle. + * + * @param xy a non-{@code null} {@link Point} with both x,y >= 0 + * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws NullPointerException if any of the arguments were null + */ + public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) { + checkNotNull(xy, "xy must not be null"); + checkNotNull(dimensions, "dimensions must not be null"); + + mX = checkArgumentNonnegative(xy.x, "x must be nonnegative"); + mY = checkArgumentNonnegative(xy.y, "y must be nonnegative"); + mWidth = checkArgumentNonnegative(dimensions.getWidth(), "width must be nonnegative"); + mHeight = checkArgumentNonnegative(dimensions.getHeight(), "height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Create a new metering rectangle. + * + * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws NullPointerException if any of the arguments were null + */ + public MeteringRectangle(Rect rect, int meteringWeight) { + checkNotNull(rect, "rect must not be null"); + + mX = checkArgumentNonnegative(rect.left, "rect.left must be nonnegative"); + mY = checkArgumentNonnegative(rect.top, "rect.top must be nonnegative"); + mWidth = checkArgumentNonnegative(rect.width(), "rect.width must be nonnegative"); + mHeight = checkArgumentNonnegative(rect.height(), "rect.height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Return the X coordinate of the left side of the rectangle. + * + * @return x coordinate >= 0 + */ + public int getX() { + return mX; + } + + /** + * Return the Y coordinate of the upper side of the rectangle. + * + * @return y coordinate >= 0 + */ + public int getY() { + return mY; + } + + /** + * Return the width of the rectangle. + * + * @return width >= 0 + */ + public int getWidth() { + return mWidth; + } + + /** + * Return the height of the rectangle. + * + * @return height >= 0 + */ + public int getHeight() { + return mHeight; + } + + /** + * Return the metering weight of the rectangle. + * + * @return weight >= 0 + */ + public int getMeteringWeight() { + return mWeight; + } + + /** + * Convenience method to create the upper-left (X,Y) coordinate as a {@link Point}. + * + * @return {@code (x,y)} point with both x,y >= 0 + */ + public Point getUpperLeftPoint() { + return new Point(mX, mY); + } + + /** + * Convenience method to create the size from this metering rectangle. + * + *

This strips away the X,Y,weight from the rectangle.

+ * + * @return a Size with non-negative width and height + */ + public Size getSize() { + return new Size(mWidth, mHeight); + } + + /** + * Convenience method to create a {@link Rect} from this metering rectangle. + * + *

This strips away the weight from the rectangle.

+ * + * @return a {@link Rect} with non-negative x1, y1, x2, y2 + */ + public Rect getRect() { + return new Rect(mX, mY, mX + mWidth, mY + mHeight); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object other) { + if (other instanceof MeteringRectangle) { + return equals(other); + } + return false; + } + + /** + * Compare two metering rectangles to see if they are equal. + * + * Two weighted rectangles are only considered equal if each of their components + * (x, y, width, height, weight) is respectively equal. + * + * @param other Another MeteringRectangle + * + * @return {@code true} if the metering rectangles are equal, {@code false} otherwise + */ + public boolean equals(final MeteringRectangle other) { + if (other == null) { + return false; + } + + return (mX == other.mX + && mY == other.mY + && mWidth == other.mWidth + && mHeight == other.mHeight + && mWidth == other.mWidth); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mX, mY, mWidth, mHeight, mWeight); + } +} diff --git a/core/java/android/hardware/camera2/Size.java b/core/java/android/hardware/camera2/Size.java index 45aaeaef45a2c..9328a00323d20 100644 --- a/core/java/android/hardware/camera2/Size.java +++ b/core/java/android/hardware/camera2/Size.java @@ -16,32 +16,55 @@ package android.hardware.camera2; +// TODO: Delete this class, since it was moved to android.util as public API + /** - * A simple immutable class for describing the dimensions of camera image - * buffers. + * Immutable class for describing width and height dimensions in pixels. + * + * @hide */ public final class Size { /** - * Create a new immutable Size instance + * Create a new immutable Size instance. * - * @param width The width to store in the Size instance - * @param height The height to store in the Size instance + * @param width The width of the size, in pixels + * @param height The height of the size, in pixels */ - public Size(int width, int height) { + public Size(final int width, final int height) { mWidth = width; mHeight = height; } + /** + * Get the width of the size (in pixels). + * @return width + */ public final int getWidth() { return mWidth; } + /** + * Get the height of the size (in pixels). + * @return height + */ public final int getHeight() { return mHeight; } + /** + * Check if this size is equal to another size. + *

+ * Two sizes are equal if and only if both their widths and heights are + * equal. + *

+ *

+ * A size object is never equal to any other type of object. + *

+ * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (obj == null) { return false; } @@ -49,27 +72,29 @@ public final class Size { return true; } if (obj instanceof Size) { - Size other = (Size) obj; + final Size other = (Size) obj; return mWidth == other.mWidth && mHeight == other.mHeight; } return false; } + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ @Override public String toString() { return mWidth + "x" + mHeight; } + /** + * {@inheritDoc} + */ @Override public int hashCode() { - final long INT_MASK = 0xffffffffL; - - long asLong = INT_MASK & mWidth; - asLong <<= 32; - - asLong |= (INT_MASK & mHeight); - - return ((Long)asLong).hashCode(); + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); } private final int mWidth; diff --git a/core/java/android/hardware/camera2/impl/HashCodeHelpers.java b/core/java/android/hardware/camera2/impl/HashCodeHelpers.java new file mode 100644 index 0000000000000..2d63827b1ce7e --- /dev/null +++ b/core/java/android/hardware/camera2/impl/HashCodeHelpers.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.impl; + +/** + * Provide hashing functions using the Modified Bernstein hash + */ +public final class HashCodeHelpers { + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + *

Useful to implement a {@link Object#hashCode} for uniformly distributed data.

+ * + * @param array a non-{@code null} array of integers + * + * @return the numeric hash code + */ + public static int hashCode(int[] array) { + if (array == null) { + return 0; + } + + /* + * Note that we use 31 here instead of 33 since it's preferred in Effective Java + * and used elsewhere in the runtime (e.g. Arrays#hashCode) + * + * That being said 33 and 31 are nearly identical in terms of their usefulness + * according to http://svn.apache.org/repos/asf/apr/apr/trunk/tables/apr_hash.c + */ + int h = 1; + for (int x : array) { + // Strength reduction; in case the compiler has illusions about divisions being faster + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + *

Useful to implement a {@link Object#hashCode} for uniformly distributed data.

+ * + * @param array a non-{@code null} array of floats + * + * @return the numeric hash code + */ + public static int hashCode(float[] array) { + if (array == null) { + return 0; + } + + int h = 1; + for (float f : array) { + int x = Float.floatToIntBits(f); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + *

Useful to implement a {@link Object#hashCode} for uniformly distributed data.

+ * + * @param array a non-{@code null} array of objects + * + * @return the numeric hash code + */ + public static int hashCode(T[] array) { + if (array == null) { + return 0; + } + + int h = 1; + for (T o : array) { + int x = (o == null) ? 0 : o.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + public static int hashCode(T a) { + return (a == null) ? 0 : a.hashCode(); + } + + public static int hashCode(T a, T b) { + int h = hashCode(a); + + int x = (b == null) ? 0 : b.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + + return h; + } + + public static int hashCode(T a, T b, T c) { + int h = hashCode(a, b); + + int x = (a == null) ? 0 : a.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + + return h; + } + + public static int hashCode(int x) { + return hashCode(new int[] { x } ); + } + + public static int hashCode(int x, int y) { + return hashCode(new int[] { x, y } ); + } + + public static int hashCode(int x, int y, int z) { + return hashCode(new int[] { x, y, z } ); + } + + public static int hashCode(int x, int y, int z, int w) { + return hashCode(new int[] { x, y, z, w } ); + } + + public static int hashCode(int x, int y, int z, int w, int t) { + return hashCode(new int[] { x, y, z, w, t } ); + } + + +} diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java new file mode 100644 index 0000000000000..9a4bd4b3f2053 --- /dev/null +++ b/core/java/android/util/Range.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static com.android.internal.util.Preconditions.*; + +import android.hardware.camera2.impl.HashCodeHelpers; + +/** + * Immutable class for describing the range of two numeric values. + *

+ * A range (or "interval") defines the inclusive boundaries around a contiguous span of + * values of some {@link Comparable} type; for example, + * "integers from 1 to 100 inclusive." + *

+ *

+ * All ranges are bounded, and the left side of the range is always {@code >=} + * the right side of the range. + *

+ * + *

Although the implementation itself is immutable, there is no restriction that objects + * stored must also be immutable. If mutable objects are stored here, then the range + * effectively becomes mutable.

+ */ +public final class Range> { + /** + * Create a new immutable range. + * + *

+ * The endpoints are {@code [lower, upper]}; that + * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal} + * to {@code upper}. + *

+ * + * @param lower The lower endpoint (inclusive) + * @param upper The upper endpoint (inclusive) + * + * @throws NullPointerException if {@code lower} or {@code upper} is {@code null} + */ + public Range(final T lower, final T upper) { + mLower = checkNotNull(lower, "lower must not be null"); + mUpper = checkNotNull(upper, "upper must not be null"); + + if (lower.compareTo(upper) > 0) { + throw new IllegalArgumentException("lower must be less than or equal to upper"); + } + } + + /** + * Create a new immutable range, with the argument types inferred. + * + *

+ * The endpoints are {@code [lower, upper]}; that + * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal} + * to {@code upper}. + *

+ * + * @param lower The lower endpoint (inclusive) + * @param upper The upper endpoint (inclusive) + * + * @throws NullPointerException if {@code lower} or {@code upper} is {@code null} + */ + public static > Range create(final T lower, final T upper) { + return new Range(lower, upper); + } + + /** + * Get the lower endpoint. + * + * @return a non-{@code null} {@code T} reference + */ + public T getLower() { + return mLower; + } + + /** + * Get the upper endpoint. + * + * @return a non-{@code null} {@code T} reference + */ + public T getUpper() { + return mUpper; + } + + /** + * Compare two ranges for equality. + * + *

A range is considered equal if and only if both the lower and upper endpoints + * are also equal.

+ * + * @return {@code true} if the ranges are equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Range) { + @SuppressWarnings("rawtypes") + final + Range other = (Range) obj; + return mLower.equals(other.mLower) && mUpper.equals(other.mUpper); + } + return false; + } + + /** + * Return the range as a string representation {@code "[lower, upper]"}. + * + * @return string representation of the range + */ + @Override + public String toString() { + return String.format("[%s, %s]", mLower, mUpper); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mLower, mUpper); + } + + private final T mLower; + private final T mUpper; +}; diff --git a/core/java/android/util/Size.java b/core/java/android/util/Size.java new file mode 100644 index 0000000000000..ba1a35f474d6a --- /dev/null +++ b/core/java/android/util/Size.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * Immutable class for describing width and height dimensions in pixels. + */ +public final class Size { + /** + * Create a new immutable Size instance. + * + * @param width The width of the size, in pixels + * @param height The height of the size, in pixels + */ + public Size(int width, int height) { + mWidth = width; + mHeight = height; + } + + /** + * Get the width of the size (in pixels). + * @return width + */ + public int getWidth() { + return mWidth; + } + + /** + * Get the height of the size (in pixels). + * @return height + */ + public int getHeight() { + return mHeight; + } + + /** + * Check if this size is equal to another size. + *

+ * Two sizes are equal if and only if both their widths and heights are + * equal. + *

+ *

+ * A size object is never equal to any other type of object. + *

+ * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Size) { + Size other = (Size) obj; + return mWidth == other.mWidth && mHeight == other.mHeight; + } + return false; + } + + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); + } + + private final int mWidth; + private final int mHeight; +}; diff --git a/core/java/android/util/SizeF.java b/core/java/android/util/SizeF.java new file mode 100644 index 0000000000000..0a8b4edf42c2e --- /dev/null +++ b/core/java/android/util/SizeF.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class for describing width and height dimensions in some arbitrary + * unit. + *

+ * Width and height are finite values stored as a floating point representation. + *

+ */ +public final class SizeF { + /** + * Create a new immutable SizeF instance. + * + *

Both the {@code width} and the {@code height} must be a finite number. + * In particular, {@code NaN} and positive/negative infinity are illegal values.

+ * + * @param width The width of the size + * @param height The height of the size + * + * @throws IllegalArgumentException + * if either {@code width} or {@code height} was not finite. + */ + public SizeF(final float width, final float height) { + mWidth = checkArgumentFinite(width, "width"); + mHeight = checkArgumentFinite(height, "height"); + } + + /** + * Get the width of the size (as an arbitrary unit). + * @return width + */ + public float getWidth() { + return mWidth; + } + + /** + * Get the height of the size (as an arbitrary unit). + * @return height + */ + public float getHeight() { + return mHeight; + } + + /** + * Check if this size is equal to another size. + * + *

Two sizes are equal if and only if both their widths and heights are the same.

+ * + *

For this purpose, the width/height float values are considered to be the same if and only + * if the method {@link Float#floatToIntBits(float)} returns the identical {@code int} value + * when applied to each.

+ * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof SizeF) { + final SizeF other = (SizeF) obj; + return mWidth == other.mWidth && mHeight == other.mHeight; + } + return false; + } + + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight); + } + + private final float mWidth; + private final float mHeight; +}; diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index a54b3640debe7..f6722a675a9e7 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -30,7 +30,7 @@ public class Preconditions { * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(T reference) { + public static T checkNotNull(final T reference) { if (reference == null) { throw new NullPointerException(); } @@ -47,7 +47,7 @@ public class Preconditions { * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(T reference, Object errorMessage) { + public static T checkNotNull(final T reference, final Object errorMessage) { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } @@ -61,7 +61,7 @@ public class Preconditions { * @param expression a boolean expression * @throws IllegalStateException if {@code expression} is false */ - public static void checkState(boolean expression) { + public static void checkState(final boolean expression) { if (!expression) { throw new IllegalStateException(); } @@ -71,11 +71,178 @@ public class Preconditions { * Check the requested flags, throwing if any requested flags are outside * the allowed set. */ - public static void checkFlagsArgument(int requestedFlags, int allowedFlags) { + public static void checkFlagsArgument(final int requestedFlags, final int allowedFlags) { if ((requestedFlags & allowedFlags) != requestedFlags) { throw new IllegalArgumentException("Requested flags 0x" + Integer.toHexString(requestedFlags) + ", but only 0x" + Integer.toHexString(allowedFlags) + " are allowed"); } } + + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static int checkArgumentNonnegative(final int value, final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric long value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static long checkArgumentNonnegative(final long value, final String errorMessage) { + if (value < 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that that the argument numeric value is positive. + * + * @param value a numeric int value + * @param errorMessage the exception message to use if the check fails + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was not positive + */ + public static int checkArgumentPositive(final int value, final String errorMessage) { + if (value <= 0) { + throw new IllegalArgumentException(errorMessage); + } + + return value; + } + + /** + * Ensures that the argument floating point value is a finite number. + * + *

A finite number is defined to be both representable (that is, not NaN) and + * not infinite (that is neither positive or negative infinity).

+ * + * @param value a floating point value + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not finite + */ + public static float checkArgumentFinite(final float value, final String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (Float.isInfinite(value)) { + throw new IllegalArgumentException(valueName + " must not be infinite"); + } + + return value; + } + + /** + * Ensures that the argument floating point value is within the inclusive range. + * + *

While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.

+ * + * @param value a floating point value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static float checkArgumentInRange(float value, float lower, float upper, + String valueName) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException(valueName + " must not be NaN"); + } else if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%f, %f] (too high)", valueName, lower, upper)); + } + + return value; + } + + /** + * Ensures that the array is not {@code null}, and none if its elements are {@code null}. + * + * @param value an array of boxed objects + * @param valueName the name of the argument to use if the check fails + * + * @return the validated array + * + * @throws NullPointerException if the {@code value} or any of its elements were {@code null} + */ + public static T[] checkArrayElementsNotNull(final T[] value, final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + + for (int i = 0; i < value.length; ++i) { + if (value[i] == null) { + throw new NullPointerException( + String.format("%s[%d] must not be null", valueName, i)); + } + } + + return value; + } + + /** + * Ensures that all elements in the argument floating point array are within the inclusive range + * + *

While this can be used to range check against +/- infinity, note that all NaN numbers + * will always be out of range.

+ * + * @param value a floating point array of values + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated floating point value + * + * @throws IllegalArgumentException if any of the elements in {@code value} were out of range + * @throws NullPointerException if the {@code value} was {@code null} + */ + public static float[] checkArrayElementsInRange(float[] value, float lower, float upper, + String valueName) { + checkNotNull(value, valueName + " must not be null"); + + for (int i = 0; i < value.length; ++i) { + float v = value[i]; + + if (Float.isNaN(v)) { + throw new IllegalArgumentException(valueName + "[" + i + "] must not be NaN"); + } else if (v < lower) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too low)", + valueName, i, lower, upper)); + } else if (v > upper) { + throw new IllegalArgumentException( + String.format("%s[%d] is out of range of [%f, %f] (too high)", + valueName, i, lower, upper)); + } + } + + return value; + } }