Loading apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +19 −106 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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(); Loading Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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); Loading Loading @@ -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); Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -48769,6 +48769,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); api/test-current.txt +19 −0 Original line number Diff line number Diff line Loading @@ -4245,6 +4245,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); } Loading core/java/android/util/SparseArray.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. Loading core/java/android/util/SparseArrayMap.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * 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)); } } } } Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +19 −106 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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(); Loading Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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); Loading Loading @@ -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); Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -48769,6 +48769,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);
api/test-current.txt +19 −0 Original line number Diff line number Diff line Loading @@ -4245,6 +4245,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); } Loading
core/java/android/util/SparseArray.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
core/java/android/util/SparseArrayMap.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * 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)); } } } }