diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 3aef5d1d59e34..3fdc5711c0d3f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -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 { - private final SparseArray> mData = new SparseArray<>(); - - public void add(int userId, @NonNull String packageName, @Nullable T obj) { - ArrayMap data = mData.get(userId); - if (data == null) { - data = new ArrayMap(); - 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 data = mData.get(userId); - if (data != null) { - data.remove(packageName); - } - } - - @Nullable - public T get(int userId, @NonNull String packageName) { - ArrayMap 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 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 consumer) { - for (int i = numUsers() - 1; i >= 0; --i) { - ArrayMap 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> mTrackedJobs = new UserPackageMap<>(); + private final SparseArrayMap> mTrackedJobs = new SparseArrayMap<>(); /** Timer for each package-userId combo. */ - private final UserPackageMap mPkgTimers = new UserPackageMap<>(); + private final SparseArrayMap mPkgTimers = new SparseArrayMap<>(); /** List of all timing sessions for a package-userId combo, in chronological order. */ - private final UserPackageMap> mTimingSessions = new UserPackageMap<>(); + private final SparseArrayMap> mTimingSessions = new SparseArrayMap<>(); /** * List of alarm listeners for each package that listen for when each package comes back within * quota. */ - private final UserPackageMap mInQuotaAlarmListeners = new UserPackageMap<>(); + private final SparseArrayMap mInQuotaAlarmListeners = new SparseArrayMap<>(); /** Cached calculation results for each app, with the standby buckets as the array indices. */ - private final UserPackageMap mExecutionStatsCache = new UserPackageMap<>(); + private final SparseArrayMap 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 { - private final UserPackageMap mToScheduleStartAlarms = new UserPackageMap<>(); + private final SparseArrayMap 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); diff --git a/api/current.txt b/api/current.txt index 81cf7cf1f7f73..71a14942fe977 100644 --- a/api/current.txt +++ b/api/current.txt @@ -48689,6 +48689,7 @@ package android.util { method public void append(int, E); method public void clear(); method public android.util.SparseArray clone(); + method public boolean contains(int); method public void delete(int); method public E get(int); method public E get(int, E); diff --git a/api/test-current.txt b/api/test-current.txt index 35b1757dae178..a2f16b66ffd26 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4242,6 +4242,25 @@ package android.util { field public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; } + public class SparseArrayMap { + 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); + 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); } diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index 0a15db2278234..484894f980234 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -103,6 +103,14 @@ public class SparseArray 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 null * if no such mapping has been made. diff --git a/core/java/android/util/SparseArrayMap.java b/core/java/android/util/SparseArrayMap.java new file mode 100644 index 0000000000000..3ec6b810fda83 --- /dev/null +++ b/core/java/android/util/SparseArrayMap.java @@ -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 Any class + * @hide + */ +@TestApi +public class SparseArrayMap { + private final SparseArray> 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 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 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 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 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 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 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 consumer) { + for (int i = numMaps() - 1; i >= 0; --i) { + ArrayMap data = mData.valueAt(i); + for (int j = data.size() - 1; j >= 0; --j) { + consumer.accept(data.valueAt(j)); + } + } + } +}