From a5f59b2a9a7f1e55f3060fc4eeb1f24405581c0b Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Sun, 11 Apr 2021 10:09:45 -0700 Subject: [PATCH 1/2] Create a snapshot cache utility Bug: 181964615 The SnapshotCache class can be used with a Watchable; it allows a snapshot to be reused until the Watchable changes. Caching snapshots significantly reduces the time required to create a PackageManager snapshot. Set ENABLE to false to disable caching if a problem is found. Two existing cached snapshots use the new class. The change to Settings and InstantAppRegistry do not change functionality. Test: atest * FrameworksServicesTests:WatcherTest * FrameworksServicesTests:PackageManagerSettingsTests * android.appsecurity.cts.InstantAppUserTest Change-Id: I76d9c95205ef2a358b6493f02ff3f1f8855a2157 --- .../android/server/pm/InstantAppRegistry.java | 27 ++++-- .../java/com/android/server/pm/Settings.java | 39 ++++---- .../android/server/utils/SnapshotCache.java | 96 +++++++++++++++++++ .../com/android/server/utils/WatcherTest.java | 21 ++++ 4 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 services/core/java/com/android/server/utils/SnapshotCache.java diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index 7bf704299373e..bf323e7d4ff2e 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -57,6 +57,7 @@ import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; import com.android.server.utils.Watched; @@ -146,7 +147,7 @@ class InstantAppRegistry implements Watchable, Snappable { /** * The cached snapshot */ - private volatile InstantAppRegistry mSnapshot = null; + private final SnapshotCache mSnapshot; /** * Watchable machinery @@ -162,7 +163,6 @@ class InstantAppRegistry implements Watchable, Snappable { return mWatchable.isRegisteredObserver(observer); } public void dispatchChange(@Nullable Watchable what) { - mSnapshot = null; mWatchable.dispatchChange(what); } /** @@ -180,6 +180,16 @@ class InstantAppRegistry implements Watchable, Snappable { } }; + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public InstantAppRegistry createSnapshot() { + InstantAppRegistry s = new InstantAppRegistry(mSource); + s.mWatchable.seal(); + return s; + }}; + } + public InstantAppRegistry(PackageManagerService service, PermissionManagerServiceInternal permissionManager) { mService = service; @@ -194,6 +204,8 @@ class InstantAppRegistry implements Watchable, Snappable { mInstantGrants.registerObserver(mObserver); mInstalledInstantAppUids.registerObserver(mObserver); Watchable.verifyWatchedAttributes(this, mObserver); + + mSnapshot = makeCache(); } /** @@ -211,20 +223,15 @@ class InstantAppRegistry implements Watchable, Snappable { mInstalledInstantAppUids = new WatchedSparseArray( r.mInstalledInstantAppUids); - // Do not register any observers. This is a clone + // Do not register any observers. This is a snapshot. + mSnapshot = null; } /** * Return a snapshot: the value is the cached snapshot if available. */ public InstantAppRegistry snapshot() { - InstantAppRegistry s = mSnapshot; - if (s == null) { - s = new InstantAppRegistry(this); - s.mWatchable.seal(); - mSnapshot = s; - } - return s; + return mSnapshot.snapshot(); } @GuardedBy("mService.mLock") diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 4823c29b96f95..24f3930706233 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -117,6 +117,7 @@ import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationPersistence; import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; @@ -164,11 +165,6 @@ import java.util.UUID; public final class Settings implements Watchable, Snappable { private static final String TAG = "PackageSettings"; - /** - * Cached snapshot - */ - private volatile Settings mSnapshot = null; - /** * Watchable machinery */ @@ -212,7 +208,6 @@ public final class Settings implements Watchable, Snappable { * @param what The {@link Watchable} that generated the event. */ public void dispatchChange(@Nullable Watchable what) { - mSnapshot = null; mWatchable.dispatchChange(what); } /** @@ -523,6 +518,19 @@ public final class Settings implements Watchable, Snappable { } }; + private final SnapshotCache mSnapshot; + + // Create a snapshot cache + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public Settings createSnapshot() { + Settings s = new Settings(mSource); + s.mWatchable.seal(); + return s; + }}; + } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public Settings(Map pkgSettings) { mLock = new PackageManagerTracedLock(); @@ -557,6 +565,8 @@ public final class Settings implements Watchable, Snappable { mDefaultBrowserApp.registerObserver(mObserver); Watchable.verifyWatchedAttributes(this, mObserver); + + mSnapshot = makeCache(); } Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence, @@ -608,6 +618,8 @@ public final class Settings implements Watchable, Snappable { mDefaultBrowserApp.registerObserver(mObserver); Watchable.verifyWatchedAttributes(this, mObserver); + + mSnapshot = makeCache(); } /** @@ -661,22 +673,15 @@ public final class Settings implements Watchable, Snappable { mPermissions = r.mPermissions; mPermissionDataProvider = r.mPermissionDataProvider; - // Do not register any Watchables + // Do not register any Watchables and do not create a snapshot cache. + mSnapshot = null; } /** - * Return a snapshot. If the cached snapshot is null, build a new one. The logic in - * the function ensures that this function returns a valid snapshot even if a race - * condition causes the cached snapshot to be cleared asynchronously to this method. + * Return a snapshot. */ public Settings snapshot() { - Settings s = mSnapshot; - if (s == null) { - s = new Settings(this); - s.mWatchable.seal(); - mSnapshot = s; - } - return s; + return mSnapshot.snapshot(); } private void invalidatePackageCache() { diff --git a/services/core/java/com/android/server/utils/SnapshotCache.java b/services/core/java/com/android/server/utils/SnapshotCache.java new file mode 100644 index 0000000000000..f0fb8b25b402a --- /dev/null +++ b/services/core/java/com/android/server/utils/SnapshotCache.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * A class that caches snapshots. Instances are instantiated on a {@link Watchable}; when the + * {@link Watchable} reports a change, the cache is cleared. The snapshot() method fetches the + * cache if it is valid, or rebuilds the cache if it has been cleared. + * + * The class is abstract; clients must implement the createSnapshot() method. + * + * @param The type returned by the snapshot() method. + */ +public abstract class SnapshotCache extends Watcher{ + + /** + * Global snapshot cache enable flag. Set to false for testing or debugging. + */ + private static final boolean ENABLED = true; + + // The source object from which snapshots are created. This may be null if createSnapshot() + // does not require it. + protected final T mSource; + + // The cached snapshot + private T mSnapshot = null; + + // True if the snapshot is sealed and may not be modified. + private boolean mSealed = false; + + /** + * Create a cache with a source object for rebuilding snapshots and a + * {@link Watchable} that notifies when the cache is invalid. + * @param source Source data for rebuilding snapshots. + * @param watchable The object that notifies when the cache is invalid. + */ + public SnapshotCache(@Nullable T source, @NonNull Watchable watchable) { + mSource = source; + watchable.registerObserver(this); + } + + /** + * Notify the object that the source object has changed. If the local object is sealed then + * IllegalStateException is thrown. Otherwise, the cache is cleared. + */ + public void onChange(@Nullable Watchable what) { + if (mSealed) { + throw new IllegalStateException("attempt to change a sealed object"); + } + mSnapshot = null; + } + + /** + * Seal the cache. Attempts to modify the cache will generate an exception. + */ + public void seal() { + mSealed = true; + } + + /** + * Return a snapshot. This uses the cache if it is non-null. Otherwise it creates a + * new snapshot and saves it in the cache. + * @return A snapshot as returned by createSnapshot() and possibly cached. + */ + public T snapshot() { + T s = mSnapshot; + if (s == null || !ENABLED) { + s = createSnapshot(); + mSnapshot = s; + } + return s; + } + + /** + * Create a single, uncached snapshot. Clients must implement this per local rules. + * @return A snapshot + */ + public abstract T createSnapshot(); +} 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 57e95d7145930..f255c67cf47d3 100644 --- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java @@ -896,4 +896,25 @@ public class WatcherTest { leafD.tick(); tester.verify(3, "tick leafD"); } + + @Test + public void testSnapshotCache() { + final String name = "SnapshotCache"; + WatchableTester tester; + + Leaf leafA = new Leaf(); + SnapshotCache cache = new SnapshotCache<>(leafA, leafA) { + @Override + public Leaf createSnapshot() { + return mSource.snapshot(); + }}; + + Leaf s1 = cache.snapshot(); + assertTrue(s1 == cache.snapshot()); + leafA.tick(); + Leaf s2 = cache.snapshot(); + assertTrue(s1 != s2); + assertTrue(leafA.get() == s1.get() + 1); + assertTrue(leafA.get() == s2.get()); + } } From 024340aa563c9388a1b022a6be052699d1bcfa3b Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Sun, 11 Apr 2021 10:09:49 -0700 Subject: [PATCH 2/2] Create a WatchedIntentFilter Bug: 181964615 Create a WatchedIntentFilter class that wraps IntentFilter. This class is not used in this commit. Note that a WatchedIntentFilter always owns its encapsulated IntentFilter. Test: atest * FrameworksServicesTests:WatchedIntentHandlingTest Change-Id: I366d5f63177a56c74ccdac254a8bf592b4c3c0ad --- .../server/pm/WatchedIntentFilter.java | 715 ++++++++++++++++++ .../server/pm/WatchedIntentHandlingTest.java | 86 +++ 2 files changed, 801 insertions(+) create mode 100644 services/core/java/com/android/server/pm/WatchedIntentFilter.java create mode 100644 services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java diff --git a/services/core/java/com/android/server/pm/WatchedIntentFilter.java b/services/core/java/com/android/server/pm/WatchedIntentFilter.java new file mode 100644 index 0000000000000..30f276e8386a3 --- /dev/null +++ b/services/core/java/com/android/server/pm/WatchedIntentFilter.java @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.PatternMatcher; +import android.util.Printer; + +import com.android.server.utils.Snappable; +import com.android.server.utils.WatchableImpl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +/** + * A watched variant of {@link IntentFilter}. The class consists of a + * {@link IntentFilter} attribute and methods that are identical to those in IntentFilter, + * forwarding to the attribute. + * + * @hide + */ +public class WatchedIntentFilter + extends WatchableImpl + implements Snappable { + + // Watch for modifications made through an {@link Iterator}. + private class WatchedIterator implements Iterator { + private final Iterator mIterator; + WatchedIterator(@NonNull Iterator i) { + mIterator = i; + } + public boolean hasNext() { + return mIterator.hasNext(); + } + public E next() { + return mIterator.next(); + } + public void remove() { + mIterator.remove(); + WatchedIntentFilter.this.onChanged(); + } + public void forEachRemaining(Consumer action) { + mIterator.forEachRemaining(action); + WatchedIntentFilter.this.onChanged(); + } + } + + // A convenience function to wrap an iterator result, but only if it is not null. + private Iterator maybeWatch(Iterator i) { + return i == null ? i : new WatchedIterator<>(i); + } + + // The wrapped {@link IntentFilter} + protected IntentFilter mFilter; + + private void onChanged() { + dispatchChange(this); + } + + protected WatchedIntentFilter() { + mFilter = new IntentFilter(); + } + + // Convert an {@link IntentFilter} to a {@link WatchedIntentFilter} + protected WatchedIntentFilter(IntentFilter f) { + mFilter = new IntentFilter(f); + } + + // The copy constructor is used to create a snapshot of the object. + protected WatchedIntentFilter(WatchedIntentFilter f) { + this(f.getIntentFilter()); + } + + /** + * Create a WatchedIntentFilter based on an action + * @see IntentFilter#IntentFilter(String) + */ + public WatchedIntentFilter(String action) { + mFilter = new IntentFilter(action); + } + + /** + * Create a WatchedIntentFilter based on an action and a data type. + * @see IntentFilter#IntentFilter(String, String) + */ + public WatchedIntentFilter(String action, String dataType) + throws IntentFilter.MalformedMimeTypeException { + mFilter = new IntentFilter(action, dataType); + } + + /** + * Return a clone of the filter represented by this object. + */ + public WatchedIntentFilter cloneFilter() { + return new WatchedIntentFilter(mFilter); + } + + /** + * Return the {@link IntentFilter} represented by this object. + */ + public IntentFilter getIntentFilter() { + return mFilter; + } + + /** + * @see IntentFilter#setPriority(int) + */ + public final void setPriority(int priority) { + mFilter.setPriority(priority); + onChanged(); + } + + /** + * @see IntentFilter#getPriority() + */ + public final int getPriority() { + return mFilter.getPriority(); + } + + /** + * @see IntentFilter#setOrder(int) + */ + public final void setOrder(int order) { + mFilter.setOrder(order); + onChanged(); + } + + /** + * @see IntentFilter#getOrder() + */ + public final int getOrder() { + return mFilter.getOrder(); + } + + /** + * @see IntentFilter#getAutoVerify() + */ + public final boolean getAutoVerify() { + return mFilter.getAutoVerify(); + } + + /** + * @see IntentFilter#handleAllWebDataURI() + */ + public final boolean handleAllWebDataURI() { + return mFilter.handleAllWebDataURI(); + } + + /** + * @see IntentFilter#handlesWebUris(boolean) + */ + public final boolean handlesWebUris(boolean onlyWebSchemes) { + return mFilter.handlesWebUris(onlyWebSchemes); + } + + /** + * @see IntentFilter#needsVerification() + */ + public final boolean needsVerification() { + return mFilter.needsVerification(); + } + + /** + * @see IntentFilter#setVerified(boolean) + */ + public void setVerified(boolean verified) { + mFilter.setVerified(verified); + onChanged(); + } + + /** + * @see IntentFilter#setVisibilityToInstantApp(int) + */ + public void setVisibilityToInstantApp(int visibility) { + mFilter.setVisibilityToInstantApp(visibility); + onChanged(); + } + + /** + * @see IntentFilter#getVisibilityToInstantApp() + */ + public int getVisibilityToInstantApp() { + return mFilter.getVisibilityToInstantApp(); + } + + /** + * @see IntentFilter#isVisibleToInstantApp() + */ + public boolean isVisibleToInstantApp() { + return mFilter.isVisibleToInstantApp(); + } + + /** + * @see IntentFilter#isExplicitlyVisibleToInstantApp() + */ + public boolean isExplicitlyVisibleToInstantApp() { + return mFilter.isExplicitlyVisibleToInstantApp(); + } + + /** + * @see IntentFilter#isImplicitlyVisibleToInstantApp() + */ + public boolean isImplicitlyVisibleToInstantApp() { + return mFilter.isImplicitlyVisibleToInstantApp(); + } + + /** + * @see IntentFilter#addAction(String) + */ + public final void addAction(String action) { + mFilter.addAction(action); + onChanged(); + } + + /** + * @see IntentFilter#countActions() + */ + public final int countActions() { + return mFilter.countActions(); + } + + /** + * @see IntentFilter#getAction(int) + */ + public final String getAction(int index) { + return mFilter.getAction(index); + } + + /** + * @see IntentFilter#hasAction(String) + */ + public final boolean hasAction(String action) { + return mFilter.hasAction(action); + } + + /** + * @see IntentFilter#matchAction(String) + */ + public final boolean matchAction(String action) { + return mFilter.matchAction(action); + } + + /** + * @see IntentFilter#actionsIterator() + */ + public final Iterator actionsIterator() { + return maybeWatch(mFilter.actionsIterator()); + } + + /** + * @see IntentFilter#addDataType(String) + */ + public final void addDataType(String type) + throws IntentFilter.MalformedMimeTypeException { + mFilter.addDataType(type); + onChanged(); + } + + /** + * @see IntentFilter#addDynamicDataType(String) + */ + public final void addDynamicDataType(String type) + throws IntentFilter.MalformedMimeTypeException { + mFilter.addDynamicDataType(type); + onChanged(); + } + + /** + * @see IntentFilter#clearDynamicDataTypes() + */ + public final void clearDynamicDataTypes() { + mFilter.clearDynamicDataTypes(); + onChanged(); + } + + /** + * @see IntentFilter#countStaticDataTypes() + */ + public int countStaticDataTypes() { + return mFilter.countStaticDataTypes(); + } + + /** + * @see IntentFilter#hasDataType(String) + */ + public final boolean hasDataType(String type) { + return mFilter.hasDataType(type); + } + + /** + * @see IntentFilter#hasExactDynamicDataType(String) + */ + public final boolean hasExactDynamicDataType(String type) { + return mFilter.hasExactDynamicDataType(type); + } + + /** + * @see IntentFilter#hasExactStaticDataType(String) + */ + public final boolean hasExactStaticDataType(String type) { + return mFilter.hasExactStaticDataType(type); + } + + /** + * @see IntentFilter#countDataTypes() + */ + public final int countDataTypes() { + return mFilter.countDataTypes(); + } + + /** + * @see IntentFilter#getDataType(int) + */ + public final String getDataType(int index) { + return mFilter.getDataType(index); + } + + /** + * @see IntentFilter#typesIterator() + */ + public final Iterator typesIterator() { + return maybeWatch(mFilter.typesIterator()); + } + + /** + /** + * @see IntentFilter#dataTypes() + */ + public final List dataTypes() { + return mFilter.dataTypes(); + } + + /** + * @see IntentFilter#addMimeGroup(String) + */ + public final void addMimeGroup(String name) { + mFilter.addMimeGroup(name); + onChanged(); + } + + /** + * @see IntentFilter#hasMimeGroup(String) + */ + public final boolean hasMimeGroup(String name) { + return mFilter.hasMimeGroup(name); + } + + /** + * @see IntentFilter#getMimeGroup(int) + */ + public final String getMimeGroup(int index) { + return mFilter.getMimeGroup(index); + } + + /** + * @see IntentFilter#countMimeGroups() + */ + public final int countMimeGroups() { + return mFilter.countMimeGroups(); + } + + /** + * @see IntentAction@mimeGroupsIterator() + */ + public final Iterator mimeGroupsIterator() { + return maybeWatch(mFilter.mimeGroupsIterator()); + } + + /** + * @see IntentFilter#addDataScheme(String) + */ + public final void addDataScheme(String scheme) { + mFilter.addDataScheme(scheme); + onChanged(); + } + + /** + * @see IntentFilter#countDataSchemes() + */ + public final int countDataSchemes() { + return mFilter.countDataSchemes(); + } + + /** + * @see IntentFilter#getDataScheme(int) + */ + public final String getDataScheme(int index) { + return mFilter.getDataScheme(index); + } + + /** + * @see IntentFilter#hasDataScheme(String) + */ + public final boolean hasDataScheme(String scheme) { + return mFilter.hasDataScheme(scheme); + } + + /** + * @see IntentFilter#schemesIterator() + */ + public final Iterator schemesIterator() { + return maybeWatch(mFilter.schemesIterator()); + } + + /** + * @see IntentFilter#addDataSchemeSpecificPart(String, int) + */ + public final void addDataSchemeSpecificPart(String ssp, int type) { + mFilter.addDataSchemeSpecificPart(ssp, type); + onChanged(); + } + + /** + * @see IntentFilter#addDataSchemeSpecificPart(PatternMatcher) + */ + public final void addDataSchemeSpecificPart(PatternMatcher ssp) { + mFilter.addDataSchemeSpecificPart(ssp); + onChanged(); + } + + /** + * @see IntentFilter#countDataSchemeSpecificParts() + */ + public final int countDataSchemeSpecificParts() { + return mFilter.countDataSchemeSpecificParts(); + } + + /** + * @see IntentFilter#getDataSchemeSpecificPart(int) + */ + public final PatternMatcher getDataSchemeSpecificPart(int index) { + return mFilter.getDataSchemeSpecificPart(index); + } + + /** + * @see IntentFilter#hasDataSchemeSpecificPart(String) + */ + public final boolean hasDataSchemeSpecificPart(String data) { + return mFilter.hasDataSchemeSpecificPart(data); + } + + /** + * @see IntentFilter#schemeSpecificPartsIterator() + */ + public final Iterator schemeSpecificPartsIterator() { + return maybeWatch(mFilter.schemeSpecificPartsIterator()); + } + + /** + * @see IntentFilter#addDataAuthority(String, String) + */ + public final void addDataAuthority(String host, String port) { + mFilter.addDataAuthority(host, port); + onChanged(); + } + + /** + * @see IntentFilter#addDataAuthority(IntentFilter.AuthorityEntry) + */ + public final void addDataAuthority(IntentFilter.AuthorityEntry ent) { + mFilter.addDataAuthority(ent); + onChanged(); + } + + /** + * @see IntentFilter#countDataAuthorities() + */ + public final int countDataAuthorities() { + return mFilter.countDataAuthorities(); + } + + /** + * @see IntentFilter#getDataAuthority(int) + */ + public final IntentFilter.AuthorityEntry getDataAuthority(int index) { + return mFilter.getDataAuthority(index); + } + + /** + * @see IntentFilter#hasDataAuthority(Uri) + */ + public final boolean hasDataAuthority(Uri data) { + return mFilter.hasDataAuthority(data); + } + + /** + * @see IntentFilter#authoritiesIterator() + */ + public final Iterator authoritiesIterator() { + return maybeWatch(mFilter.authoritiesIterator()); + } + + /** + * @see IntentFilter#addDataPath(String, int) + */ + public final void addDataPath(String path, int type) { + mFilter.addDataPath(path, type); + onChanged(); + } + + /** + * @see IntentFilter#addDataPath(PatternMatcher) + */ + public final void addDataPath(PatternMatcher path) { + mFilter.addDataPath(path); + onChanged(); + } + + /** + * @see IntentFilter#countDataPaths() + */ + public final int countDataPaths() { + return mFilter.countDataPaths(); + } + + /** + * @see IntentFilter#getDataPath(int) + */ + public final PatternMatcher getDataPath(int index) { + return mFilter.getDataPath(index); + } + + /** + * @see IntentFilter#hasDataPath(String) + */ + public final boolean hasDataPath(String data) { + return mFilter.hasDataPath(data); + } + + /** + * @see IntentFilter#pathsIterator() + */ + public final Iterator pathsIterator() { + return maybeWatch(mFilter.pathsIterator()); + } + + /** + * @see IntentFilter#matchDataAuthority(Uri) + */ + public final int matchDataAuthority(Uri data) { + return mFilter.matchDataAuthority(data); + } + + /** + * @see IntentFilter#matchDataAuthority(Uri, boolean) + */ + public final int matchDataAuthority(Uri data, boolean wildcardSupported) { + return mFilter.matchDataAuthority(data, wildcardSupported); + } + + /** + * @see IntentFilter#matchData(String, String, Uri) + */ + public final int matchData(String type, String scheme, Uri data) { + return mFilter.matchData(type, scheme, data); + } + + /** + * @see IntentFilter#addCategory(String) + */ + public final void addCategory(String category) { + mFilter.addCategory(category); + } + + /** + * @see IntentFilter#countCategories() + */ + public final int countCategories() { + return mFilter.countCategories(); + } + + /** + * @see IntentFilter#getCategory(int) + */ + public final String getCategory(int index) { + return mFilter.getCategory(index); + } + + /** + * @see IntentFilter#hasCategory(String) + */ + public final boolean hasCategory(String category) { + return mFilter.hasCategory(category); + } + + /** + * @see IntentFilter#categoriesIterator() + */ + public final Iterator categoriesIterator() { + return maybeWatch(mFilter.categoriesIterator()); + } + + /** + * @see IntentFilter#matchCategories(Set) + */ + public final String matchCategories(Set categories) { + return mFilter.matchCategories(categories); + } + + /** + * @see IntentFilter#match(ContentResolver, Intent, boolean, String) + */ + public final int match(ContentResolver resolver, Intent intent, + boolean resolve, String logTag) { + return mFilter.match(resolver, intent, + resolve, logTag); + } + + /** + * @see IntentFilter#match(String, String, String, Uri, Set, String) + */ + public final int match(String action, String type, String scheme, + Uri data, Set categories, String logTag) { + return mFilter.match(action, type, scheme, + data, categories, logTag); + } + + /** + * @see IntentFilter#match(String, String, String, Uri, Set, String, boolean, + Collection ignoreActions) + */ + public final int match(String action, String type, String scheme, + Uri data, Set categories, String logTag, boolean supportWildcards, + Collection ignoreActions) { + return mFilter.match(action, type, scheme, + data, categories, logTag, supportWildcards, + ignoreActions); + } + + /** + * @see IntentFilter#dump(Printer, String) + */ + public void dump(Printer du, String prefix) { + mFilter.dump(du, prefix); + } + + /** + * @see IntentFilter#describeContents() + */ + public final int describeContents() { + return mFilter.describeContents(); + } + + /** + * @see IntentFilter#debugCheck() + */ + public boolean debugCheck() { + return mFilter.debugCheck(); + } + + /** + * @see IntentFilter#getHostsList() + */ + public ArrayList getHostsList() { + return mFilter.getHostsList(); + } + + /** + * @see IntentFilter#getHosts() + */ + public String[] getHosts() { + return mFilter.getHosts(); + } + + /** + * Convert a list of {@link IntentFilter} into a list of {@link WatchedIntentFilter} + */ + public static List toWatchedIntentFilterList(List inList) { + ArrayList outList = new ArrayList<>(); + for (int i = 0; i < inList.size(); i++) { + outList.add(new WatchedIntentFilter(inList.get(i))); + } + return outList; + } + + /** + * Convert a list of {@link IntentFilter} into a list of {@link WatchedIntentFilter} + */ + public static List toIntentFilterList(List inList) { + ArrayList outList = new ArrayList<>(); + for (int i = 0; i < inList.size(); i++) { + outList.add(inList.get(i).getIntentFilter()); + } + return outList; + } + + /** + * Create a snapshot by cloning the object. + */ + public WatchedIntentFilter snapshot() { + return new WatchedIntentFilter(this); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java new file mode 100644 index 0000000000000..153938cbbbf1a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static org.junit.Assert.assertTrue; + +import android.content.IntentFilter; + +import androidx.test.filters.SmallTest; + +import com.android.server.utils.WatchableTester; + +import org.junit.Test; + +import java.util.Iterator; + +@SmallTest +public class WatchedIntentHandlingTest { + + @Test + public void testWatchedIntentFilter() { + IntentFilter i = new IntentFilter("TEST_ACTION"); + WatchedIntentFilter f = new WatchedIntentFilter(i); + final WatchableTester watcher = + new WatchableTester(f, "WatchedIntentFilter"); + watcher.register(); + int wantPriority = 3; + f.setPriority(wantPriority); + watcher.verifyChangeReported("setPriority"); + f.getPriority(); + watcher.verifyNoChangeReported("getPriority"); + assertTrue(f.getPriority() == wantPriority); + f.setPriority(f.getPriority() + 1); + watcher.verifyChangeReported("setPriority"); + assertTrue(f.getPriority() == wantPriority + 1); + + i.setPriority(wantPriority + 3); + watcher.verifyNoChangeReported("indendent intent"); + assertTrue(f.getPriority() == wantPriority + 1); + + f.addAction("action-1"); + f.addAction("action-2"); + f.addAction("action-3"); + f.addAction("action-4"); + watcher.verifyChangeReported("addAction"); + int actionCount = f.countActions(); + + Iterator actions = f.actionsIterator(); + watcher.verifyNoChangeReported("actionsIterator 1"); + int count = 0; + while (actions.hasNext()) { + assertTrue(f.hasAction(actions.next())); + count++; + } + watcher.verifyNoChangeReported("actionsIterator 2"); + assertTrue(count == actionCount); + + actions = f.actionsIterator(); + watcher.verifyNoChangeReported("actionsIterator 1"); + while (actions.hasNext()) { + if (actions.next().equals("action-3")) { + actions.remove(); + watcher.verifyChangeReported("remove action"); + } + } + assertTrue(f.countActions() == actionCount - 1); + + WatchedIntentFilter s1 = f.snapshot(); + watcher.verifyNoChangeReported("pulled snapshot"); + } + +}