summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/AppsFilter.java4
-rw-r--r--services/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/utils/WatchableImpl.java2
-rw-r--r--services/core/java/com/android/server/utils/Watched.java32
-rw-r--r--services/core/java/com/android/server/utils/WatchedArrayList.java416
-rw-r--r--services/core/java/com/android/server/utils/WatchedArrayMap.java52
-rw-r--r--services/core/java/com/android/server/utils/WatchedArraySet.java434
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseArray.java63
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java76
-rw-r--r--services/core/java/com/android/server/utils/WatchedSparseIntArray.java323
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/WatcherTest.java349
12 files changed, 1720 insertions, 35 deletions
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 094be0622bab..b76fff579918 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -443,7 +443,7 @@ public class AppsFilter implements Watchable, Snappable {
}
final StateProvider stateProvider = command -> {
synchronized (injector.getLock()) {
- command.currentState(injector.getSettings().getPackagesLocked().untrackedMap(),
+ command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(),
injector.getUserManagerInternal().getUserInfos());
}
};
@@ -979,7 +979,7 @@ public class AppsFilter implements Watchable, Snappable {
@Nullable
SparseArray<int[]> getVisibilityAllowList(PackageSetting setting, int[] users,
WatchedArrayMap<String, PackageSetting> existingSettings) {
- return getVisibilityAllowList(setting, users, existingSettings.untrackedMap());
+ return getVisibilityAllowList(setting, users, existingSettings.untrackedStorage());
}
/**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7c4dadea89eb..f2aaee2e529f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -471,7 +471,7 @@ public final class Settings implements Watchable, Snappable {
private final File mSystemDir;
public final KeySetManagerService mKeySetManagerService =
- new KeySetManagerService(mPackages.untrackedMap());
+ new KeySetManagerService(mPackages.untrackedStorage());
/** Settings and other information about permissions */
final LegacyPermissionSettings mPermissions;
diff --git a/services/core/java/com/android/server/utils/WatchableImpl.java b/services/core/java/com/android/server/utils/WatchableImpl.java
index 16400b186ab0..d17fca1d7a54 100644
--- a/services/core/java/com/android/server/utils/WatchableImpl.java
+++ b/services/core/java/com/android/server/utils/WatchableImpl.java
@@ -100,7 +100,7 @@ public class WatchableImpl implements Watchable {
/**
* Freeze the {@link Watchable}.
- **/
+ */
public void seal() {
synchronized (mObservers) {
mSealed = true;
diff --git a/services/core/java/com/android/server/utils/Watched.java b/services/core/java/com/android/server/utils/Watched.java
new file mode 100644
index 000000000000..d4a68ee735fd
--- /dev/null
+++ b/services/core/java/com/android/server/utils/Watched.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation type to mark an attribute that is monitored for change detection and
+ * snapshot creation.
+ * TODO(b/176923052) Automate validation of @Watchable attributes.
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.CLASS)
+public @interface Watched {
+}
diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java
new file mode 100644
index 000000000000..bb0ba1329d86
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedArrayList.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * WatchedArrayMap is an {@link android.util.ArrayMap} that can report changes to itself. If its
+ * values are {@link Watchable} then the WatchedArrayMap will also report changes to the values.
+ * A {@link Watchable} is notified only once, no matter how many times it is stored in the array.
+ * @param <E> The element type, stored in the array.
+ */
+public class WatchedArrayList<E> extends WatchableImpl
+ implements Snappable {
+
+ // The storage
+ private final ArrayList<E> mStorage;
+
+ // If true, the array is watching its children
+ private volatile boolean mWatching = false;
+
+ // The local observer
+ private final Watcher mObserver = new Watcher() {
+ @Override
+ public void onChange(@Nullable Watchable what) {
+ WatchedArrayList.this.dispatchChange(what);
+ }
+ };
+
+ /**
+ * A convenience function called when the elements are added to or removed from the storage.
+ * The watchable is always {@link this}.
+ */
+ private void onChanged() {
+ dispatchChange(this);
+ }
+
+ /**
+ * A convenience function. Register the object if it is {@link Watchable} and if the
+ * array is currently watching. Note that the watching flag must be true if this
+ * function is to succeed. Also note that if this is called with the same object
+ * twice, <this> is only registered once.
+ */
+ private void registerChild(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ ((Watchable) o).registerObserver(mObserver);
+ }
+ }
+
+ /**
+ * A convenience function. Unregister the object if it is {@link Watchable} and if the
+ * array is currently watching. This unconditionally removes the object from the
+ * registered list.
+ */
+ private void unregisterChild(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ ((Watchable) o).unregisterObserver(mObserver);
+ }
+ }
+
+ /**
+ * A convenience function. Unregister the object if it is {@link Watchable}, if the
+ * array is currently watching, and if there are no other instances of this object in
+ * the storage. Note that the watching flag must be true if this function is to
+ * succeed. The object must already have been removed from the storage before this
+ * method is called.
+ */
+ private void unregisterChildIf(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ if (!mStorage.contains(o)) {
+ ((Watchable) o).unregisterObserver(mObserver);
+ }
+ }
+ }
+
+ /**
+ * Register a {@link Watcher} with the array. If this is the first Watcher than any
+ * array values that are {@link Watchable} are registered to the array itself.
+ */
+ @Override
+ public void registerObserver(@NonNull Watcher observer) {
+ super.registerObserver(observer);
+ if (registeredObserverCount() == 1) {
+ // The watching flag must be set true before any children are registered.
+ mWatching = true;
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ registerChild(mStorage.get(i));
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link Watcher} from the array. If this is the last Watcher than any
+ * array values that are {@link Watchable} are unregistered to the array itself.
+ */
+ @Override
+ public void unregisterObserver(@NonNull Watcher observer) {
+ super.unregisterObserver(observer);
+ if (registeredObserverCount() == 0) {
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ unregisterChild(mStorage.get(i));
+ }
+ // The watching flag must be true while children are unregistered.
+ mWatching = false;
+ }
+ }
+
+ /**
+ * Create a new empty {@link WatchedArrayList}. The default capacity of an array map
+ * is 0, and will grow once items are added to it.
+ */
+ public WatchedArrayList() {
+ this(0);
+ }
+
+ /**
+ * Create a new {@link WatchedArrayList} with a given initial capacity.
+ */
+ public WatchedArrayList(int capacity) {
+ mStorage = new ArrayList<E>(capacity);
+ }
+
+ /**
+ * Create a new {@link WatchedArrayList} with the content of the collection.
+ */
+ public WatchedArrayList(@Nullable Collection<? extends E> c) {
+ mStorage = new ArrayList<E>();
+ if (c != null) {
+ // There is no need to register children because the WatchedArrayList starts
+ // life unobserved.
+ mStorage.addAll(c);
+ }
+ }
+
+ /**
+ * Create a {@link WatchedArrayList} from an {@link ArrayList}
+ */
+ public WatchedArrayList(@NonNull ArrayList<E> c) {
+ mStorage = new ArrayList<>(c);
+ }
+
+ /**
+ * Create a {@link WatchedArrayList} from an {@link WatchedArrayList}
+ */
+ public WatchedArrayList(@NonNull WatchedArrayList<E> c) {
+ mStorage = new ArrayList<>(c.mStorage);
+ }
+
+ /**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull ArrayList<E> src) {
+ clear();
+ final int end = src.size();
+ mStorage.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ add(src.get(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull ArrayList<E> dst) {
+ dst.clear();
+ final int end = size();
+ dst.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ dst.add(get(i));
+ }
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public ArrayList<E> untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
+ * Append the specified element to the end of the list
+ */
+ public boolean add(E value) {
+ final boolean result = mStorage.add(value);
+ registerChild(value);
+ onChanged();
+ return result;
+ }
+
+ /**
+ * Insert the element into the list
+ */
+ public void add(int index, E value) {
+ mStorage.add(index, value);
+ registerChild(value);
+ onChanged();
+ }
+
+ /**
+ * Append the elements of the collection to the list.
+ */
+ public boolean addAll(Collection<? extends E> c) {
+ if (c.size() > 0) {
+ for (E e: c) {
+ mStorage.add(e);
+ }
+ onChanged();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Insert the elements of the collection into the list at the index.
+ */
+ public boolean addAll(int index, Collection<? extends E> c) {
+ if (c.size() > 0) {
+ for (E e: c) {
+ mStorage.add(index++, e);
+ }
+ onChanged();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Remove all elements from the list.
+ */
+ public void clear() {
+ // The storage cannot be simply cleared. Each element in the storage must be
+ // unregistered. Deregistration is only needed if the array is actually
+ // watching.
+ if (mWatching) {
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ unregisterChild(mStorage.get(i));
+ }
+ }
+ mStorage.clear();
+ onChanged();
+ }
+
+ /**
+ * Return true if the object is in the array.
+ */
+ public boolean contains(Object o) {
+ return mStorage.contains(o);
+ }
+
+ /**
+ * Ensure capacity.
+ */
+ public void ensureCapacity(int min) {
+ mStorage.ensureCapacity(min);
+ }
+
+ /**
+ * Retrieve the element at the specified index.
+ */
+ public E get(int index) {
+ return mStorage.get(index);
+ }
+
+ /**
+ * Return the index of the object. -1 is returned if the object is not in the list.
+ */
+ public int indexOf(Object o) {
+ return mStorage.indexOf(o);
+ }
+
+ /**
+ * True if the list has no elements
+ */
+ public boolean isEmpty() {
+ return mStorage.isEmpty();
+ }
+
+ /**
+ * Return the index of the last occurrence of the object.
+ */
+ public int lastIndexOf(Object o) {
+ return mStorage.lastIndexOf(o);
+ }
+
+ /**
+ * Remove and return the element at the specified position.
+ */
+ public E remove(int index) {
+ final E result = mStorage.remove(index);
+ unregisterChildIf(result);
+ onChanged();
+ return result;
+ }
+
+ /**
+ * Remove the first occurrence of the object in the list. Return true if the object
+ * was actually in the list and false otherwise.
+ */
+ public boolean remove(Object o) {
+ if (mStorage.remove(o)) {
+ unregisterChildIf(o);
+ onChanged();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Replace the object at the index.
+ */
+ public E set(int index, E value) {
+ final E result = mStorage.set(index, value);
+ if (value != result) {
+ unregisterChildIf(result);
+ registerChild(value);
+ onChanged();
+ }
+ return result;
+ }
+
+ /**
+ * Return the number of elements in the list.
+ */
+ public int size() {
+ return mStorage.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof WatchedArrayList) {
+ WatchedArrayList w = (WatchedArrayList) o;
+ return mStorage.equals(w.mStorage);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return mStorage.hashCode();
+ }
+
+ /**
+ * Create a copy of the array. If the element is a subclass of Snapper then the copy
+ * contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The returned snapshot is immutable.
+ * @return A new array whose elements are the elements of <this>.
+ */
+ public WatchedArrayList<E> snapshot() {
+ WatchedArrayList<E> l = new WatchedArrayList<>(size());
+ snapshot(l, this);
+ return l;
+ }
+
+ /**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedArrayList<E> r) {
+ snapshot(this, r);
+ }
+
+ /**
+ * Make the destination a copy of the source. If the element is a subclass of Snapper then the
+ * copy contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The destination must be initially empty. Upon return, the destination is
+ * immutable.
+ * @param dst The destination array. It must be empty.
+ * @param src The source array. It is not modified.
+ */
+ public static <E> void snapshot(@NonNull WatchedArrayList<E> dst,
+ @NonNull WatchedArrayList<E> src) {
+ if (dst.size() != 0) {
+ throw new IllegalArgumentException("snapshot destination is not empty");
+ }
+ final int end = src.size();
+ dst.mStorage.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ final E val = Snapshots.maybeSnapshot(src.get(i));
+ dst.add(i, val);
+ }
+ dst.seal();
+ }
+}
diff --git a/services/core/java/com/android/server/utils/WatchedArrayMap.java b/services/core/java/com/android/server/utils/WatchedArrayMap.java
index e8065f140af7..7c1cde8502bd 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayMap.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayMap.java
@@ -160,10 +160,48 @@ public class WatchedArrayMap<K, V> extends WatchableImpl
}
/**
+ * Create a {@link WatchedArrayMap} from an {@link ArrayMap}
+ */
+ public WatchedArrayMap(@NonNull ArrayMap<K, V> c) {
+ mStorage = new ArrayMap<>(c);
+ }
+
+ /**
+ * Create a {@link WatchedArrayMap} from an {@link WatchedArrayMap}
+ */
+ public WatchedArrayMap(@NonNull WatchedArrayMap<K, V> c) {
+ mStorage = new ArrayMap<>(c.mStorage);
+ }
+
+ /**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull ArrayMap<K, V> src) {
+ clear();
+ final int end = src.size();
+ mStorage.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ put(src.keyAt(i), src.valueAt(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull ArrayMap<K, V> dst) {
+ dst.clear();
+ final int end = size();
+ dst.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ dst.put(keyAt(i), valueAt(i));
+ }
+ }
+
+ /**
* Return the underlying storage. This breaks the wrapper but is necessary when
* passing the array to distant methods.
*/
- public ArrayMap untrackedMap() {
+ public ArrayMap<K, V> untrackedStorage() {
return mStorage;
}
@@ -213,7 +251,7 @@ public class WatchedArrayMap<K, V> extends WatchableImpl
* {@inheritDoc}
*/
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o instanceof WatchedArrayMap) {
WatchedArrayMap w = (WatchedArrayMap) o;
return mStorage.equals(w.mStorage);
@@ -401,6 +439,15 @@ public class WatchedArrayMap<K, V> extends WatchableImpl
}
/**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedArrayMap<K, V> r) {
+ snapshot(this, r);
+ }
+
+ /**
* Make the destination a copy of the source. If the element is a subclass of Snapper then the
* copy contains snapshots of the elements. Otherwise the copy contains references to the
* elements. The destination must be initially empty. Upon return, the destination is
@@ -414,6 +461,7 @@ public class WatchedArrayMap<K, V> extends WatchableImpl
throw new IllegalArgumentException("snapshot destination is not empty");
}
final int end = src.size();
+ dst.mStorage.ensureCapacity(end);
for (int i = 0; i < end; i++) {
final V val = Snapshots.maybeSnapshot(src.valueAt(i));
final K key = src.keyAt(i);
diff --git a/services/core/java/com/android/server/utils/WatchedArraySet.java b/services/core/java/com/android/server/utils/WatchedArraySet.java
new file mode 100644
index 000000000000..5070dd1675d3
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedArraySet.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArraySet;
+
+/**
+ * WatchedArraySet is an {@link android.util.ArraySet} that can report changes to itself. If its
+ * values are {@link Watchable} then the WatchedArraySet will also report changes to the values.
+ * A {@link Watchable} is notified only once, no matter how many times it is stored in the array.
+ * @param <E> The element type
+ */
+public class WatchedArraySet<E> extends WatchableImpl
+ implements Snappable {
+
+ // The storage
+ private final ArraySet<E> mStorage;
+
+ // If true, the array is watching its children
+ private volatile boolean mWatching = false;
+
+ // The local observer
+ private final Watcher mObserver = new Watcher() {
+ @Override
+ public void onChange(@Nullable Watchable what) {
+ WatchedArraySet.this.dispatchChange(what);
+ }
+ };
+
+ /**
+ * A convenience function called when the elements are added to or removed from the storage.
+ * The watchable is always {@link this}.
+ */
+ private void onChanged() {
+ dispatchChange(this);
+ }
+
+ /**
+ * A convenience function. Register the object if it is {@link Watchable} and if the
+ * array is currently watching. Note that the watching flag must be true if this
+ * function is to succeed. Also note that if this is called with the same object
+ * twice, <this> is only registered once.
+ */
+ private void registerChild(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ ((Watchable) o).registerObserver(mObserver);
+ }
+ }
+
+ /**
+ * A convenience function. Unregister the object if it is {@link Watchable} and if the
+ * array is currently watching. This unconditionally removes the object from the
+ * registered list.
+ */
+ private void unregisterChild(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ ((Watchable) o).unregisterObserver(mObserver);
+ }
+ }
+
+ /**
+ * A convenience function. Unregister the object if it is {@link Watchable}, if the
+ * array is currently watching, and if there are no other instances of this object in
+ * the storage. Note that the watching flag must be true if this function is to
+ * succeed. The object must already have been removed from the storage before this
+ * method is called.
+ */
+ private void unregisterChildIf(Object o) {
+ if (mWatching && o instanceof Watchable) {
+ if (!mStorage.contains(o)) {
+ ((Watchable) o).unregisterObserver(mObserver);
+ }
+ }
+ }
+
+ /**
+ * Register a {@link Watcher} with the array. If this is the first Watcher than any
+ * array values that are {@link Watchable} are registered to the array itself.
+ */
+ @Override
+ public void registerObserver(@NonNull Watcher observer) {
+ super.registerObserver(observer);
+ if (registeredObserverCount() == 1) {
+ // The watching flag must be set true before any children are registered.
+ mWatching = true;
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ registerChild(mStorage.valueAt(i));
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link Watcher} from the array. If this is the last Watcher than any
+ * array values that are {@link Watchable} are unregistered to the array itself.
+ */
+ @Override
+ public void unregisterObserver(@NonNull Watcher observer) {
+ super.unregisterObserver(observer);
+ if (registeredObserverCount() == 0) {
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ unregisterChild(mStorage.valueAt(i));
+ }
+ // The watching flag must be true while children are unregistered.
+ mWatching = false;
+ }
+ }
+
+ /**
+ * Create a new empty {@link WatchedArraySet}. The default capacity of an array map
+ * is 0, and will grow once items are added to it.
+ */
+ public WatchedArraySet() {
+ this(0, false);
+ }
+
+ /**
+ * Create a new {@link WatchedArraySet} with a given initial capacity.
+ */
+ public WatchedArraySet(int capacity) {
+ this(capacity, false);
+ }
+
+ /** {@hide} */
+ public WatchedArraySet(int capacity, boolean identityHashCode) {
+ mStorage = new ArraySet<E>(capacity, identityHashCode);
+ }
+
+ /**
+ * Create a new {@link WatchedArraySet} with items from the given array
+ */
+ public WatchedArraySet(@Nullable E[] array) {
+ mStorage = new ArraySet(array);
+ }
+
+ /**
+ * Create a {@link WatchedArraySet} from an {@link ArraySet}
+ */
+ public WatchedArraySet(@NonNull ArraySet<E> c) {
+ mStorage = new ArraySet<>(c);
+ }
+
+ /**
+ * Create a {@link WatchedArraySet} from an {@link WatchedArraySet}
+ */
+ public WatchedArraySet(@NonNull WatchedArraySet<E> c) {
+ mStorage = new ArraySet<>(c.mStorage);
+ }
+
+ /**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull ArraySet<E> src) {
+ clear();
+ final int end = src.size();
+ mStorage.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ add(src.valueAt(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull ArraySet<E> dst) {
+ dst.clear();
+ final int end = size();
+ dst.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ dst.add(valueAt(i));
+ }
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public ArraySet<E> untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
+ * Make the array map empty. All storage is released.
+ */
+ public void clear() {
+ // The storage cannot be simply cleared. Each element in the storage must be
+ // unregistered. Deregistration is only needed if the array is actually
+ // watching.
+ if (mWatching) {
+ final int end = mStorage.size();
+ for (int i = 0; i < end; i++) {
+ unregisterChild(mStorage.valueAt(i));
+ }
+ }
+ mStorage.clear();
+ onChanged();
+ }
+
+ /**
+ * Check whether a value exists in the set.
+ *
+ * @param key The value to search for.
+ * @return Returns true if the value exists, else false.
+ */
+ public boolean contains(Object key) {
+ return mStorage.contains(key);
+ }
+
+ /**
+ * Returns the index of a value in the set.
+ *
+ * @param key The value to search for.
+ * @return Returns the index of the value if it exists, else a negative integer.
+ */
+ public int indexOf(Object key) {
+ return mStorage.indexOf(key);
+ }
+
+ /**
+ * Return the value at the given index in the array.
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, an
+ * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+ *
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value stored at the given index.
+ */
+ public E valueAt(int index) {
+ return mStorage.valueAt(index);
+ }
+
+ /**
+ * Return true if the array map contains no items.
+ */
+ public boolean isEmpty() {
+ return mStorage.isEmpty();
+ }
+
+ /**
+ * Adds the specified object to this set. The set is not modified if it
+ * already contains the object.
+ *
+ * @param value the object to add.
+ * @return {@code true} if this set is modified, {@code false} otherwise.
+ */
+ public boolean add(E value) {
+ final boolean result = mStorage.add(value);
+ registerChild(value);
+ onChanged();
+ return result;
+ }
+
+ /**
+ * Special fast path for appending items to the end of the array without validation.
+ * The array must already be large enough to contain the item.
+ * @hide
+ */
+ public void append(E value) {
+ mStorage.append(value);
+ registerChild(value);
+ onChanged();
+ }
+
+ /**
+ * Perform a {@link #add(Object)} of all values in <var>array</var>
+ * @param array The array whose contents are to be retrieved.
+ */
+ public void addAll(ArraySet<? extends E> array) {
+ final int end = array.size();
+ for (int i = 0; i < end; i++) {
+ add(array.valueAt(i));
+ }
+ }
+
+ /**
+ * Perform a {@link #add(Object)} of all values in <var>array</var>
+ * @param array The array whose contents are to be retrieved.
+ */
+ public void addAll(WatchedArraySet<? extends E> array) {
+ final int end = array.size();
+ for (int i = 0; i < end; i++) {
+ add(array.valueAt(i));
+ }
+ }
+
+ /**
+ * Removes the specified object from this set.
+ *
+ * @param o the object to remove.
+ * @return {@code true} if this set was modified, {@code false} otherwise.
+ */
+ public boolean remove(Object o) {
+ if (mStorage.remove(o)) {
+ unregisterChildIf(o);
+ onChanged();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the key/value mapping at the given index.
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, an
+ * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+ *
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value that was stored at this index.
+ */
+ public E removeAt(int index) {
+ final E result = mStorage.removeAt(index);
+ unregisterChildIf(result);
+ onChanged();
+ return result;
+ }
+
+ /**
+ * Perform a {@link #remove(Object)} of all values in <var>array</var>
+ * @param array The array whose contents are to be removed.
+ */
+ public boolean removeAll(ArraySet<? extends E> array) {
+ final int end = array.size();
+ boolean any = false;
+ for (int i = 0; i < end; i++) {
+ any = remove(array.valueAt(i)) || any;
+ }
+ return any;
+ }
+
+ /**
+ * Return the number of items in this array map.
+ */
+ public int size() {
+ return mStorage.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation returns false if the object is not a set, or
+ * if the sets have different sizes. Otherwise, for each value in this
+ * set, it checks to make sure the value also exists in the other set.
+ * If any value doesn't exist, the method returns false; otherwise, it
+ * returns true.
+ */
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object instanceof WatchedArraySet) {
+ return mStorage.equals(((WatchedArraySet) object).mStorage);
+ } else {
+ return mStorage.equals(object);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return mStorage.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation composes a string by iterating over its values. If
+ * this set contains itself as a value, the string "(this Set)"
+ * will appear in its place.
+ */
+ @Override
+ public String toString() {
+ return mStorage.toString();
+ }
+
+ /**
+ * Create a copy of the array. If the element is a subclass of Snapper then the copy
+ * contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The returned snapshot is immutable.
+ * @return A new array whose elements are the elements of <this>.
+ */
+ public WatchedArraySet<E> snapshot() {
+ WatchedArraySet<E> l = new WatchedArraySet<>();
+ snapshot(l, this);
+ return l;
+ }
+
+ /**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedArraySet<E> r) {
+ snapshot(this, r);
+ }
+
+ /**
+ * Make the destination a copy of the source. If the element is a subclass of Snapper then the
+ * copy contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The destination must be initially empty. Upon return, the destination is
+ * immutable.
+ * @param dst The destination array. It must be empty.
+ * @param src The source array. It is not modified.
+ */
+ public static <E> void snapshot(@NonNull WatchedArraySet<E> dst,
+ @NonNull WatchedArraySet<E> src) {
+ if (dst.size() != 0) {
+ throw new IllegalArgumentException("snapshot destination is not empty");
+ }
+ final int end = src.size();
+ dst.mStorage.ensureCapacity(end);
+ for (int i = 0; i < end; i++) {
+ final E val = Snapshots.maybeSnapshot(src.valueAt(i));
+ dst.append(val);
+ }
+ dst.seal();
+ }
+}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseArray.java b/services/core/java/com/android/server/utils/WatchedSparseArray.java
index 6797c6eb7801..9b99b9176d19 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseArray.java
@@ -143,6 +143,13 @@ public class WatchedSparseArray<E> extends WatchableImpl
}
/**
+ * Create a {@link WatchedSparseArray} from a {@link SparseArray}
+ */
+ public WatchedSparseArray(@NonNull SparseArray<E> c) {
+ mStorage = c.clone();
+ }
+
+ /**
* The copy constructor does not copy the watcher data.
*/
public WatchedSparseArray(@NonNull WatchedSparseArray<E> r) {
@@ -150,6 +157,36 @@ public class WatchedSparseArray<E> extends WatchableImpl
}
/**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull SparseArray<E> src) {
+ clear();
+ final int end = src.size();
+ for (int i = 0; i < end; i++) {
+ put(src.keyAt(i), src.valueAt(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull SparseArray<E> dst) {
+ dst.clear();
+ final int end = size();
+ for (int i = 0; i < end; i++) {
+ dst.put(keyAt(i), valueAt(i));
+ }
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public SparseArray<E> untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
* Returns true if the key exists in the array. This is equivalent to
* {@link #indexOfKey(int)} >= 0.
*
@@ -390,6 +427,21 @@ public class WatchedSparseArray<E> extends WatchableImpl
onChanged();
}
+ @Override
+ public int hashCode() {
+ return mStorage.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof WatchedSparseArray) {
+ WatchedSparseArray w = (WatchedSparseArray) o;
+ return mStorage.equals(w.mStorage);
+ } else {
+ return false;
+ }
+ }
+
/**
* <p>This implementation composes a string by iterating over its mappings. If
* this map contains itself as a value, the string "(this Map)"
@@ -407,12 +459,21 @@ public class WatchedSparseArray<E> extends WatchableImpl
* @return A new array whose elements are the elements of <this>.
*/
public WatchedSparseArray<E> snapshot() {
- WatchedSparseArray<E> l = new WatchedSparseArray<>();
+ WatchedSparseArray<E> l = new WatchedSparseArray<>(size());
snapshot(l, this);
return l;
}
/**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedSparseArray<E> r) {
+ snapshot(this, r);
+ }
+
+ /**
* Make the destination a copy of the source. If the element is a subclass of Snapper then the
* copy contains snapshots of the elements. Otherwise the copy contains references to the
* elements. The destination must be initially empty. Upon return, the destination is
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
index b845eea168a5..772a8d07cffb 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java
@@ -17,6 +17,7 @@
package com.android.server.utils;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.SparseBooleanArray;
/**
@@ -53,6 +54,13 @@ public class WatchedSparseBooleanArray extends WatchableImpl
}
/**
+ * Create a {@link WatchedSparseBooleanArray} from a {@link SparseBooleanArray}
+ */
+ public WatchedSparseBooleanArray(@NonNull SparseBooleanArray c) {
+ mStorage = c.clone();
+ }
+
+ /**
* The copy constructor does not copy the watcher data.
*/
public WatchedSparseBooleanArray(@NonNull WatchedSparseBooleanArray r) {
@@ -60,6 +68,36 @@ public class WatchedSparseBooleanArray extends WatchableImpl
}
/**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull SparseBooleanArray src) {
+ clear();
+ final int end = src.size();
+ for (int i = 0; i < end; i++) {
+ put(src.keyAt(i), src.valueAt(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull SparseBooleanArray dst) {
+ dst.clear();
+ final int end = size();
+ for (int i = 0; i < end; i++) {
+ dst.put(keyAt(i), valueAt(i));
+ }
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public SparseBooleanArray untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
* Gets the boolean mapped from the specified key, or <code>false</code>
* if no such mapping has been made.
*/
@@ -99,10 +137,10 @@ public class WatchedSparseBooleanArray extends WatchableImpl
* was one.
*/
public void put(int key, boolean value) {
- if (mStorage.get(key) != value) {
- mStorage.put(key, value);
- onChanged();
- }
+ // There is no fast way to know if the key exists with the input value, so this
+ // method always notifies change listeners.
+ mStorage.put(key, value);
+ onChanged();
}
/**
@@ -219,8 +257,13 @@ public class WatchedSparseBooleanArray extends WatchableImpl
}
@Override
- public boolean equals(Object that) {
- return this == that || mStorage.equals(that);
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof WatchedSparseBooleanArray) {
+ WatchedSparseBooleanArray w = (WatchedSparseBooleanArray) o;
+ return mStorage.equals(w.mStorage);
+ } else {
+ return false;
+ }
}
/**
@@ -249,13 +292,26 @@ public class WatchedSparseBooleanArray extends WatchableImpl
* @param r The source array, which is copied into <this>
*/
public void snapshot(@NonNull WatchedSparseBooleanArray r) {
- if (size() != 0) {
+ snapshot(this, r);
+ }
+
+ /**
+ * Make the destination a copy of the source. If the element is a subclass of Snapper then the
+ * copy contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The destination must be initially empty. Upon return, the destination is
+ * immutable.
+ * @param dst The destination array. It must be empty.
+ * @param src The source array. It is not modified.
+ */
+ public static void snapshot(@NonNull WatchedSparseBooleanArray dst,
+ @NonNull WatchedSparseBooleanArray src) {
+ if (dst.size() != 0) {
throw new IllegalArgumentException("snapshot destination is not empty");
}
- final int end = r.size();
+ final int end = src.size();
for (int i = 0; i < end; i++) {
- put(r.keyAt(i), r.valueAt(i));
+ dst.put(src.keyAt(i), src.valueAt(i));
}
- seal();
+ dst.seal();
}
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java
new file mode 100644
index 000000000000..72705bf24199
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseIntArray;
+
+/**
+ * A watched variant of SparseIntArray. Changes to the array are notified to
+ * registered {@link Watcher}s.
+ */
+public class WatchedSparseIntArray extends WatchableImpl
+ implements Snappable {
+
+ // The storage
+ private final SparseIntArray mStorage;
+
+ // A private convenience function
+ private void onChanged() {
+ dispatchChange(this);
+ }
+
+ /**
+ * Creates a new WatchedSparseIntArray containing no mappings.
+ */
+ public WatchedSparseIntArray() {
+ mStorage = new SparseIntArray();
+ }
+
+ /**
+ * Creates a new WatchedSparseIntArray 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 WatchedSparseIntArray(int initialCapacity) {
+ mStorage = new SparseIntArray(initialCapacity);
+ }
+
+ /**
+ * Create a {@link WatchedSparseIntArray} from a {@link SparseIntArray}
+ */
+ public WatchedSparseIntArray(@NonNull SparseIntArray c) {
+ mStorage = c.clone();
+ }
+
+ /**
+ * The copy constructor does not copy the watcher data.
+ */
+ public WatchedSparseIntArray(@NonNull WatchedSparseIntArray r) {
+ mStorage = r.mStorage.clone();
+ }
+
+ /**
+ * Make <this> a copy of src. Any data in <this> is discarded.
+ */
+ public void copyFrom(@NonNull SparseIntArray src) {
+ clear();
+ final int end = src.size();
+ for (int i = 0; i < end; i++) {
+ put(src.keyAt(i), src.valueAt(i));
+ }
+ }
+
+ /**
+ * Make dst a copy of <this>. Any previous data in dst is discarded.
+ */
+ public void copyTo(@NonNull SparseIntArray dst) {
+ dst.clear();
+ final int end = size();
+ for (int i = 0; i < end; i++) {
+ dst.put(keyAt(i), valueAt(i));
+ }
+ }
+
+ /**
+ * Return the underlying storage. This breaks the wrapper but is necessary when
+ * passing the array to distant methods.
+ */
+ public SparseIntArray untrackedStorage() {
+ return mStorage;
+ }
+
+ /**
+ * Gets the boolean mapped from the specified key, or <code>false</code>
+ * if no such mapping has been made.
+ */
+ public int get(int key) {
+ return mStorage.get(key);
+ }
+
+ /**
+ * Gets the boolean mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public int get(int key, int valueIfKeyNotFound) {
+ return mStorage.get(key, valueIfKeyNotFound);
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(int key) {
+ // This code ensures that onChanged is called only if the key is actually
+ // present.
+ final int index = mStorage.indexOfKey(key);
+ if (index >= 0) {
+ mStorage.removeAt(index);
+ onChanged();
+ }
+ }
+
+ /**
+ * Removes the mapping at the specified index.
+ */
+ public void removeAt(int index) {
+ mStorage.removeAt(index);
+ onChanged();
+ }
+
+ /**
+ * 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, int value) {
+ // There is no fast way to know if the key exists with the input value, so this
+ // method always notifies change listeners.
+ mStorage.put(key, value);
+ onChanged();
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ return mStorage.size();
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseIntArray stores.
+ *
+ * <p>The keys corresponding to indices in ascending order are guaranteed to
+ * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+ * smallest key and <code>keyAt(size()-1)</code> will return the largest
+ * key.</p>
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+ * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+ * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+ */
+ public int keyAt(int index) {
+ return mStorage.keyAt(index);
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseIntArray stores.
+ *
+ * <p>The values corresponding to indices in ascending order are guaranteed
+ * to be associated with keys in ascending order, e.g.,
+ * <code>valueAt(0)</code> will return the value associated with the
+ * smallest key and <code>valueAt(size()-1)</code> will return the value
+ * associated with the largest key.</p>
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+ * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+ * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+ */
+ public int valueAt(int index) {
+ return mStorage.valueAt(index);
+ }
+
+ /**
+ * Directly set the value at a particular index.
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+ * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+ * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+ */
+ public void setValueAt(int index, int value) {
+ if (mStorage.valueAt(index) != value) {
+ mStorage.setValueAt(index, value);
+ onChanged();
+ }
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(int key) {
+ return mStorage.indexOfKey(key);
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(int value) {
+ return mStorage.indexOfValue(value);
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ final int count = size();
+ mStorage.clear();
+ if (count > 0) {
+ onChanged();
+ }
+ }
+
+ /**
+ * Puts a key/value pair into the array, optimizing for the case where
+ * the key is greater than all existing keys in the array.
+ */
+ public void append(int key, int value) {
+ mStorage.append(key, value);
+ onChanged();
+ }
+
+ /**
+ * Provides a copy of keys.
+ **/
+ public int[] copyKeys() {
+ return mStorage.copyKeys();
+ }
+
+ @Override
+ public int hashCode() {
+ return mStorage.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof WatchedSparseIntArray) {
+ WatchedSparseIntArray w = (WatchedSparseIntArray) o;
+ return mStorage.equals(w.mStorage);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation composes a string by iterating over its mappings.
+ */
+ @Override
+ public String toString() {
+ return mStorage.toString();
+ }
+
+ /**
+ * Create a snapshot. The snapshot does not include any {@link Watchable}
+ * information.
+ */
+ public WatchedSparseIntArray snapshot() {
+ WatchedSparseIntArray l = new WatchedSparseIntArray(this);
+ l.seal();
+ return l;
+ }
+
+ /**
+ * Make <this> a snapshot of the argument. Note that <this> is immutable when the
+ * method returns. <this> must be empty when the function is called.
+ * @param r The source array, which is copied into <this>
+ */
+ public void snapshot(@NonNull WatchedSparseIntArray r) {
+ snapshot(this, r);
+ }
+
+ /**
+ * Make the destination a copy of the source. If the element is a subclass of Snapper then the
+ * copy contains snapshots of the elements. Otherwise the copy contains references to the
+ * elements. The destination must be initially empty. Upon return, the destination is
+ * immutable.
+ * @param dst The destination array. It must be empty.
+ * @param src The source array. It is not modified.
+ */
+ public static void snapshot(@NonNull WatchedSparseIntArray dst,
+ @NonNull WatchedSparseIntArray src) {
+ if (dst.size() != 0) {
+ throw new IllegalArgumentException("snapshot destination is not empty");
+ }
+ final int end = src.size();
+ for (int i = 0; i < end; i++) {
+ dst.put(src.keyAt(i), src.valueAt(i));
+ }
+ dst.seal();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 282047afaa51..333ec9295b93 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1215,7 +1215,7 @@ public class PackageManagerSettingsTests {
private void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
ArrayMap<String, PackageSetting> packages =
- settings.mPackages.untrackedMap();
+ settings.mPackages.untrackedStorage();
KeySetManagerService ksms = settings.mKeySetManagerService;
/* verify keyset and public key ref counts */
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index 9bea9d4cedbd..7c65dc03a57e 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -20,12 +20,20 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.util.ArrayList;
+
/**
* Test class for {@link Watcher}, {@link Watchable}, {@link WatchableImpl},
* {@link WatchedArrayMap}, {@link WatchedSparseArray}, and
@@ -40,7 +48,7 @@ public class WatcherTest {
// A counter to generate unique IDs for Leaf elements.
private int mLeafId = 0;
- // Useful indices used int the tests.
+ // Useful indices used in the tests.
private static final int INDEX_A = 1;
private static final int INDEX_B = 2;
private static final int INDEX_C = 3;
@@ -171,6 +179,7 @@ public class WatcherTest {
@Test
public void testWatchedArrayMap() {
+ final String name = "WatchedArrayMap";
WatchableTester tester;
// Create a few leaves
@@ -183,7 +192,7 @@ public class WatcherTest {
WatchedArrayMap<Integer, Leaf> array = new WatchedArrayMap<>();
array.put(INDEX_A, leafA);
array.put(INDEX_B, leafB);
- tester = new WatchableTester(array, "WatchedArrayMap");
+ tester = new WatchableTester(array, name);
tester.verify(0, "Initial array - no registration");
leafA.tick();
tester.verify(0, "Updates with no registration");
@@ -231,20 +240,20 @@ public class WatcherTest {
final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot();
tester.verify(14, "Generate snapshot (no changes)");
// Verify that the snapshot is a proper copy of the source.
- assertEquals("WatchedArrayMap snap same size",
+ assertEquals(name + " snap same size",
array.size(), arraySnap.size());
for (int i = 0; i < array.size(); i++) {
for (int j = 0; j < arraySnap.size(); j++) {
- assertTrue("WatchedArrayMap elements differ",
+ assertTrue(name + " elements differ",
array.valueAt(i) != arraySnap.valueAt(j));
}
- assertTrue("WatchedArrayMap element copy",
+ assertTrue(name + " element copy",
array.valueAt(i).equals(arraySnap.valueAt(i)));
}
leafD.tick();
tester.verify(15, "Tick after snapshot");
// Verify that the snapshot is sealed
- verifySealed("WatchedArrayMap", ()->arraySnap.put(INDEX_A, leafA));
+ verifySealed(name, ()->arraySnap.put(INDEX_A, leafA));
}
// Recreate the snapshot since the test corrupted it.
{
@@ -253,10 +262,235 @@ public class WatcherTest {
final Leaf arraySnapElement = arraySnap.valueAt(0);
verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
}
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out failed";
+ ArrayMap<Integer, Leaf> base = new ArrayMap<>();
+ array.copyTo(base);
+ WatchedArrayMap<Integer, Leaf> copy = new WatchedArrayMap<>();
+ copy.copyFrom(base);
+ if (!array.equals(copy)) {
+ fail(msg);
+ }
+ }
+ }
+
+ @Test
+ public void testWatchedArraySet() {
+ final String name = "WatchedArraySet";
+ WatchableTester tester;
+
+ // Create a few leaves
+ Leaf leafA = new Leaf();
+ Leaf leafB = new Leaf();
+ Leaf leafC = new Leaf();
+ Leaf leafD = new Leaf();
+
+ // Test WatchedArraySet
+ WatchedArraySet<Leaf> array = new WatchedArraySet<>();
+ array.add(leafA);
+ array.add(leafB);
+ tester = new WatchableTester(array, name);
+ tester.verify(0, "Initial array - no registration");
+ leafA.tick();
+ tester.verify(0, "Updates with no registration");
+ tester.register();
+ tester.verify(0, "Updates with no registration");
+ leafA.tick();
+ tester.verify(1, "Updates with registration");
+ leafB.tick();
+ tester.verify(2, "Updates with registration");
+ array.remove(leafB);
+ tester.verify(3, "Removed b");
+ leafB.tick();
+ tester.verify(3, "Updates with b not watched");
+ array.add(leafB);
+ array.add(leafB);
+ tester.verify(5, "Added b once");
+ leafB.tick();
+ tester.verify(6, "Changed b - single notification");
+ array.remove(leafB);
+ tester.verify(7, "Removed b");
+ leafB.tick();
+ tester.verify(7, "Changed b - not watched");
+ array.remove(leafB);
+ tester.verify(7, "Removed non-existent b");
+ array.clear();
+ tester.verify(8, "Cleared array");
+ leafA.tick();
+ tester.verify(8, "Change to a not in array");
+
+ // Special methods
+ array.add(leafA);
+ array.add(leafB);
+ array.add(leafC);
+ tester.verify(11, "Added a, b, c");
+ leafC.tick();
+ tester.verify(12, "Ticked c");
+ array.removeAt(array.indexOf(leafC));
+ tester.verify(13, "Removed c");
+ leafC.tick();
+ tester.verify(13, "Ticked c, not registered");
+ array.append(leafC);
+ tester.verify(14, "Append c");
+ leafC.tick();
+ leafD.tick();
+ tester.verify(15, "Ticked d and c");
+ assertEquals("Verify three elements", 3, array.size());
+
+ // Snapshot
+ {
+ final WatchedArraySet<Leaf> arraySnap = array.snapshot();
+ tester.verify(15, "Generate snapshot (no changes)");
+ // Verify that the snapshot is a proper copy of the source.
+ assertEquals(name + " snap same size",
+ array.size(), arraySnap.size());
+ for (int i = 0; i < array.size(); i++) {
+ for (int j = 0; j < arraySnap.size(); j++) {
+ assertTrue(name + " elements differ",
+ array.valueAt(i) != arraySnap.valueAt(j));
+ }
+ }
+ leafC.tick();
+ tester.verify(16, "Tick after snapshot");
+ // Verify that the array snapshot is sealed
+ verifySealed(name, ()->arraySnap.add(leafB));
+ }
+ // Recreate the snapshot since the test corrupted it.
+ {
+ final WatchedArraySet<Leaf> arraySnap = array.snapshot();
+ // Verify that elements are also snapshots
+ final Leaf arraySnapElement = arraySnap.valueAt(0);
+ verifySealed(name + " snap element", ()->arraySnapElement.tick());
+ }
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out";
+ ArraySet<Leaf> base = new ArraySet<>();
+ array.copyTo(base);
+ WatchedArraySet<Leaf> copy = new WatchedArraySet<>();
+ copy.copyFrom(base);
+ if (!array.equals(copy)) {
+ fail(msg);
+ }
+ }
+ }
+
+ @Test
+ public void testWatchedArrayList() {
+ final String name = "WatchedArrayList";
+ WatchableTester tester;
+
+ // Create a few leaves
+ Leaf leafA = new Leaf();
+ Leaf leafB = new Leaf();
+ Leaf leafC = new Leaf();
+ Leaf leafD = new Leaf();
+
+ // Redefine the indices used in the tests to be zero-based
+ final int indexA = 0;
+ final int indexB = 1;
+ final int indexC = 2;
+ final int indexD = 3;
+
+ // Test WatchedArrayList
+ WatchedArrayList<Leaf> array = new WatchedArrayList<>();
+ // A spacer that takes up index 0 (and is not Watchable).
+ array.add(indexA, leafA);
+ array.add(indexB, leafB);
+ tester = new WatchableTester(array, name);
+ tester.verify(0, "Initial array - no registration");
+ leafA.tick();
+ tester.verify(0, "Updates with no registration");
+ tester.register();
+ tester.verify(0, "Updates with no registration");
+ leafA.tick();
+ tester.verify(1, "Updates with registration");
+ leafB.tick();
+ tester.verify(2, "Updates with registration");
+ array.remove(indexB);
+ tester.verify(3, "Removed b");
+ leafB.tick();
+ tester.verify(3, "Updates with b not watched");
+ array.add(indexB, leafB);
+ array.add(indexC, leafB);
+ tester.verify(5, "Added b twice");
+ leafB.tick();
+ tester.verify(6, "Changed b - single notification");
+ array.remove(indexC);
+ tester.verify(7, "Removed first b");
+ leafB.tick();
+ tester.verify(8, "Changed b - single notification");
+ array.remove(indexB);
+ tester.verify(9, "Removed second b");
+ leafB.tick();
+ tester.verify(9, "Updated leafB - no change");
+ array.clear();
+ tester.verify(10, "Cleared array");
+ leafB.tick();
+ tester.verify(10, "Change to b not in array");
+
+ // Special methods
+ array.add(indexA, leafA);
+ array.add(indexB, leafB);
+ array.add(indexC, leafC);
+ tester.verify(13, "Added c");
+ leafC.tick();
+ tester.verify(14, "Ticked c");
+ array.set(array.indexOf(leafC), leafD);
+ tester.verify(15, "Replaced c with d");
+ leafC.tick();
+ leafD.tick();
+ tester.verify(16, "Ticked d and c (c not registered)");
+ array.add(leafC);
+ tester.verify(17, "Append c");
+ leafC.tick();
+ leafD.tick();
+ tester.verify(19, "Ticked d and c");
+
+ // Snapshot
+ {
+ final WatchedArrayList<Leaf> arraySnap = array.snapshot();
+ tester.verify(19, "Generate snapshot (no changes)");
+ // Verify that the snapshot is a proper copy of the source.
+ assertEquals(name + " snap same size",
+ array.size(), arraySnap.size());
+ for (int i = 0; i < array.size(); i++) {
+ for (int j = 0; j < arraySnap.size(); j++) {
+ assertTrue(name + " elements differ",
+ array.get(i) != arraySnap.get(j));
+ }
+ assertTrue(name + " element copy",
+ array.get(i).equals(arraySnap.get(i)));
+ }
+ leafD.tick();
+ tester.verify(20, "Tick after snapshot");
+ // Verify that the array snapshot is sealed
+ verifySealed(name, ()->arraySnap.add(indexA, leafB));
+ }
+ // Recreate the snapshot since the test corrupted it.
+ {
+ final WatchedArrayList<Leaf> arraySnap = array.snapshot();
+ // Verify that elements are also snapshots
+ final Leaf arraySnapElement = arraySnap.get(0);
+ verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+ }
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out";
+ ArrayList<Leaf> base = new ArrayList<>();
+ array.copyTo(base);
+ WatchedArrayList<Leaf> copy = new WatchedArrayList<>();
+ copy.copyFrom(base);
+ if (!array.equals(copy)) {
+ fail(msg);
+ }
+ }
}
@Test
public void testWatchedSparseArray() {
+ final String name = "WatchedSparseArray";
WatchableTester tester;
// Create a few leaves
@@ -269,7 +503,7 @@ public class WatcherTest {
WatchedSparseArray<Leaf> array = new WatchedSparseArray<>();
array.put(INDEX_A, leafA);
array.put(INDEX_B, leafB);
- tester = new WatchableTester(array, "WatchedSparseArray");
+ tester = new WatchableTester(array, name);
tester.verify(0, "Initial array - no registration");
leafA.tick();
tester.verify(0, "Updates with no registration");
@@ -338,20 +572,20 @@ public class WatcherTest {
final WatchedSparseArray<Leaf> arraySnap = array.snapshot();
tester.verify(22, "Generate snapshot (no changes)");
// Verify that the snapshot is a proper copy of the source.
- assertEquals("WatchedSparseArray snap same size",
+ assertEquals(name + " snap same size",
array.size(), arraySnap.size());
for (int i = 0; i < array.size(); i++) {
for (int j = 0; j < arraySnap.size(); j++) {
- assertTrue("WatchedSparseArray elements differ",
+ assertTrue(name + " elements differ",
array.valueAt(i) != arraySnap.valueAt(j));
}
- assertTrue("WatchedArrayMap element copy",
+ assertTrue(name + " element copy",
array.valueAt(i).equals(arraySnap.valueAt(i)));
}
leafD.tick();
tester.verify(23, "Tick after snapshot");
// Verify that the array snapshot is sealed
- verifySealed("WatchedSparseArray", ()->arraySnap.put(INDEX_A, leafB));
+ verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
}
// Recreate the snapshot since the test corrupted it.
{
@@ -360,15 +594,30 @@ public class WatcherTest {
final Leaf arraySnapElement = arraySnap.valueAt(0);
verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
}
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out";
+ SparseArray<Leaf> base = new SparseArray<>();
+ array.copyTo(base);
+ WatchedSparseArray<Leaf> copy = new WatchedSparseArray<>();
+ copy.copyFrom(base);
+ final int end = array.size();
+ assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+ for (int i = 0; i < end; i++) {
+ final int key = array.keyAt(i);
+ assertTrue(msg, array.get(i) == copy.get(i));
+ }
+ }
}
@Test
public void testWatchedSparseBooleanArray() {
+ final String name = "WatchedSparseBooleanArray";
WatchableTester tester;
// Test WatchedSparseBooleanArray
WatchedSparseBooleanArray array = new WatchedSparseBooleanArray();
- tester = new WatchableTester(array, "WatchedSparseBooleanArray");
+ tester = new WatchableTester(array, name);
tester.verify(0, "Initial array - no registration");
array.put(INDEX_A, true);
tester.verify(0, "Updates with no registration");
@@ -376,14 +625,10 @@ public class WatcherTest {
tester.verify(0, "Updates with no registration");
array.put(INDEX_B, true);
tester.verify(1, "Updates with registration");
- array.put(INDEX_B, true);
- tester.verify(1, "Null update");
array.put(INDEX_B, false);
array.put(INDEX_C, true);
tester.verify(3, "Updates with registration");
// Special methods
- array.put(INDEX_C, true);
- tester.verify(3, "Added true, no change");
array.setValueAt(array.indexOfKey(INDEX_C), false);
tester.verify(4, "Replaced true with false");
array.append(INDEX_D, true);
@@ -403,7 +648,77 @@ public class WatcherTest {
array.put(INDEX_D, false);
tester.verify(6, "Tick after snapshot");
// Verify that the array is sealed
- verifySealed("WatchedSparseBooleanArray", ()->arraySnap.put(INDEX_D, false));
+ verifySealed(name, ()->arraySnap.put(INDEX_D, false));
+ }
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out";
+ SparseBooleanArray base = new SparseBooleanArray();
+ array.copyTo(base);
+ WatchedSparseBooleanArray copy = new WatchedSparseBooleanArray();
+ copy.copyFrom(base);
+ final int end = array.size();
+ assertTrue(msg + " size mismatch/2 " + end + " " + copy.size(), end == copy.size());
+ for (int i = 0; i < end; i++) {
+ final int key = array.keyAt(i);
+ assertTrue(msg + " element", array.get(i) == copy.get(i));
+ }
+ }
+ }
+
+ @Test
+ public void testWatchedSparseIntArray() {
+ final String name = "WatchedSparseIntArray";
+ WatchableTester tester;
+
+ // Test WatchedSparseIntArray
+ WatchedSparseIntArray array = new WatchedSparseIntArray();
+ tester = new WatchableTester(array, name);
+ tester.verify(0, "Initial array - no registration");
+ array.put(INDEX_A, 1);
+ tester.verify(0, "Updates with no registration");
+ tester.register();
+ tester.verify(0, "Updates with no registration");
+ array.put(INDEX_B, 2);
+ tester.verify(1, "Updates with registration");
+ array.put(INDEX_B, 4);
+ array.put(INDEX_C, 5);
+ tester.verify(3, "Updates with registration");
+ // Special methods
+ array.setValueAt(array.indexOfKey(INDEX_C), 7);
+ tester.verify(4, "Replaced 6 with 7");
+ array.append(INDEX_D, 8);
+ tester.verify(5, "Append 8");
+
+ // Snapshot
+ {
+ WatchedSparseIntArray arraySnap = array.snapshot();
+ tester.verify(5, "Generate snapshot");
+ // Verify that the snapshot is a proper copy of the source.
+ assertEquals("WatchedSparseIntArray snap same size",
+ array.size(), arraySnap.size());
+ for (int i = 0; i < array.size(); i++) {
+ assertEquals(name + " element copy",
+ array.valueAt(i), arraySnap.valueAt(i));
+ }
+ array.put(INDEX_D, 9);
+ tester.verify(6, "Tick after snapshot");
+ // Verify that the array is sealed
+ verifySealed(name, ()->arraySnap.put(INDEX_D, 10));
+ }
+ // Verify copy-in/out
+ {
+ final String msg = name + " copy-in/out";
+ SparseIntArray base = new SparseIntArray();
+ array.copyTo(base);
+ WatchedSparseIntArray copy = new WatchedSparseIntArray();
+ copy.copyFrom(base);
+ final int end = array.size();
+ assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+ for (int i = 0; i < end; i++) {
+ final int key = array.keyAt(i);
+ assertTrue(msg, array.get(i) == copy.get(i));
+ }
}
}
}