Revert "Revert "[AppsFilter] read-only interface for snapshots""

This reverts commit 2e06c6008e.

Reason for revert: fixing the boot time regression

Provides a read-only interface that is used by computer and snapshots.
It fixes the data conflicts when AppsFilter is changed while a snapshot is
taken.

Test: atest AppsFilterTest
Test: atest com.android.server.utils.WatcherTest
Test: m RUN_ERROR_PRONE=true framework services.core |& grep AppsFilter
BUG: 218411030
Change-Id: I06624ea79ff85160488a6c7863213b0c44fe2154
This commit is contained in:
Songchun Fan
2022-04-01 16:42:38 +00:00
parent aea49e77fd
commit 3def2802db
13 changed files with 685 additions and 196 deletions

View File

@@ -15,15 +15,34 @@
*/
package android.util;
import android.annotation.NonNull;
/**
* A sparse array of ArraySets, which is suitable to hold userid->packages association.
*
* @hide
*/
public class SparseSetArray<T> {
private final SparseArray<ArraySet<T>> mData = new SparseArray<>();
private final SparseArray<ArraySet<T>> mData;
public SparseSetArray() {
mData = new SparseArray<>();
}
/**
* Copy constructor
*/
public SparseSetArray(@NonNull SparseSetArray<T> src) {
final int arraySize = src.size();
mData = new SparseArray<>(arraySize);
for (int i = 0; i < arraySize; i++) {
final int key = src.keyAt(i);
final ArraySet<T> set = src.get(key);
final int setSize = set.size();
for (int j = 0; j < setSize; j++) {
add(key, set.valueAt(j));
}
}
}
/**

View File

@@ -44,7 +44,6 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -64,14 +63,18 @@ import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProvider;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Snapshots;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
import com.android.server.utils.Watched;
import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
import com.android.server.utils.WatchedSparseSetArray;
import com.android.server.utils.Watcher;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -84,7 +87,7 @@ import java.util.concurrent.Executor;
* manifests.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class AppsFilter implements Watchable, Snappable {
public class AppsFilterImpl implements AppsFilterSnapshot, Watchable, Snappable {
private static final String TAG = "AppsFilter";
@@ -99,32 +102,43 @@ public class AppsFilter implements Watchable, Snappable {
* application B is implicitly allowed to query for application A; regardless of any manifest
* entries.
*/
private final SparseSetArray<Integer> mImplicitlyQueryable = new SparseSetArray<>();
@Watched
private final WatchedSparseSetArray<Integer> mImplicitlyQueryable;
private final SnapshotCache<WatchedSparseSetArray<Integer>> mImplicitQueryableSnapshot;
/**
* This contains a list of app UIDs that are implicitly queryable because another app explicitly
* interacted with it, but could keep across package updates. For example, if application A
* grants persistable uri permission to application B; regardless of any manifest entries.
*/
private final SparseSetArray<Integer> mRetainedImplicitlyQueryable = new SparseSetArray<>();
@Watched
private final WatchedSparseSetArray<Integer> mRetainedImplicitlyQueryable;
private final SnapshotCache<WatchedSparseSetArray<Integer>>
mRetainedImplicitlyQueryableSnapshot;
/**
* A mapping from the set of App IDs that query other App IDs via package name to the
* list of packages that they can see.
*/
private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
@Watched
private final WatchedSparseSetArray<Integer> mQueriesViaPackage;
private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaPackageSnapshot;
/**
* A mapping from the set of App IDs that query others via component match to the list
* of packages that the they resolve to.
*/
private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
@Watched
private final WatchedSparseSetArray<Integer> mQueriesViaComponent;
private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueriesViaComponentSnapshot;
/**
* A mapping from the set of App IDs that query other App IDs via library name to the
* list of packages that they can see.
*/
private final SparseSetArray<Integer> mQueryableViaUsesLibrary = new SparseSetArray<>();
@Watched
private final WatchedSparseSetArray<Integer> mQueryableViaUsesLibrary;
private final SnapshotCache<WatchedSparseSetArray<Integer>> mQueryableViaUsesLibrarySnapshot;
/**
* Executor for running reasonably short background tasks such as building the initial
@@ -144,7 +158,9 @@ public class AppsFilter implements Watchable, Snappable {
* A set of App IDs that are always queryable by any package, regardless of their manifest
* content.
*/
private final ArraySet<Integer> mForceQueryable = new ArraySet<>();
@Watched
private final WatchedArraySet<Integer> mForceQueryable;
private final SnapshotCache<WatchedArraySet<Integer>> mForceQueryableSnapshot;
/**
* The set of package names provided by the device that should be force queryable regardless of
@@ -154,14 +170,15 @@ public class AppsFilter implements Watchable, Snappable {
/** True if all system apps should be made queryable by default. */
private final boolean mSystemAppsQueryable;
private final FeatureConfig mFeatureConfig;
private final OverlayReferenceMapper mOverlayReferenceMapper;
private final StateProvider mStateProvider;
private final PackageManagerInternal mPmInternal;
private SigningDetails mSystemSigningDetails;
private Set<String> mProtectedBroadcasts = new ArraySet<>();
@Watched
private final WatchedArrayList<String> mProtectedBroadcasts;
private final SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
private final Object mCacheLock = new Object();
@@ -175,19 +192,21 @@ public class AppsFilter implements Watchable, Snappable {
@GuardedBy("mCacheLock")
@NonNull
private final WatchedSparseBooleanMatrix mShouldFilterCache;
private final SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
private volatile boolean mSystemReady = false;
/**
* A cached snapshot.
*/
private final SnapshotCache<AppsFilter> mSnapshot;
private final SnapshotCache<AppsFilterImpl> mSnapshot;
private SnapshotCache<AppsFilter> makeCache() {
return new SnapshotCache<AppsFilter>(this, this) {
private SnapshotCache<AppsFilterImpl> makeCache() {
return new SnapshotCache<AppsFilterImpl>(this, this) {
@Override
public AppsFilter createSnapshot() {
AppsFilter s = new AppsFilter(mSource);
public AppsFilterImpl createSnapshot() {
AppsFilterImpl s = new AppsFilterImpl(mSource);
s.mWatchable.seal();
return s;
}
};
@@ -197,6 +216,15 @@ public class AppsFilter implements Watchable, Snappable {
* Watchable machinery
*/
private final WatchableImpl mWatchable = new WatchableImpl();
/**
* The observer that watches for changes from array members
*/
private final Watcher mObserver = new Watcher() {
@Override
public void onChange(@Nullable Watchable what) {
AppsFilterImpl.this.dispatchChange(what);
}
};
/**
* Ensures an observer is in the list, exactly once. The observer cannot be null. The
@@ -251,7 +279,7 @@ public class AppsFilter implements Watchable, Snappable {
}
@VisibleForTesting(visibility = PRIVATE)
AppsFilter(StateProvider stateProvider,
AppsFilterImpl(StateProvider stateProvider,
FeatureConfig featureConfig,
String[] forceQueryableList,
boolean systemAppsQueryable,
@@ -267,31 +295,67 @@ public class AppsFilter implements Watchable, Snappable {
mPmInternal = pmInternal;
mBackgroundExecutor = backgroundExecutor;
mShouldFilterCache = new WatchedSparseBooleanMatrix();
mShouldFilterCacheSnapshot = new SnapshotCache.Auto<>(
mShouldFilterCache, mShouldFilterCache, "AppsFilter.mShouldFilterCache");
mImplicitlyQueryable = new WatchedSparseSetArray<>();
mImplicitQueryableSnapshot = new SnapshotCache.Auto<>(
mImplicitlyQueryable, mImplicitlyQueryable, "AppsFilter.mImplicitlyQueryable");
mRetainedImplicitlyQueryable = new WatchedSparseSetArray<>();
mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Auto<>(
mRetainedImplicitlyQueryable, mRetainedImplicitlyQueryable,
"AppsFilter.mRetainedImplicitlyQueryable");
mQueriesViaPackage = new WatchedSparseSetArray<>();
mQueriesViaPackageSnapshot = new SnapshotCache.Auto<>(
mQueriesViaPackage, mQueriesViaPackage, "AppsFilter.mQueriesViaPackage");
mQueriesViaComponent = new WatchedSparseSetArray<>();
mQueriesViaComponentSnapshot = new SnapshotCache.Auto<>(
mQueriesViaComponent, mQueriesViaComponent, "AppsFilter.mQueriesViaComponent");
mQueryableViaUsesLibrary = new WatchedSparseSetArray<>();
mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Auto<>(
mQueryableViaUsesLibrary, mQueryableViaUsesLibrary,
"AppsFilter.mQueryableViaUsesLibrary");
mForceQueryable = new WatchedArraySet<>();
mForceQueryableSnapshot = new SnapshotCache.Auto<>(
mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
mProtectedBroadcasts = new WatchedArrayList<>();
mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
registerObservers();
Watchable.verifyWatchedAttributes(this, mObserver);
mSnapshot = makeCache();
}
/**
* The copy constructor is used by PackageManagerService to construct a snapshot.
* Attributes are not deep-copied since these are supposed to be immutable.
* TODO: deep-copy the attributes, if necessary.
*/
private AppsFilter(AppsFilter orig) {
Snapshots.copy(mImplicitlyQueryable, orig.mImplicitlyQueryable);
Snapshots.copy(mRetainedImplicitlyQueryable, orig.mRetainedImplicitlyQueryable);
Snapshots.copy(mQueriesViaPackage, orig.mQueriesViaPackage);
Snapshots.copy(mQueriesViaComponent, orig.mQueriesViaComponent);
Snapshots.copy(mQueryableViaUsesLibrary, orig.mQueryableViaUsesLibrary);
private AppsFilterImpl(AppsFilterImpl orig) {
mImplicitlyQueryable = orig.mImplicitQueryableSnapshot.snapshot();
mImplicitQueryableSnapshot = new SnapshotCache.Sealed<>();
mRetainedImplicitlyQueryable = orig.mRetainedImplicitlyQueryableSnapshot.snapshot();
mRetainedImplicitlyQueryableSnapshot = new SnapshotCache.Sealed<>();
mQueriesViaPackage = orig.mQueriesViaPackageSnapshot.snapshot();
mQueriesViaPackageSnapshot = new SnapshotCache.Sealed<>();
mQueriesViaComponent = orig.mQueriesViaComponentSnapshot.snapshot();
mQueriesViaComponentSnapshot = new SnapshotCache.Sealed<>();
mQueryableViaUsesLibrary = orig.mQueryableViaUsesLibrarySnapshot.snapshot();
mQueryableViaUsesLibrarySnapshot = new SnapshotCache.Sealed<>();
mForceQueryable = orig.mForceQueryableSnapshot.snapshot();
mForceQueryableSnapshot = new SnapshotCache.Sealed<>();
mProtectedBroadcasts = orig.mProtectedBroadcastsSnapshot.snapshot();
mProtectedBroadcastsSnapshot = new SnapshotCache.Sealed<>();
mQueriesViaComponentRequireRecompute = orig.mQueriesViaComponentRequireRecompute;
mForceQueryable.addAll(orig.mForceQueryable);
mForceQueryableByDevicePackageNames = orig.mForceQueryableByDevicePackageNames;
mForceQueryableByDevicePackageNames =
Arrays.copyOf(orig.mForceQueryableByDevicePackageNames,
orig.mForceQueryableByDevicePackageNames.length);
mSystemAppsQueryable = orig.mSystemAppsQueryable;
mFeatureConfig = orig.mFeatureConfig;
mOverlayReferenceMapper = orig.mOverlayReferenceMapper;
mStateProvider = orig.mStateProvider;
mSystemSigningDetails = orig.mSystemSigningDetails;
mProtectedBroadcasts = orig.mProtectedBroadcasts;
synchronized (orig.mCacheLock) {
mShouldFilterCache = orig.mShouldFilterCache.snapshot();
mShouldFilterCache = orig.mShouldFilterCacheSnapshot.snapshot();
mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
}
mBackgroundExecutor = null;
@@ -300,12 +364,24 @@ public class AppsFilter implements Watchable, Snappable {
mSystemReady = true;
}
@SuppressWarnings("GuardedBy")
private void registerObservers() {
mImplicitlyQueryable.registerObserver(mObserver);
mRetainedImplicitlyQueryable.registerObserver(mObserver);
mQueriesViaPackage.registerObserver(mObserver);
mQueriesViaComponent.registerObserver(mObserver);
mQueryableViaUsesLibrary.registerObserver(mObserver);
mForceQueryable.registerObserver(mObserver);
mProtectedBroadcasts.registerObserver(mObserver);
mShouldFilterCache.registerObserver(mObserver);
}
/**
* 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.
*/
public AppsFilter snapshot() {
public AppsFilterSnapshot snapshot() {
return mSnapshot.snapshot();
}
@@ -366,7 +442,7 @@ public class AppsFilter implements Watchable, Snappable {
@Nullable
private SparseBooleanArray mLoggingEnabled = null;
private AppsFilter mAppsFilter;
private AppsFilterImpl mAppsFilter;
private FeatureConfigImpl(
PackageManagerInternal pmInternal, PackageManagerServiceInjector injector) {
@@ -374,7 +450,7 @@ public class AppsFilter implements Watchable, Snappable {
mInjector = injector;
}
public void setAppsFilter(AppsFilter filter) {
public void setAppsFilter(AppsFilterImpl filter) {
mAppsFilter = filter;
}
@@ -478,8 +554,9 @@ public class AppsFilter implements Watchable, Snappable {
@Override
public void updatePackageState(PackageStateInternal setting, boolean removed) {
final boolean enableLogging = setting.getPkg() != null &&
!removed && (setting.getPkg().isTestOnly() || setting.getPkg().isDebuggable());
final boolean enableLogging = setting.getPkg() != null
&& !removed && (setting.getPkg().isTestOnly()
|| setting.getPkg().isDebuggable());
enableLogging(setting.getAppId(), enableLogging);
if (removed) {
mDisabledPackages.remove(setting.getPackageName());
@@ -490,7 +567,7 @@ public class AppsFilter implements Watchable, Snappable {
}
/** Builder method for an AppsFilter */
public static AppsFilter create(@NonNull PackageManagerServiceInjector injector,
public static AppsFilterImpl create(@NonNull PackageManagerServiceInjector injector,
@NonNull PackageManagerInternal pmInt) {
final boolean forceSystemAppsQueryable =
injector.getContext().getResources()
@@ -514,7 +591,7 @@ public class AppsFilter implements Watchable, Snappable {
injector.getUserManagerInternal().getUserInfos());
}
};
AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
AppsFilterImpl appsFilter = new AppsFilterImpl(stateProvider, featureConfig,
forcedQueryablePackageNames, forceSystemAppsQueryable, null,
injector.getBackgroundExecutor(), pmInt);
featureConfig.setAppsFilter(appsFilter);
@@ -527,7 +604,7 @@ public class AppsFilter implements Watchable, Snappable {
/** Returns true if the querying package may query for the potential target package */
private static boolean canQueryViaComponents(AndroidPackage querying,
AndroidPackage potentialTarget, Set<String> protectedBroadcasts) {
AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
if (!querying.getQueriesIntents().isEmpty()) {
for (Intent intent : querying.getQueriesIntents()) {
if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -599,7 +676,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
Set<String> protectedBroadcasts) {
WatchedArrayList<String> protectedBroadcasts) {
if (matchesAnyComponents(
intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
return true;
@@ -620,7 +697,7 @@ public class AppsFilter implements Watchable, Snappable {
private static boolean matchesAnyComponents(Intent intent,
List<? extends ParsedMainComponent> components,
Set<String> protectedBroadcasts) {
WatchedArrayList<String> protectedBroadcasts) {
for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
ParsedMainComponent component = components.get(i);
if (!component.isExported()) {
@@ -634,7 +711,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
Set<String> protectedBroadcasts) {
WatchedArrayList<String> protectedBroadcasts) {
List<ParsedIntentInfo> intents = component.getIntents();
for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -646,10 +723,10 @@ public class AppsFilter implements Watchable, Snappable {
}
private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
@Nullable Set<String> protectedBroadcasts) {
@Nullable WatchedArrayList<String> protectedBroadcasts) {
return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
intent.getData(), intent.getCategories(), "AppsFilter", true, protectedBroadcasts)
> 0;
intent.getData(), intent.getCategories(), "AppsFilter", true,
protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
}
/**
@@ -665,7 +742,8 @@ public class AppsFilter implements Watchable, Snappable {
if (recipientUid == visibleUid) {
return false;
}
final boolean changed = retainOnUpdate
final boolean changed;
changed = retainOnUpdate
? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
: mImplicitlyQueryable.add(recipientUid, visibleUid);
if (changed && DEBUG_LOGGING) {
@@ -787,7 +865,8 @@ public class AppsFilter implements Watchable, Snappable {
for (int i = existingSettings.size() - 1; i >= 0; i--) {
final PackageStateInternal existingSetting = existingSettings.valueAt(i);
if (existingSetting.getAppId() == newPkgSetting.getAppId() || existingSetting.getPkg()
if (existingSetting.getAppId() == newPkgSetting.getAppId()
|| existingSetting.getPkg()
== null) {
continue;
}
@@ -796,11 +875,13 @@ public class AppsFilter implements Watchable, Snappable {
if (!newIsForceQueryable) {
if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg,
newPkg, mProtectedBroadcasts)) {
mQueriesViaComponent.add(existingSetting.getAppId(), newPkgSetting.getAppId());
mQueriesViaComponent.add(existingSetting.getAppId(),
newPkgSetting.getAppId());
}
if (canQueryViaPackage(existingPkg, newPkg)
|| canQueryAsInstaller(existingSetting, newPkg)) {
mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
mQueriesViaPackage.add(existingSetting.getAppId(),
newPkgSetting.getAppId());
}
if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
mQueryableViaUsesLibrary.add(existingSetting.getAppId(),
@@ -811,11 +892,13 @@ public class AppsFilter implements Watchable, Snappable {
if (!mForceQueryable.contains(existingSetting.getAppId())) {
if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg,
existingPkg, mProtectedBroadcasts)) {
mQueriesViaComponent.add(newPkgSetting.getAppId(), existingSetting.getAppId());
mQueriesViaComponent.add(newPkgSetting.getAppId(),
existingSetting.getAppId());
}
if (canQueryViaPackage(newPkg, existingPkg)
|| canQueryAsInstaller(newPkgSetting, existingPkg)) {
mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
mQueriesViaPackage.add(newPkgSetting.getAppId(),
existingSetting.getAppId());
}
if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
mQueryableViaUsesLibrary.add(newPkgSetting.getAppId(),
@@ -1048,10 +1131,10 @@ public class AppsFilter implements Watchable, Snappable {
&& pkgSetting.getSigningDetails().signaturesMatchExactly(sysSigningDetails);
}
private ArraySet<String> collectProtectedBroadcasts(
private void collectProtectedBroadcasts(
ArrayMap<String, ? extends PackageStateInternal> existingSettings,
@Nullable String excludePackage) {
ArraySet<String> ret = new ArraySet<>();
mProtectedBroadcasts.clear();
for (int i = existingSettings.size() - 1; i >= 0; i--) {
PackageStateInternal setting = existingSettings.valueAt(i);
if (setting.getPkg() == null || setting.getPkg().getPackageName().equals(
@@ -1060,10 +1143,9 @@ public class AppsFilter implements Watchable, Snappable {
}
final List<String> protectedBroadcasts = setting.getPkg().getProtectedBroadcasts();
if (!protectedBroadcasts.isEmpty()) {
ret.addAll(protectedBroadcasts);
mProtectedBroadcasts.addAll(protectedBroadcasts);
}
}
return ret;
}
/**
@@ -1097,19 +1179,9 @@ public class AppsFilter implements Watchable, Snappable {
}
/**
* Fetches all app Ids that a given setting is currently visible to, per provided user. This
* only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
* all applications.
*
* If the setting is visible to all UIDs, null is returned. If an app is not visible to any
* applications, the int array will be empty.
*
* @param users the set of users that should be evaluated for this calculation
* @param existingSettings the set of all package settings that currently exist on device
* @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
* provided setting or null if the app is visible to all and no allow list should be
* applied.
* See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageStateInternal, int[], ArrayMap)}
*/
@Override
@Nullable
public SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
@@ -1164,7 +1236,7 @@ public class AppsFilter implements Watchable, Snappable {
* Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
* {@code isReplace} equal to {@code false}.
*
* @see AppsFilter#addPackage(PackageStateInternal, boolean)
* @see AppsFilterImpl#addPackage(PackageStateInternal, boolean)
*/
public void addPackage(PackageStateInternal newPkgSetting) {
addPackage(newPkgSetting, false /* isReplace */);
@@ -1178,13 +1250,15 @@ public class AppsFilter implements Watchable, Snappable {
*/
public void removePackage(PackageStateInternal setting, boolean isReplace) {
mStateProvider.runWithState((settings, users) -> {
final ArraySet<String> additionalChangedPackages;
final int userCount = users.length;
for (int u = 0; u < userCount; u++) {
final int userId = users[u].id;
final int removingUid = UserHandle.getUid(userId, setting.getAppId());
mImplicitlyQueryable.remove(removingUid);
for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid);
mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
removingUid);
}
if (isReplace) {
@@ -1201,12 +1275,14 @@ public class AppsFilter implements Watchable, Snappable {
if (!mQueriesViaComponentRequireRecompute) {
mQueriesViaComponent.remove(setting.getAppId());
for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.getAppId());
mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
setting.getAppId());
}
}
mQueriesViaPackage.remove(setting.getAppId());
for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.getAppId());
mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i),
setting.getAppId());
}
mQueryableViaUsesLibrary.remove(setting.getAppId());
for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
@@ -1216,23 +1292,26 @@ public class AppsFilter implements Watchable, Snappable {
mForceQueryable.remove(setting.getAppId());
if (setting.getPkg() != null && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
if (setting.getPkg() != null
&& !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
final String removingPackageName = setting.getPkg().getPackageName();
final Set<String> protectedBroadcasts = mProtectedBroadcasts;
mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName);
final ArrayList<String> protectedBroadcasts = new ArrayList<>();
protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
collectProtectedBroadcasts(settings, removingPackageName);
if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
mQueriesViaComponentRequireRecompute = true;
}
}
ArraySet<String> additionalChangedPackages =
mOverlayReferenceMapper.removePkg(setting.getPackageName());
additionalChangedPackages = mOverlayReferenceMapper.removePkg(setting.getPackageName());
mFeatureConfig.updatePackageState(setting, true /*removed*/);
// After removing all traces of the package, if it's part of a shared user, re-add other
// shared user members to re-establish visibility between them and other packages.
// NOTE: this must come after all removals from data structures but before we update the
// After removing all traces of the package, if it's part of a shared user,
// re-add other
// shared user members to re-establish visibility between them and other
// packages.
// NOTE: this must come after all removals from data structures but before we
// update the
// cache
if (setting.hasSharedUser()) {
final ArraySet<PackageStateInternal> sharedUserPackages =
@@ -1285,15 +1364,11 @@ public class AppsFilter implements Watchable, Snappable {
}
/**
* Returns true if the calling package should not be able to see the target package, false if no
* filtering should be done.
*
* @param callingUid the uid of the caller attempting to access a package
* @param callingSetting the setting attempting to access a package or null if it could not be
* found
* @param targetPkgSetting the package being accessed
* @param userId the user in which this access is being attempted
* See
* {@link AppsFilterSnapshot#shouldFilterApplication(int, Object, PackageStateInternal,
* int)}
*/
@Override
public boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
PackageStateInternal targetPkgSetting, int userId) {
if (DEBUG_TRACING) {
@@ -1610,7 +1685,11 @@ public class AppsFilter implements Watchable, Snappable {
}
}
boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
/**
* See {@link AppsFilterSnapshot#canQueryPackage(AndroidPackage, String)}
*/
@Override
public boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget) {
int appId = UserHandle.getAppId(querying.getUid());
if (appId < Process.FIRST_APPLICATION_UID) {
return true;
@@ -1665,6 +1744,11 @@ public class AppsFilter implements Watchable, Snappable {
+ targetPkgSetting + " " + description);
}
/**
* See {@link AppsFilterSnapshot#dumpQueries(PrintWriter, Integer, DumpState, int[],
* QuadFunction)}
*/
@Override
public void dumpQueries(
PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState, int[] users,
QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid) {
@@ -1699,7 +1783,8 @@ public class AppsFilter implements Watchable, Snappable {
}
}
pw.println(" system apps queryable: " + mSystemAppsQueryable);
dumpPackageSet(pw, filteringAppId, mForceQueryable, "forceQueryable", " ", expandPackages);
dumpPackageSet(pw, filteringAppId, mForceQueryable.untrackedStorage(),
"forceQueryable", " ", expandPackages);
pw.println(" queries via package name:");
dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages);
pw.println(" queries via component:");
@@ -1715,11 +1800,12 @@ public class AppsFilter implements Watchable, Snappable {
mRetainedImplicitlyQueryable, " ", expandPackages);
}
pw.println(" queryable via uses-library:");
dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ", expandPackages);
dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ",
expandPackages);
}
private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId,
SparseSetArray<Integer> queriesMap, String spacing,
WatchedSparseSetArray<Integer> queriesMap, String spacing,
@Nullable ToString<Integer> toString) {
for (int i = 0; i < queriesMap.size(); i++) {
Integer callingId = queriesMap.keyAt(i);
@@ -1748,7 +1834,7 @@ public class AppsFilter implements Watchable, Snappable {
}
private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
Set<T> targetPkgSet, String subTitle, String spacing,
ArraySet<T> targetPkgSet, String subTitle, String spacing,
@Nullable ToString<T> toString) {
if (targetPkgSet != null && targetPkgSet.size() > 0
&& (filteringId == null || targetPkgSet.contains(filteringId))) {

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 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.annotation.Nullable;
import android.os.Process;
import android.util.ArrayMap;
import android.util.SparseArray;
import com.android.internal.util.function.QuadFunction;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import java.io.PrintWriter;
/**
* Read-only interface used by computer and snapshots to query the visibility of packages
*/
public interface AppsFilterSnapshot {
/**
* Fetches all app Ids that a given setting is currently visible to, per provided user. This
* only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see
* all applications.
*
* If the setting is visible to all UIDs, null is returned. If an app is not visible to any
* applications, the int array will be empty.
*
* @param users the set of users that should be evaluated for this calculation
* @param existingSettings the set of all package settings that currently exist on device
* @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
* provided setting or null if the app is visible to all and no allow list should be
* applied.
*/
SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
ArrayMap<String, ? extends PackageStateInternal> existingSettings);
/**
* Returns true if the calling package should not be able to see the target package, false if no
* filtering should be done.
*
* @param callingUid the uid of the caller attempting to access a package
* @param callingSetting the setting attempting to access a package or null if it could not be
* found
* @param targetPkgSetting the package being accessed
* @param userId the user in which this access is being attempted
*/
boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
PackageStateInternal targetPkgSetting, int userId);
/**
* Returns whether the querying package is allowed to see the target package.
*
* @param querying the querying package
* @param potentialTarget the package name of the target package
*/
boolean canQueryPackage(@NonNull AndroidPackage querying, String potentialTarget);
/**
* Dump the packages that are queryable by the querying package.
*
* @param pw the output print writer
* @param filteringAppId the querying package's app ID
* @param dumpState the state of the dumping
* @param users the users for which the packages are installed
* @param getPackagesForUid the function that produces the package names for given uids
*/
void dumpQueries(PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState,
int[] users,
QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid);
}

View File

@@ -385,7 +385,7 @@ public class ComputerEngine implements Computer {
private final ResolveInfo mInstantAppInstallerInfo;
private final InstantAppRegistry mInstantAppRegistry;
private final ApplicationInfo mLocalAndroidApplication;
private final AppsFilter mAppsFilter;
private final AppsFilterSnapshot mAppsFilter;
private final WatchedArrayMap<String, Integer> mFrozenPackages;
// Immutable service attribute

View File

@@ -720,7 +720,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Watched
final AppsFilter mAppsFilter;
final AppsFilterImpl mAppsFilter;
final PackageParser2.Callback mPackageParserCallback;
@@ -979,7 +979,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
public final InstantAppRegistry instantAppRegistry;
public final ApplicationInfo androidApplication;
public final String appPredictionServicePackage;
public final AppsFilter appsFilter;
public final AppsFilterSnapshot appsFilter;
public final ComponentResolverApi componentResolver;
public final PackageManagerService service;
public final WatchedArrayMap<String, Integer> frozenPackages;
@@ -1431,7 +1431,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
RuntimePermissionsPersistence.createInstance(),
i.getPermissionManagerServiceInternal(),
domainVerificationService, lock),
(i, pm) -> AppsFilter.create(i, i.getLocalService(PackageManagerInternal.class)),
(i, pm) -> AppsFilterImpl.create(i,
i.getLocalService(PackageManagerInternal.class)),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
(i, pm) -> SystemConfig.getInstance(),
(i, pm) -> new PackageDexOptimizer(i.getInstaller(), i.getInstallLock(),

View File

@@ -99,7 +99,7 @@ public class PackageManagerServiceInjector {
private final Singleton<UserManagerService>
mUserManagerProducer;
private final Singleton<Settings> mSettingsProducer;
private final Singleton<AppsFilter> mAppsFilterProducer;
private final Singleton<AppsFilterImpl> mAppsFilterProducer;
private final Singleton<PlatformCompat>
mPlatformCompatProducer;
private final Singleton<SystemConfig> mSystemConfigProducer;
@@ -148,7 +148,7 @@ public class PackageManagerServiceInjector {
Producer<PermissionManagerServiceInternal> permissionManagerServiceProducer,
Producer<UserManagerService> userManagerProducer,
Producer<Settings> settingsProducer,
Producer<AppsFilter> appsFilterProducer,
Producer<AppsFilterImpl> appsFilterProducer,
Producer<PlatformCompat> platformCompatProducer,
Producer<SystemConfig> systemConfigProducer,
Producer<PackageDexOptimizer> packageDexOptimizerProducer,
@@ -282,7 +282,7 @@ public class PackageManagerServiceInjector {
return mSettingsProducer.get(this, mPackageManager);
}
public AppsFilter getAppsFilter() {
public AppsFilterImpl getAppsFilter() {
return mAppsFilterProducer.get(this, mPackageManager);
}

View File

@@ -272,6 +272,13 @@ public class WatchedArrayList<E> extends WatchableImpl
return mStorage.contains(o);
}
/**
* Return true if all the objects in the given collection are in this array list.
*/
public boolean containsAll(Collection<?> c) {
return mStorage.containsAll(c);
}
/**
* Ensure capacity.
*/

View File

@@ -18,6 +18,7 @@ package com.android.server.utils;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -168,12 +169,19 @@ public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappab
* A copy constructor that can be used for snapshotting.
*/
private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) {
mOrder = r.mOrder;
mSize = r.mSize;
mKeys = r.mKeys.clone();
mMap = r.mMap.clone();
mInUse = r.mInUse.clone();
mValues = r.mValues.clone();
copyFrom(r);
}
/**
* Copy from src to this.
*/
public void copyFrom(@NonNull WatchedSparseBooleanMatrix src) {
mOrder = src.mOrder;
mSize = src.mSize;
mKeys = src.mKeys.clone();
mMap = src.mMap.clone();
mInUse = src.mInUse.clone();
mValues = src.mValues.clone();
}
/**

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2022 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.util.ArraySet;
import android.util.SparseSetArray;
/**
* A watched variant of SparseSetArray. Changes to the array are notified to
* registered {@link Watcher}s.
* @param <T> The element type, stored in the SparseSetArray.
*/
public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable {
// The storage
private final SparseSetArray mStorage;
// A private convenience function
private void onChanged() {
dispatchChange(this);
}
public WatchedSparseSetArray() {
mStorage = new SparseSetArray();
}
/**
* Creates a new WatchedSparseSetArray from an existing WatchedSparseSetArray and copy its data
*/
public WatchedSparseSetArray(@NonNull WatchedSparseSetArray<T> watchedSparseSetArray) {
mStorage = new SparseSetArray(watchedSparseSetArray.untrackedStorage());
}
/**
* Return the underlying storage. This breaks the wrapper but is necessary when
* passing the array to distant methods.
*/
public SparseSetArray<T> untrackedStorage() {
return mStorage;
}
/**
* Add a value for key n.
* @return FALSE when the value already existed for the given key, TRUE otherwise.
*/
public boolean add(int n, T value) {
final boolean res = mStorage.add(n, value);
onChanged();
return res;
}
/**
* Removes all mappings from this SparseSetArray.
*/
public void clear() {
mStorage.clear();
onChanged();
}
/**
* @return whether the value exists for the key n.
*/
public boolean contains(int n, T value) {
return mStorage.contains(n, value);
}
/**
* @return the set of items of key n
*/
public ArraySet<T> get(int n) {
return mStorage.get(n);
}
/**
* Remove a value for key n.
* @return TRUE when the value existed for the given key and removed, FALSE otherwise.
*/
public boolean remove(int n, T value) {
if (mStorage.remove(n, value)) {
onChanged();
return true;
}
return false;
}
/**
* Remove all values for key n.
*/
public void remove(int n) {
mStorage.remove(n);
onChanged();
}
/**
* Return the size of the SparseSetArray.
*/
public int size() {
return mStorage.size();
}
/**
* Return the key stored at the given index.
*/
public int keyAt(int index) {
return mStorage.keyAt(index);
}
/**
* Return the size of the array at the given index.
*/
public int sizeAt(int index) {
return mStorage.sizeAt(index);
}
/**
* Return the value in the SetArray at the given key index and value index.
*/
public T valueAt(int intIndex, int valueIndex) {
return (T) mStorage.valueAt(intIndex, valueIndex);
}
@NonNull
@Override
public Object snapshot() {
WatchedSparseSetArray l = new WatchedSparseSetArray(this);
l.seal();
return l;
}
/**
* Make <this> a snapshot of the argument. Note that <this> is immutable when the
* method returns. <this> must be empty when the function is called.
* @param r The source array, which is copied into <this>
*/
public void snapshot(@NonNull WatchedSparseSetArray<T> r) {
snapshot(this, r);
}
/**
* Make the destination a copy of the source. If the element is a subclass of Snapper then the
* copy contains snapshots of the elements. Otherwise the copy contains references to the
* elements. The destination must be initially empty. Upon return, the destination is
* immutable.
* @param dst The destination array. It must be empty.
* @param src The source array. It is not modified.
*/
public static void snapshot(@NonNull WatchedSparseSetArray dst,
@NonNull WatchedSparseSetArray src) {
if (dst.size() != 0) {
throw new IllegalArgumentException("snapshot destination is not empty");
}
final int arraySize = src.size();
for (int i = 0; i < arraySize; i++) {
final ArraySet set = src.get(i);
final int setSize = set.size();
for (int j = 0; j < setSize; j++) {
dst.add(src.keyAt(i), set.valueAt(j));
}
}
dst.seal();
}
}

View File

@@ -34,7 +34,6 @@ import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverr
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
import com.android.server.wm.ActivityTaskManagerInternal
import com.google.common.truth.Truth.assertThat
@@ -46,13 +45,9 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.intThat
import org.mockito.Mockito.never
import org.mockito.Mockito.same
import org.mockito.Mockito.verify
import org.testng.Assert.assertThrows
import java.io.File
import java.util.UUID
@@ -365,7 +360,7 @@ class PackageManagerComponentLabelIconOverrideTest {
val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
whenever(this.isCallerRecents(anyInt())) { false }
}
val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
any<PackageSetting>(), anyInt())) { false }
whenever(this.snapshot()) { this@mockThrowOnUnmocked }

View File

@@ -194,7 +194,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val packageParser: PackageParser2 = mock()
val keySetManagerService: KeySetManagerService = mock()
val packageAbiHelper: PackageAbiHelper = mock()
val appsFilter: AppsFilter = mock {
val appsFilter: AppsFilterImpl = mock {
whenever(snapshot()) { this@mock }
}
val dexManager: DexManager = mock()

View File

@@ -77,7 +77,7 @@ import java.util.concurrent.Executor;
@Presubmit
@RunWith(JUnit4.class)
public class AppsFilterTest {
public class AppsFilterImplTest {
private static final int DUMMY_CALLING_APPID = 10345;
private static final int DUMMY_TARGET_APPID = 10556;
@@ -98,9 +98,9 @@ public class AppsFilterTest {
}
@Mock
AppsFilter.FeatureConfig mFeatureConfigMock;
AppsFilterImpl.FeatureConfig mFeatureConfigMock;
@Mock
AppsFilter.StateProvider mStateProvider;
AppsFilterImpl.StateProvider mStateProvider;
@Mock
Executor mMockExecutor;
@Mock
@@ -204,11 +204,11 @@ public class AppsFilterTest {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST);
return new Object();
}).when(mStateProvider)
.runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
.runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
@@ -218,14 +218,14 @@ public class AppsFilterTest {
when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
(Answer<Boolean>) invocation ->
((AndroidPackage)invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
>= Build.VERSION_CODES.R);
}
@Test
public void testSystemReadyPropogates() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -236,8 +236,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_FilterMatches() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -259,8 +259,8 @@ public class AppsFilterTest {
}
@Test
public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -308,8 +308,8 @@ public class AppsFilterTest {
@Test
public void testQueriesProvider_FilterMatches() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -333,8 +333,8 @@ public class AppsFilterTest {
@Test
public void testOnUserUpdated_FilterMatches() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
@@ -356,11 +356,11 @@ public class AppsFilterTest {
// adds new user
doAnswer(invocation -> {
((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST_WITH_ADDED);
return new Object();
}).when(mStateProvider)
.runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
.runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
appsFilter.onUserCreated(ADDED_USER);
for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
@@ -373,11 +373,11 @@ public class AppsFilterTest {
// delete user
doAnswer(invocation -> {
((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
.currentState(mExisting, USER_INFO_LIST);
return new Object();
}).when(mStateProvider)
.runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
.runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
appsFilter.onUserDeleted(ADDED_USER);
for (int subjectUserId : USER_ARRAY) {
@@ -391,8 +391,8 @@ public class AppsFilterTest {
@Test
public void testQueriesDifferentProvider_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -416,8 +416,8 @@ public class AppsFilterTest {
@Test
public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -435,8 +435,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -452,8 +452,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -473,8 +473,8 @@ public class AppsFilterTest {
@Test
public void testNoQueries_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -490,7 +490,7 @@ public class AppsFilterTest {
@Test
public void testNoUsesLibrary_Filters() throws Exception {
final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -516,7 +516,7 @@ public class AppsFilterTest {
@Test
public void testUsesLibrary_DoesntFilter() throws Exception {
final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -543,7 +543,7 @@ public class AppsFilterTest {
@Test
public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -570,7 +570,7 @@ public class AppsFilterTest {
@Test
public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor, mMockPmInternal);
@@ -602,8 +602,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryable_SystemDoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -621,8 +621,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryable_NonSystemFilters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -638,9 +638,10 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
false, null, mMockExecutor, mMockPmInternal);
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{"com.some.package"}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -657,8 +658,8 @@ public class AppsFilterTest {
@Test
public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
appsFilter.onSystemReady();
@@ -686,9 +687,10 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
false, null, mMockExecutor, mMockPmInternal);
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
new String[]{"com.some.package"}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -704,8 +706,8 @@ public class AppsFilterTest {
@Test
public void testSystemQueryable_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{},
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{},
true /* system force queryable */, null, mMockExecutor,
mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
@@ -723,8 +725,8 @@ public class AppsFilterTest {
@Test
public void testQueriesPackage_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -742,8 +744,8 @@ public class AppsFilterTest {
public void testNoQueries_FeatureOff_DoesntFilter() throws Exception {
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
.thenReturn(false);
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -759,8 +761,8 @@ public class AppsFilterTest {
@Test
public void testSystemUid_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -775,8 +777,8 @@ public class AppsFilterTest {
@Test
public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -792,8 +794,8 @@ public class AppsFilterTest {
@Test
public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -807,8 +809,8 @@ public class AppsFilterTest {
@Test
public void testNoTargetPackage_filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -838,7 +840,7 @@ public class AppsFilterTest {
.setOverlayTargetOverlayableName("overlayableName");
ParsingPackage actor = pkg("com.some.package.actor");
final AppsFilter appsFilter = new AppsFilter(
final AppsFilterImpl appsFilter = new AppsFilterImpl(
mStateProvider,
mFeatureConfigMock,
new String[]{},
@@ -933,7 +935,7 @@ public class AppsFilterTest {
when(mMockPmInternal.getSharedUserPackages(any(Integer.class))).thenReturn(
actorSharedSettingPackages
);
final AppsFilter appsFilter = new AppsFilter(
final AppsFilterImpl appsFilter = new AppsFilterImpl(
mStateProvider,
mFeatureConfigMock,
new String[]{},
@@ -985,8 +987,8 @@ public class AppsFilterTest {
@Test
public void testInitiatingApp_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1003,8 +1005,8 @@ public class AppsFilterTest {
@Test
public void testUninstalledInitiatingApp_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1021,8 +1023,8 @@ public class AppsFilterTest {
@Test
public void testOriginatingApp_Filters() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1046,8 +1048,8 @@ public class AppsFilterTest {
@Test
public void testInstallingApp_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1071,8 +1073,8 @@ public class AppsFilterTest {
@Test
public void testInstrumentation_DoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1100,8 +1102,8 @@ public class AppsFilterTest {
@Test
public void testWhoCanSee() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1173,8 +1175,8 @@ public class AppsFilterTest {
@Test
public void testOnChangeReport() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -1246,8 +1248,8 @@ public class AppsFilterTest {
@Test
public void testOnChangeReportedFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
@@ -1270,6 +1272,53 @@ public class AppsFilterTest {
watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
public void testAppsFilterRead() throws Exception {
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor, mMockPmInternal);
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
DUMMY_TARGET_APPID);
PackageSetting instrumentation = simulateAddPackage(appsFilter,
pkgWithInstrumentation("com.some.other.package", "com.some.package"),
DUMMY_CALLING_APPID);
final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
PackageSetting queriesProvider = simulateAddPackage(appsFilter,
pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
queriesProviderAppId);
appsFilter.grantImplicitAccess(
hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
AppsFilterSnapshot snapshot = appsFilter.snapshot();
assertFalse(
snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
SYSTEM_USER));
assertFalse(
snapshot.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
SYSTEM_USER));
SparseArray<int[]> queriesProviderFilter =
snapshot.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
target.getPackageName()));
// New changes don't affect the snapshot
appsFilter.removePackage(target, false);
assertTrue(
appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
SYSTEM_USER));
assertFalse(
snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
SYSTEM_USER));
}
private List<Integer> toList(int[] array) {
ArrayList<Integer> ret = new ArrayList<>(array.length);
for (int i = 0; i < array.length; i++) {
@@ -1282,7 +1331,7 @@ public class AppsFilterTest {
PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
}
private void simulateAddBasicAndroid(AppsFilter appsFilter) throws Exception {
private void simulateAddBasicAndroid(AppsFilterImpl appsFilter) throws Exception {
final Signature frameworkSignature = Mockito.mock(Signature.class);
final SigningDetails frameworkSigningDetails =
new SigningDetails(new Signature[]{frameworkSignature}, 1);
@@ -1291,17 +1340,17 @@ public class AppsFilterTest {
b -> b.setSigningDetails(frameworkSigningDetails));
}
private PackageSetting simulateAddPackage(AppsFilter filter,
private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId) {
return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/);
}
private PackageSetting simulateAddPackage(AppsFilter filter,
private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/);
}
private PackageSetting simulateAddPackage(AppsFilter filter,
private PackageSetting simulateAddPackage(AppsFilterImpl filter,
ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
@Nullable SharedUserSetting sharedUserSetting) {
final PackageSetting setting =
@@ -1324,7 +1373,7 @@ public class AppsFilterTest {
return setting;
}
private void simulateAddPackage(PackageSetting setting, AppsFilter filter,
private void simulateAddPackage(PackageSetting setting, AppsFilterImpl filter,
@Nullable SharedUserSetting sharedUserSetting) {
mExisting.put(setting.getPackageName(), setting);
if (sharedUserSetting != null) {

View File

@@ -17,6 +17,7 @@
package com.android.server.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -860,6 +861,54 @@ public class WatcherTest {
}
}
@Test
public void testWatchedSparseSetArray() {
final String name = "WatchedSparseSetArray";
WatchableTester tester;
// Test WatchedSparseSetArray
WatchedSparseSetArray array = new WatchedSparseSetArray();
tester = new WatchableTester(array, name);
tester.verify(0, "Initial array - no registration");
array.add(INDEX_A, 1);
tester.verify(0, "Updates with no registration");
tester.register();
tester.verify(0, "Updates with no registration");
array.add(INDEX_B, 2);
tester.verify(1, "Updates with registration");
array.add(INDEX_B, 4);
array.add(INDEX_C, 5);
tester.verify(3, "Updates with registration");
// Special methods
assertTrue(array.remove(INDEX_C, 5));
tester.verify(4, "Removed 5 from key 3");
array.remove(INDEX_B);
tester.verify(5, "Removed everything for key 2");
// Snapshot
{
WatchedSparseSetArray arraySnap = (WatchedSparseSetArray) array.snapshot();
tester.verify(5, "Generate snapshot");
// Verify that the snapshot is a proper copy of the source.
assertEquals("WatchedSparseSetArray snap same size",
array.size(), arraySnap.size());
for (int i = 0; i < array.size(); i++) {
ArraySet set = array.get(array.keyAt(i));
ArraySet setSnap = arraySnap.get(arraySnap.keyAt(i));
assertNotNull(set);
assertTrue(set.equals(setSnap));
}
array.add(INDEX_D, 9);
tester.verify(6, "Tick after snapshot");
// Verify that the array is sealed
verifySealed(name, ()->arraySnap.add(INDEX_D, 10));
assertTrue(!array.isSealed());
assertTrue(arraySnap.isSealed());
}
array.clear();
tester.verify(7, "Cleared all entries");
}
private static class IndexGenerator {
private final int mSeed;
private final Random mRandom;
@@ -1084,6 +1133,18 @@ public class WatcherTest {
assertEquals(a.equals(s), true);
a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
assertEquals(a.equals(s), false);
// Verify copy-in/out
{
final String msg = name + " copy";
WatchedSparseBooleanMatrix copy = new WatchedSparseBooleanMatrix();
copy.copyFrom(matrix);
final int end = copy.size();
assertTrue(msg + " size mismatch " + end + " " + matrix.size(), end == matrix.size());
for (int i = 0; i < end; i++) {
assertEquals(copy.keyAt(i), keys[i]);
}
}
}
@Test