From 024340aa563c9388a1b022a6be052699d1bcfa3b Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Sun, 11 Apr 2021 10:09:49 -0700 Subject: [PATCH] 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"); + } + +}