diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java new file mode 100644 index 0000000000000..dc93a473fe448 --- /dev/null +++ b/core/java/android/util/SparseDoubleArray.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021 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; + +/** + * SparseDoubleArrays map integers to doubles. Unlike a normal array of doubles, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Doubles, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ * + * @see SparseLongArray + * + * @hide + */ +public class SparseDoubleArray implements Cloneable { + /** + * The int->double map, but storing the doubles as longs using + * {@link Double#doubleToRawLongBits(double)}. + */ + private SparseLongArray mValues; + + /** Creates a new SparseDoubleArray containing no mappings. */ + public SparseDoubleArray() { + this(10); + } + + /** + * Creates a new SparseDoubleArray, containing no mappings, that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseDoubleArray(int initialCapacity) { + mValues = new SparseLongArray(initialCapacity); + } + + @Override + public SparseDoubleArray clone() { + SparseDoubleArray clone = null; + try { + clone = (SparseDoubleArray) super.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) { + /* ignore */ + } + return clone; + } + + /** + * Gets the double mapped from the specified key, or 0 + * if no such mapping has been made. + */ + public double get(int key) { + final int index = mValues.indexOfKey(key); + if (index < 0) { + return 0.0d; + } + return valueAt(index); + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, double value) { + mValues.put(key, Double.doubleToRawLongBits(value)); + } + + /** + * Adds a mapping from the specified key to the specified value, + * adding its value to the previous mapping from the specified key if there + * was one. + * + *

This differs from {@link #put} because instead of replacing any previous value, it adds + * (in the numerical sense) to it. + */ + public void add(int key, double summand) { + final double oldValue = get(key); + put(key, oldValue + summand); + } + + /** Returns the number of key-value mappings that this SparseDoubleArray currently stores. */ + public int size() { + return mValues.size(); + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseDoubleArray stores. + * + * @see SparseLongArray#keyAt(int) + */ + public int keyAt(int index) { + return mValues.keyAt(index); + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseDoubleArray stores. + * + * @see SparseLongArray#valueAt(int) + */ + public double valueAt(int index) { + return Double.longBitsToDouble(mValues.valueAt(index)); + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(size() * 34); + buffer.append('{'); + for (int i = 0; i < size(); i++) { + if (i > 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + double value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 7f87885297144..a043756ca2622 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -89,6 +89,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseDoubleArray; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; @@ -12547,81 +12548,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - /** - * SparseDoubleArray map integers to doubles. - * Its implementation is the same as that of {@link SparseLongArray}; see there for details. - * - * @see SparseLongArray - */ - private static class SparseDoubleArray { - /** - * The int->double map, but storing the doubles as longs using - * {@link Double.doubleToRawLongBits(double)}. - */ - private final SparseLongArray mValues = new SparseLongArray(); - - /** - * Gets the double mapped from the specified key, or 0 - * if no such mapping has been made. - */ - public double get(int key) { - if (mValues.indexOfKey(key) >= 0) { - return Double.longBitsToDouble(mValues.get(key)); - } - return 0; - } - - /** - * Adds a mapping from the specified key to the specified value, - * replacing the previous mapping from the specified key if there - * was one. - */ - public void put(int key, double value) { - mValues.put(key, Double.doubleToRawLongBits(value)); - } - - /** - * Adds a mapping from the specified key to the specified value, - * adding to the previous mapping from the specified key if there - * was one. - */ - public void add(int key, double summand) { - final double oldValue = get(key); - put(key, oldValue + summand); - } - - /** - * Returns the number of key-value mappings that this SparseDoubleArray - * currently stores. - */ - public int size() { - return mValues.size(); - } - - /** - * Given an index in the range 0...size()-1, returns - * the key from the indexth key-value mapping that this - * SparseDoubleArray stores. - * - * @see SparseLongArray#keyAt(int) - */ - public int keyAt(int index) { - return mValues.keyAt(index); - } - - /** - * Given an index in the range 0...size()-1, returns - * the value from the indexth key-value mapping that this - * SparseDoubleArray stores. - * - * @see SparseLongArray#valueAt(int) - */ - public double valueAt(int index) { - return Double.longBitsToDouble(mValues.valueAt(index)); - } - - } - /** * Read and record Rail Energy data. */ diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java new file mode 100644 index 0000000000000..2dd3f69852c1d --- /dev/null +++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 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 org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +/** + * Internal tests for {@link SparseDoubleArray}. + * + * Run using: + * atest FrameworksCoreTests:android.util.SparseDoubleArrayTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SparseDoubleArrayTest { + private static final double EXACT_PRECISION = 0; + private static final double PRECISION = 0.000000001; + + @Test + public void testPutGet() { + final SparseDoubleArray sda = new SparseDoubleArray(); + assertEquals("Array should be empty", 0, sda.size()); + + final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2}; + final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, + Double.NaN, + 4311236312.0 / 3431470161413514334123.0, + 431123636434132151313412.0 / 34323.0, + 0}; + for (int i = 0; i < keys.length; i++) { + sda.put(keys[i], values[i]); + } + + assertEquals("Wrong size array", keys.length, sda.size()); + // Due to the implementation, we actually expect EXACT double equality. + for (int i = 0; i < keys.length; i++) { + assertEquals("Wrong value at index " + i, values[i], sda.get(keys[i]), EXACT_PRECISION); + } + + // Now check something that was never put in + assertEquals("Wrong value for absent index", 0, sda.get(100000), EXACT_PRECISION); + } + + @Test + public void testAdd() { + final SparseDoubleArray sda = new SparseDoubleArray(); + + sda.put(4, 6.1); + sda.add(4, -1.2); + sda.add(2, -1.2); + + assertEquals(6.1 - 1.2, sda.get(4), PRECISION); + assertEquals(-1.2, sda.get(2), PRECISION); + } + + @Test + public void testKeyValueAt() { + final SparseDoubleArray sda = new SparseDoubleArray(); + + final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2}; + final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, + Double.NaN, + 4311236312.0 / 3431470161413514334123.0, + 431123636434132151313412.0 / 34323.0, + 0}; + for (int i = 0; i < keys.length; i++) { + sda.put(keys[i], values[i]); + } + + // Sort the sample data. + final ArrayMap map = new ArrayMap<>(keys.length); + for (int i = 0; i < keys.length; i++) { + map.put(keys[i], values[i]); + } + final int[] sortedKeys = Arrays.copyOf(keys, keys.length); + Arrays.sort(sortedKeys); + + for (int i = 0; i < sortedKeys.length; i++) { + final int expectedKey = sortedKeys[i]; + final double expectedValue = map.get(expectedKey); + + assertEquals("Wrong key at index " + i, expectedKey, sda.keyAt(i), PRECISION); + assertEquals("Wrong value at index " + i, expectedValue, sda.valueAt(i), PRECISION); + } + } +}