Extract SparseArrayMap from QuotaController.

This structure can be useful in other parts of the system, so extracting
it and making it more generic makes sense.

Bug: 135764360
Bug: 141645789
Test: atest QuotaControllerTest
Test: atest SparseArrayMapTest
Change-Id: I2aeb3ea53e3dc2ec00667a0a2ccdb112cf562377
This commit is contained in:
Kweku Adams
2019-11-14 14:59:57 -08:00
parent cefa9e3ef8
commit a26ae90b4d
5 changed files with 206 additions and 106 deletions

View File

@@ -50,12 +50,11 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
@@ -113,92 +112,6 @@ public final class QuotaController extends StateController {
private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
/**
* A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
* associations.
*/
private static class UserPackageMap<T> {
private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
public void add(int userId, @NonNull String packageName, @Nullable T obj) {
ArrayMap<String, T> data = mData.get(userId);
if (data == null) {
data = new ArrayMap<String, T>();
mData.put(userId, data);
}
data.put(packageName, obj);
}
public void clear() {
for (int i = 0; i < mData.size(); ++i) {
mData.valueAt(i).clear();
}
}
/** Removes all the data for the user, if there was any. */
public void delete(int userId) {
mData.delete(userId);
}
/** Removes the data for the user and package, if there was any. */
public void delete(int userId, @NonNull String packageName) {
ArrayMap<String, T> data = mData.get(userId);
if (data != null) {
data.remove(packageName);
}
}
@Nullable
public T get(int userId, @NonNull String packageName) {
ArrayMap<String, T> data = mData.get(userId);
if (data != null) {
return data.get(packageName);
}
return null;
}
/** @see SparseArray#indexOfKey */
public int indexOfKey(int userId) {
return mData.indexOfKey(userId);
}
/** Returns the userId at the given index. */
public int keyAt(int index) {
return mData.keyAt(index);
}
/** Returns the package name at the given index. */
@NonNull
public String keyAt(int userIndex, int packageIndex) {
return mData.valueAt(userIndex).keyAt(packageIndex);
}
/** Returns the size of the outer (userId) array. */
public int numUsers() {
return mData.size();
}
public int numPackagesForUser(int userId) {
ArrayMap<String, T> data = mData.get(userId);
return data == null ? 0 : data.size();
}
/** Returns the value T at the given user and index. */
@Nullable
public T valueAt(int userIndex, int packageIndex) {
return mData.valueAt(userIndex).valueAt(packageIndex);
}
public void forEach(Consumer<T> consumer) {
for (int i = numUsers() - 1; i >= 0; --i) {
ArrayMap<String, T> data = mData.valueAt(i);
for (int j = data.size() - 1; j >= 0; --j) {
consumer.accept(data.valueAt(j));
}
}
}
}
/**
* Standardize the output of userId-packageName combo.
*/
@@ -378,22 +291,22 @@ public final class QuotaController extends StateController {
}
/** List of all tracked jobs keyed by source package-userId combo. */
private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
private final SparseArrayMap<ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
/** Timer for each package-userId combo. */
private final UserPackageMap<Timer> mPkgTimers = new UserPackageMap<>();
private final SparseArrayMap<Timer> mPkgTimers = new SparseArrayMap<>();
/** List of all timing sessions for a package-userId combo, in chronological order. */
private final UserPackageMap<List<TimingSession>> mTimingSessions = new UserPackageMap<>();
private final SparseArrayMap<List<TimingSession>> mTimingSessions = new SparseArrayMap<>();
/**
* List of alarm listeners for each package that listen for when each package comes back within
* quota.
*/
private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
private final SparseArrayMap<QcAlarmListener> mInQuotaAlarmListeners = new SparseArrayMap<>();
/** Cached calculation results for each app, with the standby buckets as the array indices. */
private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
private final SparseArrayMap<ExecutionStats[]> mExecutionStatsCache = new SparseArrayMap<>();
/** List of UIDs currently in the foreground. */
private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
@@ -1206,9 +1119,9 @@ public final class QuotaController extends StateController {
private void maybeUpdateAllConstraintsLocked() {
boolean changed = false;
for (int u = 0; u < mTrackedJobs.numUsers(); ++u) {
for (int u = 0; u < mTrackedJobs.numMaps(); ++u) {
final int userId = mTrackedJobs.keyAt(u);
for (int p = 0; p < mTrackedJobs.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
final String packageName = mTrackedJobs.keyAt(u, p);
changed |= maybeUpdateConstraintForPkgLocked(userId, packageName);
}
@@ -1268,7 +1181,7 @@ public final class QuotaController extends StateController {
}
private class UidConstraintUpdater implements Consumer<JobStatus> {
private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
private final SparseArrayMap<Integer> mToScheduleStartAlarms = new SparseArrayMap<>();
public boolean wasJobChanged;
@Override
@@ -1290,9 +1203,9 @@ public final class QuotaController extends StateController {
}
void postProcess() {
for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
for (int u = 0; u < mToScheduleStartAlarms.numMaps(); ++u) {
final int userId = mToScheduleStartAlarms.keyAt(u);
for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mToScheduleStartAlarms.numElementsForKey(userId); ++p) {
final String packageName = mToScheduleStartAlarms.keyAt(u, p);
final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
@@ -2547,9 +2460,9 @@ public final class QuotaController extends StateController {
});
pw.println();
for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
for (int u = 0; u < mPkgTimers.numMaps(); ++u) {
final int userId = mPkgTimers.keyAt(u);
for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) {
final String pkgName = mPkgTimers.keyAt(u, p);
mPkgTimers.valueAt(u, p).dump(pw, predicate);
pw.println();
@@ -2571,9 +2484,9 @@ public final class QuotaController extends StateController {
pw.println("Cached execution stats:");
pw.increaseIndent();
for (int u = 0; u < mExecutionStatsCache.numUsers(); ++u) {
for (int u = 0; u < mExecutionStatsCache.numMaps(); ++u) {
final int userId = mExecutionStatsCache.keyAt(u);
for (int p = 0; p < mExecutionStatsCache.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mExecutionStatsCache.numElementsForKey(userId); ++p) {
final String pkgName = mExecutionStatsCache.keyAt(u, p);
ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
@@ -2595,9 +2508,9 @@ public final class QuotaController extends StateController {
pw.println();
pw.println("In quota alarms:");
pw.increaseIndent();
for (int u = 0; u < mInQuotaAlarmListeners.numUsers(); ++u) {
for (int u = 0; u < mInQuotaAlarmListeners.numMaps(); ++u) {
final int userId = mInQuotaAlarmListeners.keyAt(u);
for (int p = 0; p < mInQuotaAlarmListeners.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mInQuotaAlarmListeners.numElementsForKey(userId); ++p) {
final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
@@ -2667,9 +2580,9 @@ public final class QuotaController extends StateController {
}
});
for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
for (int u = 0; u < mPkgTimers.numMaps(); ++u) {
final int userId = mPkgTimers.keyAt(u);
for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) {
final String pkgName = mPkgTimers.keyAt(u, p);
final long psToken = proto.start(
StateControllerProto.QuotaController.PACKAGE_STATS);

View File

@@ -48689,6 +48689,7 @@ package android.util {
method public void append(int, E);
method public void clear();
method public android.util.SparseArray<E> clone();
method public boolean contains(int);
method public void delete(int);
method public E get(int);
method public E get(int, E);

View File

@@ -4242,6 +4242,25 @@ package android.util {
field public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
}
public class SparseArrayMap<T> {
ctor public SparseArrayMap();
method public void add(int, @NonNull String, @Nullable T);
method public void clear();
method public boolean contains(int, @NonNull String);
method public void delete(int);
method @Nullable public T delete(int, @NonNull String);
method public void forEach(@NonNull java.util.function.Consumer<T>);
method @Nullable public T get(int, @NonNull String);
method @Nullable public T getOrDefault(int, @NonNull String, T);
method public int indexOfKey(int);
method public int indexOfKey(int, @NonNull String);
method public int keyAt(int);
method @NonNull public String keyAt(int, int);
method public int numElementsForKey(int);
method public int numMaps();
method @Nullable public T valueAt(int, int);
}
public class TimeUtils {
method public static String formatDuration(long);
}

View File

@@ -103,6 +103,14 @@ public class SparseArray<E> implements Cloneable {
return clone;
}
/**
* Returns true if the key exists in the array. This is equivalent to
* {@link #indexOfKey(int)} >= 0.
*/
public boolean contains(int key) {
return indexOfKey(key) >= 0;
}
/**
* Gets the Object mapped from the specified key, or <code>null</code>
* if no such mapping has been made.

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2019 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import java.util.function.Consumer;
/**
* A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
* associations.
*
* @param <T> Any class
* @hide
*/
@TestApi
public class SparseArrayMap<T> {
private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
/** Add an entry associating obj with the int-String pair. */
public void add(int key, @NonNull String mapKey, @Nullable T obj) {
ArrayMap<String, T> data = mData.get(key);
if (data == null) {
data = new ArrayMap<>();
mData.put(key, data);
}
data.put(mapKey, obj);
}
/** Remove all entries from the map. */
public void clear() {
for (int i = 0; i < mData.size(); ++i) {
mData.valueAt(i).clear();
}
}
/** Return true if the structure contains an explicit entry for the int-String pair. */
public boolean contains(int key, @NonNull String mapKey) {
return mData.contains(key) && mData.get(key).containsKey(mapKey);
}
/** Removes all the data for the key, if there was any. */
public void delete(int key) {
mData.delete(key);
}
/**
* Removes the data for the key and mapKey, if there was any.
*
* @return Returns the value that was stored under the keys, or null if there was none.
*/
@Nullable
public T delete(int key, @NonNull String mapKey) {
ArrayMap<String, T> data = mData.get(key);
if (data != null) {
return data.remove(mapKey);
}
return null;
}
/**
* Get the value associated with the int-String pair.
*/
@Nullable
public T get(int key, @NonNull String mapKey) {
ArrayMap<String, T> data = mData.get(key);
if (data != null) {
return data.get(mapKey);
}
return null;
}
/**
* Returns the value to which the specified key and mapKey are mapped, or defaultValue if this
* map contains no mapping for them.
*/
@Nullable
public T getOrDefault(int key, @NonNull String mapKey, T defaultValue) {
if (mData.contains(key)) {
ArrayMap<String, T> data = mData.get(key);
if (data != null && data.containsKey(mapKey)) {
return data.get(mapKey);
}
}
return defaultValue;
}
/** @see SparseArray#indexOfKey */
public int indexOfKey(int key) {
return mData.indexOfKey(key);
}
/**
* Returns the index of the mapKey.
*
* @see SparseArray#indexOfKey
*/
public int indexOfKey(int key, @NonNull String mapKey) {
ArrayMap<String, T> data = mData.get(key);
if (data != null) {
return data.indexOfKey(mapKey);
}
return -1;
}
/** Returns the key at the given index. */
public int keyAt(int index) {
return mData.keyAt(index);
}
/** Returns the map's key at the given mapIndex for the given keyIndex. */
@NonNull
public String keyAt(int keyIndex, int mapIndex) {
return mData.valueAt(keyIndex).keyAt(mapIndex);
}
/** Returns the size of the outer array. */
public int numMaps() {
return mData.size();
}
/** Returns the number of elements in the map of the given key. */
public int numElementsForKey(int key) {
ArrayMap<String, T> data = mData.get(key);
return data == null ? 0 : data.size();
}
/** Returns the value T at the given key and map index. */
@Nullable
public T valueAt(int keyIndex, int mapIndex) {
return mData.valueAt(keyIndex).valueAt(mapIndex);
}
/** Iterate through all int-String pairs and operate on all of the values. */
public void forEach(@NonNull Consumer<T> consumer) {
for (int i = numMaps() - 1; i >= 0; --i) {
ArrayMap<String, T> data = mData.valueAt(i);
for (int j = data.size() - 1; j >= 0; --j) {
consumer.accept(data.valueAt(j));
}
}
}
}