diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 9067028668895..047aae772f44f 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -839,33 +839,45 @@ public abstract class IntentResolver { } }; + // Method to take the snapshot of an F. + protected F snapshot(F f) { + return f; + } + // Helper method to copy some of the maps. - private static void copyInto(ArrayMap l, ArrayMap r) { + protected void copyInto(ArrayMap l, ArrayMap r) { + final int end = r.size(); + l.clear(); + l.ensureCapacity(end); + for (int i = 0; i < end; i++) { + final F[] val = r.valueAt(i); + final String key = r.keyAt(i); + final F[] newval = Arrays.copyOf(val, val.length); + for (int j = 0; j < newval.length; j++) { + newval[j] = snapshot(newval[j]); + } + l.put(key, newval); + } + } + + protected void copyInto(ArraySet l, ArraySet r) { + l.clear(); final int end = r.size(); l.ensureCapacity(end); for (int i = 0; i < end; i++) { - final E[] val = r.valueAt(i); - final String key = r.keyAt(i); - l.put(key, Arrays.copyOf(val, val.length)); + l.append(snapshot(r.valueAt(i))); } } // Make a copy of . The presumption is that is empty but all // arrays are cleared out explicitly, just to be sure. protected void copyFrom(IntentResolver orig) { - mFilters.clear(); - mFilters.addAll(orig.mFilters); - mTypeToFilter.clear(); + copyInto(mFilters, orig.mFilters); copyInto(mTypeToFilter, orig.mTypeToFilter); - mBaseTypeToFilter.clear(); copyInto(mBaseTypeToFilter, orig.mBaseTypeToFilter); - mWildTypeToFilter.clear(); copyInto(mWildTypeToFilter, orig.mWildTypeToFilter); - mSchemeToFilter.clear(); copyInto(mSchemeToFilter, orig.mSchemeToFilter); - mActionToFilter.clear(); copyInto(mActionToFilter, orig.mActionToFilter); - mTypedActionToFilter.clear(); copyInto(mTypedActionToFilter, orig.mTypedActionToFilter); } diff --git a/services/core/java/com/android/server/WatchedIntentResolver.java b/services/core/java/com/android/server/WatchedIntentResolver.java index e514f3c6fd59e..0831c36d2bf6a 100644 --- a/services/core/java/com/android/server/WatchedIntentResolver.java +++ b/services/core/java/com/android/server/WatchedIntentResolver.java @@ -19,10 +19,13 @@ package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.server.pm.WatchedIntentFilter; +import com.android.server.utils.Snappable; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; import com.android.server.utils.Watcher; +import java.util.ArrayList; import java.util.List; /** @@ -31,9 +34,9 @@ import java.util.List; * @param The resolver type. * {@hide} */ -public abstract class WatchedIntentResolver +public abstract class WatchedIntentResolver extends IntentResolver - implements Watchable { + implements Watchable, Snappable { /** * Watchable machinery @@ -78,6 +81,13 @@ public abstract class WatchedIntentResolver mWatchable.dispatchChange(what); } + private final Watcher mWatcher = new Watcher() { + @Override + public void onChange(@Nullable Watchable what) { + dispatchChange(what); + } + }; + /** * Notify listeners that this object has changed. */ @@ -88,17 +98,20 @@ public abstract class WatchedIntentResolver @Override public void addFilter(F f) { super.addFilter(f); + f.registerObserver(mWatcher); onChanged(); } @Override public void removeFilter(F f) { + f.unregisterObserver(mWatcher); super.removeFilter(f); onChanged(); } @Override protected void removeFilterInternal(F f) { + f.unregisterObserver(mWatcher); super.removeFilterInternal(f); onChanged(); } @@ -109,4 +122,17 @@ public abstract class WatchedIntentResolver super.sortResults(results); onChanged(); } + + /** + * @see IntentResolver#findFilters(IntentFilter) + */ + public ArrayList findFilters(WatchedIntentFilter matching) { + return super.findFilters(matching.getIntentFilter()); + } + + // Make a copy of . The presumption is that is empty but all + // arrays are cleared out explicitly, just to be sure. + protected void copyFrom(WatchedIntentResolver orig) { + super.copyFrom(orig); + } } diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java index 7c19d90e4eded..f5910fa3143c7 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java @@ -24,6 +24,7 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.server.utils.SnapshotCache; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -35,7 +36,7 @@ import java.io.IOException; * If an {@link Intent} matches the {@link CrossProfileIntentFilter}, then activities in the user * {@link #mTargetUserId} can access it. */ -class CrossProfileIntentFilter extends IntentFilter { +class CrossProfileIntentFilter extends WatchedIntentFilter { private static final String ATTR_TARGET_USER_ID = "targetUserId"; private static final String ATTR_FLAGS = "flags"; private static final String ATTR_OWNER_PACKAGE = "ownerPackage"; @@ -48,12 +49,41 @@ class CrossProfileIntentFilter extends IntentFilter { final String mOwnerPackage; // packageName of the app. final int mFlags; + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public CrossProfileIntentFilter createSnapshot() { + CrossProfileIntentFilter s = new CrossProfileIntentFilter(mSource); + s.seal(); + return s; + }}; + } + CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId, int flags) { super(filter); mTargetUserId = targetUserId; mOwnerPackage = ownerPackage; mFlags = flags; + mSnapshot = makeCache(); + } + + CrossProfileIntentFilter(WatchedIntentFilter filter, String ownerPackage, int targetUserId, + int flags) { + this(filter.mFilter, ownerPackage, targetUserId, flags); + } + + // Copy constructor used only to create a snapshot. + private CrossProfileIntentFilter(CrossProfileIntentFilter f) { + super(f); + mTargetUserId = f.mTargetUserId; + mOwnerPackage = f.mOwnerPackage; + mFlags = f.mFlags; + mSnapshot = new SnapshotCache.Sealed(); } public int getTargetUserId() { @@ -72,6 +102,7 @@ class CrossProfileIntentFilter extends IntentFilter { mTargetUserId = parser.getAttributeInt(null, ATTR_TARGET_USER_ID, UserHandle.USER_NULL); mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, ""); mFlags = parser.getAttributeInt(null, ATTR_FLAGS, 0); + mSnapshot = makeCache(); int outerDepth = parser.getDepth(); String tagName = parser.getName(); @@ -94,7 +125,7 @@ class CrossProfileIntentFilter extends IntentFilter { } } if (tagName.equals(ATTR_FILTER)) { - readFromXml(parser); + mFilter.readFromXml(parser); } else { String msg = "Missing element under " + TAG + ": " + ATTR_FILTER + " at " + parser.getPositionDescription(); @@ -103,7 +134,8 @@ class CrossProfileIntentFilter extends IntentFilter { } } - String getStringFromXml(TypedXmlPullParser parser, String attribute, String defaultValue) { + private String getStringFromXml(TypedXmlPullParser parser, String attribute, + String defaultValue) { String value = parser.getAttributeValue(null, attribute); if (value == null) { String msg = "Missing element under " + TAG +": " + attribute + " at " + @@ -120,7 +152,7 @@ class CrossProfileIntentFilter extends IntentFilter { serializer.attributeInt(null, ATTR_FLAGS, mFlags); serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage); serializer.startTag(null, ATTR_FILTER); - super.writeToXml(serializer); + mFilter.writeToXml(serializer); serializer.endTag(null, ATTR_FILTER); } @@ -135,4 +167,8 @@ class CrossProfileIntentFilter extends IntentFilter { && mOwnerPackage.equals(other.mOwnerPackage) && mFlags == other.mFlags; } + + public CrossProfileIntentFilter snapshot() { + return mSnapshot.snapshot(); + } } diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java index 791a1057d112a..7927538603582 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java @@ -21,6 +21,7 @@ import android.content.IntentFilter; import com.android.server.WatchedIntentResolver; import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; import java.util.List; @@ -47,7 +48,34 @@ class CrossProfileIntentResolver @Override protected IntentFilter getIntentFilter(@NonNull CrossProfileIntentFilter input) { - return input; + return input.getIntentFilter(); + } + + CrossProfileIntentResolver() { + mSnapshot = makeCache(); + } + + // Take the snapshot of F + protected CrossProfileIntentFilter snapshot(CrossProfileIntentFilter f) { + return (f == null) ? null : f.snapshot(); + } + + // Copy constructor used only to create a snapshot. + private CrossProfileIntentResolver(CrossProfileIntentResolver f) { + copyFrom(f); + mSnapshot = new SnapshotCache.Sealed(); + } + + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public CrossProfileIntentResolver createSnapshot() { + return new CrossProfileIntentResolver(mSource); + }}; } /** @@ -56,8 +84,6 @@ class CrossProfileIntentResolver * @return A snapshot of the current object. */ public CrossProfileIntentResolver snapshot() { - CrossProfileIntentResolver result = new CrossProfileIntentResolver(); - result.copyFrom(this); - return result; + return mSnapshot.snapshot(); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9a3fe82f2d0fd..3d507b99a1b53 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3484,8 +3484,8 @@ public class PackageManagerService extends IPackageManager.Stub for (int i = resultTargetUser.size() - 1; i >= 0; i--) { if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) { - return createForwardingResolveInfoUnchecked(filter, sourceUserId, - targetUserId); + return createForwardingResolveInfoUnchecked(filter.getIntentFilter(), + sourceUserId, targetUserId); } } } @@ -22243,7 +22243,7 @@ public class PackageManagerService extends IPackageManager.Stub || (pa.mPref.mComponent.getPackageName().equals(packageName) && pa.mPref.mAlways)) { if (outFilters != null) { - outFilters.add(new IntentFilter(pa)); + outFilters.add(new IntentFilter(pa.getIntentFilter())); } if (outActivities != null) { outActivities.add(pa.mPref.mComponent); diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java index a015456f1485a..ad3950c906ef5 100644 --- a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java +++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java @@ -23,13 +23,14 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.server.utils.SnapshotCache; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -class PersistentPreferredActivity extends IntentFilter { +class PersistentPreferredActivity extends WatchedIntentFilter { private static final String ATTR_NAME = "name"; // component name private static final String ATTR_FILTER = "filter"; // filter private static final String ATTR_SET_BY_DPM = "set-by-dpm"; // set by DPM @@ -41,10 +42,38 @@ class PersistentPreferredActivity extends IntentFilter { final ComponentName mComponent; final boolean mIsSetByDpm; + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public PersistentPreferredActivity createSnapshot() { + PersistentPreferredActivity s = new PersistentPreferredActivity(mSource); + s.seal(); + return s; + }}; + } + PersistentPreferredActivity(IntentFilter filter, ComponentName activity, boolean isSetByDpm) { super(filter); mComponent = activity; mIsSetByDpm = isSetByDpm; + mSnapshot = makeCache(); + } + + PersistentPreferredActivity(WatchedIntentFilter filter, ComponentName activity, + boolean isSetByDpm) { + this(filter.mFilter, activity, isSetByDpm); + } + + // Copy constructor used only to create a snapshot + private PersistentPreferredActivity(PersistentPreferredActivity f) { + super(f); + mComponent = f.mComponent; + mIsSetByDpm = f.mIsSetByDpm; + mSnapshot = new SnapshotCache.Sealed(); } PersistentPreferredActivity(TypedXmlPullParser parser) @@ -79,27 +108,36 @@ class PersistentPreferredActivity extends IntentFilter { } } if (tagName.equals(ATTR_FILTER)) { - readFromXml(parser); + mFilter.readFromXml(parser); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Missing element filter at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); } + mSnapshot = makeCache(); } public void writeToXml(TypedXmlSerializer serializer) throws IOException { serializer.attribute(null, ATTR_NAME, mComponent.flattenToShortString()); serializer.attributeBoolean(null, ATTR_SET_BY_DPM, mIsSetByDpm); serializer.startTag(null, ATTR_FILTER); - super.writeToXml(serializer); + mFilter.writeToXml(serializer); serializer.endTag(null, ATTR_FILTER); } + public IntentFilter getIntentFilter() { + return mFilter; + } + @Override public String toString() { return "PersistentPreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this)) + " " + mComponent.flattenToShortString() + ", mIsSetByDpm=" + mIsSetByDpm + "}"; } + + public PersistentPreferredActivity snapshot() { + return mSnapshot.snapshot(); + } } diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java index 2b11a42978354..bfddaea0dd756 100644 --- a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java +++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java @@ -21,6 +21,7 @@ import android.content.IntentFilter; import com.android.server.WatchedIntentResolver; import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; public class PersistentPreferredIntentResolver extends WatchedIntentResolver @@ -32,7 +33,7 @@ public class PersistentPreferredIntentResolver @Override protected IntentFilter getIntentFilter(@NonNull PersistentPreferredActivity input) { - return input; + return input.getIntentFilter(); } @Override @@ -40,14 +41,40 @@ public class PersistentPreferredIntentResolver return packageName.equals(filter.mComponent.getPackageName()); } + public PersistentPreferredIntentResolver() { + super(); + mSnapshot = makeCache(); + } + + // Take the snapshot of F + protected PersistentPreferredActivity snapshot(PersistentPreferredActivity f) { + return (f == null) ? null : f.snapshot(); + } + + // Copy constructor used only to create a snapshot. + private PersistentPreferredIntentResolver(PersistentPreferredIntentResolver f) { + copyFrom(f); + mSnapshot = new SnapshotCache.Sealed(); + } + + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public PersistentPreferredIntentResolver createSnapshot() { + return new PersistentPreferredIntentResolver(mSource); + }}; + } + /** * Return a snapshot of the current object. The snapshot is a read-only copy suitable * for read-only methods. * @return A snapshot of the current object. */ public PersistentPreferredIntentResolver snapshot() { - PersistentPreferredIntentResolver result = new PersistentPreferredIntentResolver(); - result.copyFrom(this); - return result; + return mSnapshot.snapshot(); } } diff --git a/services/core/java/com/android/server/pm/PreferredActivity.java b/services/core/java/com/android/server/pm/PreferredActivity.java index 4e1dcb27111df..5bc915f2c1c4e 100644 --- a/services/core/java/com/android/server/pm/PreferredActivity.java +++ b/services/core/java/com/android/server/pm/PreferredActivity.java @@ -23,32 +23,62 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.server.utils.SnapshotCache; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; -class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { +class PreferredActivity extends WatchedIntentFilter implements PreferredComponent.Callbacks { private static final String TAG = "PreferredActivity"; private static final boolean DEBUG_FILTERS = false; final PreferredComponent mPref; + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public PreferredActivity createSnapshot() { + PreferredActivity s = new PreferredActivity(mSource); + s.seal(); + return s; + }}; + } + PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, boolean always) { super(filter); mPref = new PreferredComponent(this, match, set, activity, always); + mSnapshot = makeCache(); + } + + PreferredActivity(WatchedIntentFilter filter, int match, ComponentName[] set, + ComponentName activity, boolean always) { + this(filter.mFilter, match, set, activity, always); + } + + // Copy constructor used only to create a snapshot + private PreferredActivity(PreferredActivity f) { + super(f); + mPref = f.mPref; + mSnapshot = new SnapshotCache.Sealed(); } PreferredActivity(TypedXmlPullParser parser) throws XmlPullParserException, IOException { mPref = new PreferredComponent(this, parser); + mSnapshot = makeCache(); } public void writeToXml(TypedXmlSerializer serializer, boolean full) throws IOException { mPref.writeToXml(serializer, full); serializer.startTag(null, "filter"); - super.writeToXml(serializer); + mFilter.writeToXml(serializer); serializer.endTag(null, "filter"); } @@ -58,7 +88,7 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb if (DEBUG_FILTERS) { Log.i(TAG, "Starting to parse filter..."); } - readFromXml(parser); + mFilter.readFromXml(parser); if (DEBUG_FILTERS) { Log.i(TAG, "Finished filter: depth=" + parser.getDepth() + " tag=" + parser.getName()); @@ -71,9 +101,17 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb return true; } + public void dumpPref(PrintWriter out, String prefix, PreferredActivity filter) { + mPref.dump(out, prefix, filter); + } + @Override public String toString() { return "PreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this)) + " " + mPref.mComponent.flattenToShortString() + "}"; } + + public PreferredActivity snapshot() { + return mSnapshot.snapshot(); + } } diff --git a/services/core/java/com/android/server/pm/PreferredIntentResolver.java b/services/core/java/com/android/server/pm/PreferredIntentResolver.java index 10a6b3f69fde2..0aca6eec25fcc 100644 --- a/services/core/java/com/android/server/pm/PreferredIntentResolver.java +++ b/services/core/java/com/android/server/pm/PreferredIntentResolver.java @@ -21,6 +21,7 @@ import android.content.IntentFilter; import com.android.server.WatchedIntentResolver; import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; import java.io.PrintWriter; import java.util.ArrayList; @@ -46,7 +47,7 @@ public class PreferredIntentResolver @Override protected IntentFilter getIntentFilter(@NonNull PreferredActivity input) { - return input; + return input.getIntentFilter(); } public boolean shouldAddPreferredActivity(PreferredActivity pa) { @@ -69,14 +70,40 @@ public class PreferredIntentResolver return true; } + public PreferredIntentResolver() { + super(); + mSnapshot = makeCache(); + } + + // Take the snapshot of F + protected PreferredActivity snapshot(PreferredActivity f) { + return (f == null) ? null : f.snapshot(); + } + + // Copy constructor used only to create a snapshot. + private PreferredIntentResolver(PreferredIntentResolver f) { + copyFrom(f); + mSnapshot = new SnapshotCache.Sealed(); + } + + // The cache for snapshots, so they are not rebuilt if the base object has not + // changed. + final SnapshotCache mSnapshot; + + private SnapshotCache makeCache() { + return new SnapshotCache(this, this) { + @Override + public PreferredIntentResolver createSnapshot() { + return new PreferredIntentResolver(mSource); + }}; + } + /** * Return a snapshot of the current object. The snapshot is a read-only copy suitable * for read-only methods. * @return A snapshot of the current object. */ public PreferredIntentResolver snapshot() { - PreferredIntentResolver result = new PreferredIntentResolver(); - result.copyFrom(this); - return result; + return mSnapshot.snapshot(); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b6d4a5b88f8a8..d23bb7295fbf8 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3015,8 +3015,9 @@ public final class Settings implements Watchable, Snappable { = ps.pkg.getPreferredActivityFilters(); for (int i=0; i pair = intents.get(i); - applyDefaultPreferredActivityLPw(pmInternal, pair.second, new ComponentName( - ps.name, pair.first), userId); + applyDefaultPreferredActivityLPw(pmInternal, + new WatchedIntentFilter(pair.second), + new ComponentName(ps.name, pair.first), userId); } } } @@ -3101,8 +3102,8 @@ public final class Settings implements Watchable, Snappable { } } - private void applyDefaultPreferredActivityLPw( - PackageManagerInternal pmInternal, IntentFilter tmpPa, ComponentName cn, int userId) { + private void applyDefaultPreferredActivityLPw(PackageManagerInternal pmInternal, + WatchedIntentFilter tmpPa, ComponentName cn, int userId) { // The initial preferences only specify the target activity // component and intent-filter, not the set of matches. So we // now need to query for the matches to build the correct diff --git a/services/core/java/com/android/server/utils/SnapshotCache.java b/services/core/java/com/android/server/utils/SnapshotCache.java index f0fb8b25b402a..b4b8835ac0269 100644 --- a/services/core/java/com/android/server/utils/SnapshotCache.java +++ b/services/core/java/com/android/server/utils/SnapshotCache.java @@ -56,6 +56,15 @@ public abstract class SnapshotCache extends Watcher{ watchable.registerObserver(this); } + /** + * A private constructor that sets fields to null and mSealed to true. This supports + * the Sealed subclass. + */ + public SnapshotCache() { + mSource = null; + mSealed = true; + } + /** * Notify the object that the source object has changed. If the local object is sealed then * IllegalStateException is thrown. Otherwise, the cache is cleared. @@ -93,4 +102,25 @@ public abstract class SnapshotCache extends Watcher{ * @return A snapshot */ public abstract T createSnapshot(); + + /** + * A snapshot cache suitable for sealed snapshots. Attempting to retrieve the + * snapshot will throw an UnsupportedOperationException. + * @param the type of object being cached. This is needed for compilation only. It + * has no effect on execution. + */ + public static class Sealed extends SnapshotCache { + /** + * Create a sealed SnapshotCache that cannot be used to create new snapshots. + */ + public Sealed() { + } + /** + * Provide a concrete implementation of createSnapshot() that throws + * UnsupportedOperationException. + */ + public T createSnapshot() { + throw new UnsupportedOperationException("cannot snapshot a sealed snaphot"); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java index 153938cbbbf1a..b2c300255aef3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static org.junit.Assert.assertTrue; +import android.content.ComponentName; import android.content.IntentFilter; import androidx.test.filters.SmallTest; @@ -83,4 +84,80 @@ public class WatchedIntentHandlingTest { watcher.verifyNoChangeReported("pulled snapshot"); } + @Test + public void testPreferredActivity() { + // Create a bunch of nondescript component names + ComponentName component = new ComponentName("Package_A", "Class_A"); + ComponentName[] components = new ComponentName[10]; + for (int i = 0; i < components.length; i++) { + components[i] = new ComponentName("Package_" + i, "Class_" + i); + } + IntentFilter i = new IntentFilter("TEST_ACTION"); + PreferredActivity a = new PreferredActivity(i, 1, components, component, true); + final WatchableTester watcher = new WatchableTester(a, "PreferredIntentResolver"); + watcher.register(); + + // Verify that the initial IntentFilter and the PreferredActivity are truly + // independent. This is in addition to verifying that the PreferredActivity + // properly reports its changes. + i.setPriority(i.getPriority() + 1); + watcher.verifyNoChangeReported("indepenent intent"); + a.setPriority(a.getPriority() + 2); + watcher.verifyChangeReported("dependent intent"); + // Verify independence of i and a + assertTrue(i.getPriority() != a.getPriority()); + + // Verify that snapshots created from the PreferredActivity are stable when the + // source PreferredActivity changes. + a.setPriority(3); + watcher.verifyChangeReported("initialize intent priority"); + PreferredActivity s1 = a.snapshot(); + watcher.verifyNoChangeReported("pulled snapshot"); + // Verify snapshot cache. In the absence of changes to the PreferredActivity, the + // snapshot will not be rebuilt and will be the exact same object as before. + assertTrue(s1 == a.snapshot()); + // Force a change by incrementing the priority. The next snapshot must be + // different from the first snapshot. + a.setPriority(a.getPriority() + 1); + watcher.verifyChangeReported("increment priority"); + PreferredActivity s2 = a.snapshot(); + watcher.verifyNoChangeReported("pulled second snapshot"); + assertTrue(s1 != s2); + // Assert the two snapshots are different. s1 should have priority 3 and s2 + // should have priority 4. s2 should match the current value in a. + assertTrue(a.getPriority() == s2.getPriority()); + assertTrue(s1.getPriority() != s2.getPriority()); + } + + @Test + public void testPreferredIntentResolver() { + PreferredIntentResolver r = new PreferredIntentResolver(); + final WatchableTester watcher = new WatchableTester(r, "PreferredIntentResolver"); + watcher.register(); + // Create a bunch of nondescript component names + ComponentName component = new ComponentName("Package_A", "Class_A"); + ComponentName[] components = new ComponentName[10]; + for (int i = 0; i < components.length; i++) { + components[i] = new ComponentName("Package_" + i, "Class_" + i); + } + IntentFilter i = new IntentFilter("TEST_ACTION"); + PreferredActivity a1 = new PreferredActivity(i, 1, components, component, true); + + r.addFilter(a1); + watcher.verifyChangeReported("addFilter"); + i.setPriority(i.getPriority() + 1); + watcher.verifyNoChangeReported("indepenent intent"); + a1.setPriority(a1.getPriority() + 1); + watcher.verifyChangeReported("dependent intent"); + + PreferredActivity s1 = a1.snapshot(); + watcher.verifyNoChangeReported("pulled snapshot"); + // Verify snapshot cache. + assertTrue(s1 == a1.snapshot()); + a1.setPriority(a1.getPriority() + 1); + watcher.verifyChangeReported("increment priority"); + PreferredActivity s2 = a1.snapshot(); + watcher.verifyNoChangeReported("pulled second snapshot"); + assertTrue(s1.getPriority() != s2.getPriority()); + } } 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 f255c67cf47d3..9679e58c4e7d2 100644 --- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java @@ -916,5 +916,14 @@ public class WatcherTest { assertTrue(s1 != s2); assertTrue(leafA.get() == s1.get() + 1); assertTrue(leafA.get() == s2.get()); + + // Test sealed snapshots + SnapshotCache sealed = new SnapshotCache.Sealed(); + try { + Leaf x1 = sealed.snapshot(); + fail(name + " sealed snapshot did not throw"); + } catch (UnsupportedOperationException e) { + // This is the passing scenario - the exception is expected. + } } }