WatchedIntentResolver now uses WatchedIntentFilter

Bug: 181964615

Convert WatchedIntentResolver to use WatchedIntentFilter instead of
IntentFilter.  WatchedIntentFilters are converted back to
IntentFilters when they are used in APIs.  The final conversion to
WatchedIntentFilter in PackageManagerService happens in a follow-up
commit.

Test: atest
 * FrameworksServicesTests:WatchedIntentHandlingTest
 * FrameworksServicesTests:AppsFilterTest
 * FrameworksServicesTests:PackageInstallerSessionTest
 * FrameworksServicesTests:PackageManagerServiceTest
 * FrameworksServicesTests:PackageManagerSettingsTests
 * FrameworksServicesTests:ScanTests
 * FrameworksServicesTests:UserSystemPackageInstallerTest
 * PackageManagerServiceBootTest
 * UserLifecycleTests#startUser
 * UserLifecycleTests#stopUser
 * UserLifecycleTests#switchUser
 * android.appsecurity.cts.EphemeralTest
 * android.appsecurity.cts.InstantAppUserTest
 * FrameworksServicesTests:WatcherTest
 * CtsContentTestCases:IntentFilterTest
 * CtsDynamicMimeHostTestCases
 * FrameworksServicesTests:WatcherTest

Change-Id: Iba80142b44268443221c22b5626fc53d60f2aa9f
This commit is contained in:
Lee Shombert
2021-04-30 06:21:54 -07:00
parent 4d2eee78b1
commit 5c302db303
13 changed files with 390 additions and 43 deletions

View File

@@ -839,33 +839,45 @@ public abstract class IntentResolver<F, R extends Object> {
}
};
// 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 <E> void copyInto(ArrayMap<String, E[]> l, ArrayMap<String, E[]> r) {
protected void copyInto(ArrayMap<String, F[]> l, ArrayMap<String, F[]> 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<F> l, ArraySet<F> 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 <this> a copy of <orig>. The presumption is that <this> 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);
}

View File

@@ -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 <R> The resolver type.
* {@hide}
*/
public abstract class WatchedIntentResolver<F, R extends Object>
public abstract class WatchedIntentResolver<F extends Watchable, R extends Object>
extends IntentResolver<F, R>
implements Watchable {
implements Watchable, Snappable {
/**
* Watchable machinery
@@ -78,6 +81,13 @@ public abstract class WatchedIntentResolver<F, R extends Object>
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<F, R extends Object>
@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<F, R extends Object>
super.sortResults(results);
onChanged();
}
/**
* @see IntentResolver#findFilters(IntentFilter)
*/
public ArrayList<F> findFilters(WatchedIntentFilter matching) {
return super.findFilters(matching.getIntentFilter());
}
// Make <this> a copy of <orig>. The presumption is that <this> is empty but all
// arrays are cleared out explicitly, just to be sure.
protected void copyFrom(WatchedIntentResolver orig) {
super.copyFrom(orig);
}
}

View File

@@ -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<CrossProfileIntentFilter> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<CrossProfileIntentFilter>(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();
}
}

View File

@@ -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<CrossProfileIntentResolver> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<CrossProfileIntentResolver>(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();
}
}

View File

@@ -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);

View File

@@ -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<PersistentPreferredActivity> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<PersistentPreferredActivity>(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();
}
}

View File

@@ -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<PersistentPreferredActivity, PersistentPreferredActivity>
@@ -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<PersistentPreferredIntentResolver> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<PersistentPreferredIntentResolver>(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();
}
}

View File

@@ -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<PreferredActivity> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<PreferredActivity>(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();
}
}

View File

@@ -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<PreferredIntentResolver> mSnapshot;
private SnapshotCache makeCache() {
return new SnapshotCache<PreferredIntentResolver>(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();
}
}

View File

@@ -3015,8 +3015,9 @@ public final class Settings implements Watchable, Snappable {
= ps.pkg.getPreferredActivityFilters();
for (int i=0; i<intents.size(); i++) {
Pair<String, ParsedIntentInfo> 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

View File

@@ -56,6 +56,15 @@ public abstract class SnapshotCache<T> 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<T> 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 <T> the type of object being cached. This is needed for compilation only. It
* has no effect on execution.
*/
public static class Sealed<T> extends SnapshotCache<T> {
/**
* 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");
}
}
}

View File

@@ -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());
}
}

View File

@@ -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<Leaf> 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.
}
}
}