summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/Computer.java2
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerLocal.java106
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java64
-rw-r--r--services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java184
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt171
6 files changed, 514 insertions, 26 deletions
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 591619698295..15cd63927648 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -187,6 +187,8 @@ public interface Computer extends PackageDataSnapshot {
PackageStateInternal getPackageStateInternal(String packageName);
PackageStateInternal getPackageStateInternal(String packageName, int callingUid);
+ PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+ @UserIdInt int userId);
ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId);
ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
int sourceUserId, int targetUserId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 50bb05161ffd..da6f042ca462 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1816,6 +1816,19 @@ public class ComputerEngine implements Computer {
return mSettings.getPackage(packageName);
}
+ @Override
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName,
+ int callingUid, @UserIdInt int userId) {
+ packageName = resolveInternalPackageNameInternalLocked(
+ packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
+ var packageState = mSettings.getPackage(packageName);
+ if (shouldFilterApplication(packageState, callingUid, userId)) {
+ return null;
+ } else {
+ return packageState;
+ }
+ }
+
public final ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId) {
final int callingUid = Binder.getCallingUid();
if (getInstantAppPackageName(callingUid) != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 39cc37ea2e7c..c9b48bf3c2a1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,11 +20,17 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.server.pm.pkg.PackageState;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
/**
* In-process API for server side PackageManager related infrastructure.
@@ -57,10 +63,10 @@ public interface PackageManagerLocal {
FLAG_STORAGE_CE,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface StorageFlags {}
+ @interface StorageFlags {}
/**
- * Reconcile sdk data sub-directories for the given {@code packagName}.
+ * Reconcile sdk data sub-directories for the given {@code packageName}.
*
* Sub directories are created if they do not exist already. If there is an existing per-
* sdk directory that is missing from {@code subDirNames}, then it is removed.
@@ -80,4 +86,100 @@ public interface PackageManagerLocal {
void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
@NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
@NonNull String seInfo, @StorageFlags int flags) throws IOException;
+
+ /**
+ * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and
+ * closed as soon as possible.
+ * <p/>
+ * All reachable types in the snapshot are read-only.
+ * <p/>
+ * The snapshot assumes the caller is acting on behalf of the system and will not filter any
+ * results.
+ *
+ * @hide
+ */
+ @NonNull
+ UnfilteredSnapshot withUnfilteredSnapshot();
+
+ /**
+ * {@link #withFilteredSnapshot(int, UserHandle)} that infers the UID and user from the
+ * caller through {@link Binder#getCallingUid()} and {@link Binder#getCallingUserHandle()}.
+ *
+ * @see #withFilteredSnapshot(int, UserHandle)
+ * @hide
+ */
+ @NonNull
+ FilteredSnapshot withFilteredSnapshot();
+
+ /**
+ * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and
+ * closed as soon as possible.
+ * <p/>
+ * All reachable types in the snapshot are read-only.
+ *
+ * @param callingUid The caller UID to filter results based on. This includes package visibility
+ * and permissions, including cross-user enforcement.
+ * @param user The user to query as, should usually be the user that the caller was
+ * invoked from.
+ * @hide
+ */
+ @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user
+ @NonNull
+ FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
+
+ /**
+ * @hide
+ */
+ interface UnfilteredSnapshot extends AutoCloseable {
+
+ /**
+ * Allows re-use of this snapshot, but in a filtered context. This allows a caller to invoke
+ * itself as multiple other actual callers without having to re-take a snapshot.
+ * <p/>
+ * Note that closing the parent snapshot closes any filtered children generated from it.
+ *
+ * @return An isolated instance of {@link FilteredSnapshot} which can be closed without
+ * affecting this parent snapshot or any sibling snapshots.
+ */
+ @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user
+ @NonNull
+ FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user);
+
+ /**
+ * Returns a map of all {@link PackageState PackageStates} on the device.
+ *
+ * @return Mapping of package name to {@link PackageState}.
+ */
+ @NonNull
+ Map<String, PackageState> getPackageStates();
+
+ @Override
+ void close();
+ }
+
+ /**
+ * @hide
+ */
+ interface FilteredSnapshot extends AutoCloseable {
+
+ /**
+ * @return {@link PackageState} for the {@code packageName}, filtered if applicable.
+ */
+ @Nullable
+ PackageState getPackageState(@NonNull String packageName);
+
+ /**
+ * Iterates on all states. This should only be used when either the target package name
+ * is not known or the large majority of the states are expected to be used.
+ *
+ * This will cause app visibility filtering to be invoked on each state on the device,
+ * which can be expensive.
+ *
+ * @param consumer Block to accept each state as it becomes available post-filtering.
+ */
+ void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
+
+ @Override
+ void close();
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2c460f858d36..d939ca61fa08 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -200,6 +200,7 @@ import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.ArtUtils;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.local.PackageManagerLocalImpl;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
@@ -1071,10 +1072,27 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@VisibleForTesting(visibility = Visibility.PACKAGE)
@NonNull
public Computer snapshotComputer() {
- if (Thread.holdsLock(mLock)) {
- // If the current thread holds mLock then it may have modified state but not
- // yet invalidated the snapshot. Always give the thread the live computer.
- return mLiveComputer;
+ return snapshotComputer(true /*allowLiveComputer*/);
+ }
+
+ /**
+ * This method should only ever be called from {@link PackageManagerLocal#snapshot()}.
+ *
+ * @param allowLiveComputer Whether to allow a live computer instance based on caller {@link
+ * #mLock} hold state. In certain cases, like for {@link
+ * PackageManagerLocal} API, it must be enforced that the caller gets
+ * a snapshot at time, and never the live variant.
+ * @deprecated Use {@link #snapshotComputer()}
+ */
+ @Deprecated
+ @NonNull
+ public Computer snapshotComputer(boolean allowLiveComputer) {
+ if (allowLiveComputer) {
+ if (Thread.holdsLock(mLock)) {
+ // If the current thread holds mLock then it may have modified state but not
+ // yet invalidated the snapshot. Always give the thread the live computer.
+ return mLiveComputer;
+ }
}
var oldSnapshot = sSnapshot.get();
@@ -1531,7 +1549,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);
- LocalManagerRegistry.addManager(PackageManagerLocal.class, m.new PackageManagerLocalImpl());
+ LocalManagerRegistry.addManager(PackageManagerLocal.class,
+ new PackageManagerLocalImpl(m));
return Pair.create(m, iPackageManager);
}
@@ -6017,25 +6036,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
}
- private class PackageManagerLocalImpl implements PackageManagerLocal {
- @Override
- public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
- @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
- @NonNull String seInfo, int flags) throws IOException {
- synchronized (mInstallLock) {
- ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid,
- packageName, subDirNames, userId, appId, seInfo,
- flags);
- args.previousAppId = previousAppId;
- try {
- mInstaller.reconcileSdkData(args);
- } catch (InstallerException e) {
- throw new IOException(e.getMessage());
- }
- }
- }
- }
-
private class PackageManagerInternalImpl extends PackageManagerInternalBase {
public PackageManagerInternalImpl() {
@@ -7296,4 +7296,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mSettings.addInstallerPackageNames(installSource);
}
}
+
+ public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
+ @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
+ @NonNull String seInfo, int flags) throws IOException {
+ synchronized (mInstallLock) {
+ ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid,
+ packageName, subDirNames, userId, appId, seInfo,
+ flags);
+ args.previousAppId = previousAppId;
+ try {
+ mInstaller.reconcileSdkData(args);
+ } catch (InstallerException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
new file mode 100644
index 000000000000..00c8f84b340b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -0,0 +1,184 @@
+/*
+ * 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.local;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.server.pm.Computer;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/** @hide */
+public class PackageManagerLocalImpl implements PackageManagerLocal {
+
+ private final PackageManagerService mService;
+
+ public PackageManagerLocalImpl(PackageManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
+ @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
+ @NonNull String seInfo, int flags) throws IOException {
+ mService.reconcileSdkData(volumeUuid, packageName, subDirNames, userId, appId,
+ previousAppId, seInfo, flags);
+ }
+
+ @NonNull
+ @Override
+ public UnfilteredSnapshotImpl withUnfilteredSnapshot() {
+ return new UnfilteredSnapshotImpl(mService.snapshotComputer(false /*allowLiveComputer*/));
+ }
+
+ @NonNull
+ @Override
+ public FilteredSnapshotImpl withFilteredSnapshot() {
+ return withFilteredSnapshot(Binder.getCallingUid(), Binder.getCallingUserHandle());
+ }
+
+ @NonNull
+ @Override
+ public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) {
+ return new FilteredSnapshotImpl(callingUid, user,
+ mService.snapshotComputer(false /*allowLiveComputer*/), null);
+ }
+
+ private abstract static class BaseSnapshotImpl implements AutoCloseable {
+
+ private boolean mClosed;
+
+ @NonNull
+ protected Computer mSnapshot;
+
+ private BaseSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
+ mSnapshot = (Computer) snapshot;
+ }
+
+ @Override
+ public void close() {
+ mClosed = true;
+ mSnapshot = null;
+ // TODO: Recycle snapshots?
+ }
+
+ @CallSuper
+ protected void checkClosed() {
+ if (mClosed) {
+ throw new IllegalStateException("Snapshot already closed");
+ }
+ }
+ }
+
+ private static class UnfilteredSnapshotImpl extends BaseSnapshotImpl implements
+ UnfilteredSnapshot {
+
+ private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
+ super(snapshot);
+ }
+
+ @Override
+ public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
+ return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this);
+ }
+
+ @SuppressWarnings("RedundantSuppression")
+ @NonNull
+ @Override
+ public Map<String, PackageState> getPackageStates() {
+ checkClosed();
+
+ //noinspection unchecked, RedundantCast
+ return (Map<String, PackageState>) (Map<?, ?>) mSnapshot.getPackageStates();
+ }
+ }
+
+ private static class FilteredSnapshotImpl extends BaseSnapshotImpl implements
+ FilteredSnapshot {
+
+ private final int mCallingUid;
+
+ @UserIdInt
+ private final int mUserId;
+
+ @Nullable
+ private ArrayList<PackageState> mFilteredPackageStates;
+
+ @Nullable
+ private final UnfilteredSnapshotImpl mParentSnapshot;
+
+ private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
+ @NonNull PackageDataSnapshot snapshot,
+ @Nullable UnfilteredSnapshotImpl parentSnapshot) {
+ super(snapshot);
+ mCallingUid = callingUid;
+ mUserId = user.getIdentifier();
+ mParentSnapshot = parentSnapshot;
+ }
+
+ @Override
+ protected void checkClosed() {
+ if (mParentSnapshot != null) {
+ mParentSnapshot.checkClosed();
+ }
+
+ super.checkClosed();
+ }
+
+ @Nullable
+ @Override
+ public PackageState getPackageState(@NonNull String packageName) {
+ checkClosed();
+ return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
+ }
+
+ @Override
+ public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+ checkClosed();
+
+ if (mFilteredPackageStates == null) {
+ var packageStates = mSnapshot.getPackageStates();
+ var filteredPackageStates = new ArrayList<PackageState>();
+ for (int index = 0, size = packageStates.size(); index < size; index++) {
+ var packageState = packageStates.valueAt(index);
+ if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
+ filteredPackageStates.add(packageState);
+ }
+ }
+ mFilteredPackageStates = filteredPackageStates;
+ }
+
+ for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
+ var packageState = mFilteredPackageStates.get(index);
+ consumer.accept(packageState);
+ }
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
new file mode 100644
index 000000000000..faa235254665
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.test
+
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.pm.Computer
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.PackageManagerService
+import com.android.server.pm.local.PackageManagerLocalImpl
+import com.android.server.pm.pkg.PackageState
+import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import kotlin.test.assertFailsWith
+
+class PackageManagerLocalSnapshotTest {
+
+ private val service = mockThrowOnUnmocked<PackageManagerService> {
+ @Suppress("DEPRECATION")
+ whenever(snapshotComputer(false)) { mockSnapshot() }
+ }
+
+ private val packageStateAll = mockPackageState("com.package.all")
+ private val packageStateUser0 = mockPackageState("com.package.zero")
+ private val packageStateUser10 = mockPackageState("com.package.ten")
+
+ @Test
+ fun unfiltered() {
+ val pmLocal = pmLocal()
+ val snapshot = pmLocal.withUnfilteredSnapshot()
+ val filteredOne: PackageManagerLocal.FilteredSnapshot
+ val filteredTwo: PackageManagerLocal.FilteredSnapshot
+ snapshot.use {
+ val packageStates = it.packageStates
+
+ // Check contents
+ assertThat(packageStates).containsExactly(
+ packageStateAll.packageName, packageStateAll,
+ packageStateUser0.packageName, packageStateUser0,
+ packageStateUser10.packageName, packageStateUser10,
+ )
+
+ // Check further calls get the same object
+ assertThat(it.packageStates).isSameInstanceAs(packageStates)
+
+ // Generate 3 filtered children (2 for the same caller, 1 for different)
+ filteredOne = it.filtered(1000, UserHandle.getUserHandleForUid(1000))
+ filteredTwo = it.filtered(1000, UserHandle.getUserHandleForUid(1000))
+ val filteredThree = it.filtered(20000, UserHandle.getUserHandleForUid(1001000))
+
+ // Check that siblings, even for the same input, are isolated
+ assertThat(filteredOne).isNotSameInstanceAs(filteredTwo)
+
+ assertThat(filteredOne.getPackageState(packageStateAll.packageName))
+ .isEqualTo(packageStateAll)
+ assertThat(filteredOne.getPackageState(packageStateUser0.packageName))
+ .isEqualTo(packageStateUser0)
+ assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
+
+ filteredThree.use {
+ val statesList = mutableListOf<PackageState>()
+ assertThat(it.forAllPackageStates { statesList += it })
+ assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+ }
+
+ // Call after child close, parent open fails
+ assertClosedFailure {
+ filteredThree.getPackageState(packageStateAll.packageName)
+ }
+
+ // Manually close first sibling and check that second still works
+ filteredOne.close()
+ assertThat(filteredTwo.getPackageState(packageStateAll.packageName))
+ .isEqualTo(packageStateAll)
+ }
+
+ // Call after close fails
+ assertClosedFailure { snapshot.packageStates }
+ assertClosedFailure { filteredOne.forAllPackageStates {} }
+ assertClosedFailure {
+ filteredTwo.getPackageState(packageStateAll.packageName)
+ }
+
+ // Check newly taken snapshot is different
+ assertThat(pmLocal.withUnfilteredSnapshot()).isNotSameInstanceAs(snapshot)
+ }
+
+ @Test
+ fun filtered() {
+ val pmLocal = pmLocal()
+ val snapshot = pmLocal.withFilteredSnapshot(1000, UserHandle.getUserHandleForUid(1000))
+ snapshot.use {
+ assertThat(it.getPackageState(packageStateAll.packageName))
+ .isEqualTo(packageStateAll)
+ assertThat(it.getPackageState(packageStateUser0.packageName))
+ .isEqualTo(packageStateUser0)
+ assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
+
+ val statesList = mutableListOf<PackageState>()
+ assertThat(it.forAllPackageStates { statesList += it })
+ assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+ }
+
+ // Call after close fails
+ assertClosedFailure {
+ snapshot.getPackageState(packageStateAll.packageName)
+ }
+
+ // Check newly taken snapshot is different
+ assertThat(pmLocal.withFilteredSnapshot()).isNotSameInstanceAs(snapshot)
+ }
+
+ private fun pmLocal(): PackageManagerLocal = PackageManagerLocalImpl(service)
+
+ private fun mockSnapshot() = mockThrowOnUnmocked<Computer> {
+ val packageStates = ArrayMap<String, PackageStateInternal>().apply {
+ put(packageStateAll.packageName, packageStateAll)
+ put(packageStateUser0.packageName, packageStateUser0)
+ put(packageStateUser10.packageName, packageStateUser10)
+ }
+ whenever(this.packageStates) { packageStates }
+ whenever(getPackageStateFiltered(anyString(), anyInt(), anyInt())) {
+ packageStates[arguments[0]]?.takeUnless {
+ shouldFilterApplication(it, arguments[1] as Int, arguments[2] as Int)
+ }
+ }
+
+ whenever(
+ shouldFilterApplication(any(PackageStateInternal::class.java), anyInt(), anyInt())
+ ) {
+ val packageState = arguments[0] as PackageState
+ val user = arguments[2] as Int
+
+ when (packageState) {
+ packageStateAll -> false
+ packageStateUser0 -> user != 0
+ packageStateUser10 -> user != 10
+ else -> true
+ }
+ }
+ }
+
+ private fun mockPackageState(packageName: String) = mockThrowOnUnmocked<PackageStateInternal> {
+ whenever(this.packageName) { packageName }
+ whenever(toString()) { packageName }
+ }
+
+ private fun assertClosedFailure(block: () -> Unit) =
+ assertFailsWith(IllegalStateException::class, block)
+ .run { assertThat(message).isEqualTo("Snapshot already closed") }
+}