Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dfddf9f7 authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge changes I3fb320ba,If0dfde20,I4eb66ffd into sc-dev

* changes:
  Add two more methods to the computer
  Add a WatchedLongSparseArray
  Disable snapshots for mocked tests
parents 4ac678f2 4d81bb10
Loading
Loading
Loading
Loading
+53 −49
Original line number Diff line number Diff line
@@ -316,7 +316,6 @@ import android.util.ExceptionUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.LogPrinter;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
import android.util.MathUtils;
import android.util.PackageUtils;
@@ -401,6 +400,7 @@ import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
import com.android.server.utils.Watched;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
import com.android.server.utils.WatchedSparseBooleanArray;
import com.android.server.utils.Watcher;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -1402,10 +1402,10 @@ public class PackageManagerService extends IPackageManager.Stub
    // Currently known shared libraries.
    @Watched
    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries =
            new WatchedArrayMap<>();
    final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
            mSharedLibraries = new WatchedArrayMap<>();
    @Watched
    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
    final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
            mStaticLibsByDeclaringPackage = new WatchedArrayMap<>();
    // Mapping from instrumentation class names to info about them.
@@ -1791,8 +1791,8 @@ public class PackageManagerService extends IPackageManager.Stub
        public final Settings settings;
        public final SparseIntArray isolatedOwners;
        public final WatchedArrayMap<String, AndroidPackage> packages;
        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> sharedLibs;
        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> staticLibs;
        public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibs;
        public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> staticLibs;
        public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation;
        public final WatchedSparseBooleanArray webInstantAppsDisabled;
        public final ComponentName resolveComponentName;
@@ -2010,9 +2010,9 @@ public class PackageManagerService extends IPackageManager.Stub
        private final WatchedArrayMap<String, AndroidPackage> mPackages;
        private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
                mInstrumentation;
        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
        private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
                mStaticLibsByDeclaringPackage;
        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
        private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
                mSharedLibraries;
        private final ComponentName mLocalResolveComponentName;
        private final ActivityInfo mResolveActivity;
@@ -3551,7 +3551,7 @@ public class PackageManagerService extends IPackageManager.Stub
            packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
            // Is this a static library?
            LongSparseArray<SharedLibraryInfo> versionedLib =
            WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
                    mStaticLibsByDeclaringPackage.get(packageName);
            if (versionedLib == null || versionedLib.size() <= 0) {
                return packageName;
@@ -4706,6 +4706,10 @@ public class PackageManagerService extends IPackageManager.Stub
    // and an image with the flag set false does not use snapshots.
    private static final boolean SNAPSHOT_ENABLED = true;
    // The per-instance snapshot disable/enable flag.  This is generally set to false in
    // test instances and set to SNAPSHOT_ENABLED in operational instances.
    private final boolean mSnapshotEnabled;
    /**
     * Return the live computer.
     */
@@ -4718,7 +4722,7 @@ public class PackageManagerService extends IPackageManager.Stub
     * The live computer will be returned if snapshots are disabled.
     */
    private Computer snapshotComputer() {
        if (!SNAPSHOT_ENABLED) {
        if (!mSnapshotEnabled) {
            return mLiveComputer;
        }
        if (Thread.holdsLock(mLock)) {
@@ -6053,15 +6057,12 @@ public class PackageManagerService extends IPackageManager.Stub
        mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
        mResolveComponentName = testParams.resolveComponentName;
        // Create the computer as soon as the state objects have been installed.  The
        // cached computer is the same as the live computer until the end of the
        // constructor, at which time the invalidation method updates it.  The cache is
        // corked initially to ensure a cached computer is not built until the end of the
        // constructor.
        sSnapshotCorked = true;
        // Disable snapshots in this instance of PackageManagerService, which is only used
        // for testing.  The instance still needs a live computer.  The snapshot computer
        // is set to null since it must never be used by this instance.
        mSnapshotEnabled = false;
        mLiveComputer = createLiveComputer();
        mSnapshotComputer = mLiveComputer;
        registerObserver();
        mSnapshotComputer = null;
        mPackages.putAll(testParams.packages);
        mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6074,7 +6075,6 @@ public class PackageManagerService extends IPackageManager.Stub
        mIncrementalVersion = testParams.incrementalVersion;
        invalidatePackageInfoCache();
        sSnapshotCorked = false;
    }
    public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest,
@@ -6220,6 +6220,7 @@ public class PackageManagerService extends IPackageManager.Stub
        // constructor, at which time the invalidation method updates it.  The cache is
        // corked initially to ensure a cached computer is not built until the end of the
        // constructor.
        mSnapshotEnabled = SNAPSHOT_ENABLED;
        sSnapshotCorked = true;
        mLiveComputer = createLiveComputer();
        mSnapshotComputer = mLiveComputer;
@@ -8043,7 +8044,7 @@ public class PackageManagerService extends IPackageManager.Stub
            final int[] allUsers = mUserManager.getUserIds();
            final int libCount = mSharedLibraries.size();
            for (int i = 0; i < libCount; i++) {
                final LongSparseArray<SharedLibraryInfo> versionedLib
                final WatchedLongSparseArray<SharedLibraryInfo> versionedLib
                        = mSharedLibraries.valueAt(i);
                if (versionedLib == null) {
                    continue;
@@ -8308,7 +8309,8 @@ public class PackageManagerService extends IPackageManager.Stub
            final int libCount = mSharedLibraries.size();
            for (int i = 0; i < libCount; i++) {
                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.valueAt(i);
                WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
                        mSharedLibraries.valueAt(i);
                if (versionedLib == null) {
                    continue;
                }
@@ -8377,7 +8379,8 @@ public class PackageManagerService extends IPackageManager.Stub
            int libraryCount = mSharedLibraries.size();
            for (int i = 0; i < libraryCount; i++) {
                LongSparseArray<SharedLibraryInfo> versionedLibrary = mSharedLibraries.valueAt(i);
                WatchedLongSparseArray<SharedLibraryInfo> versionedLibrary =
                        mSharedLibraries.valueAt(i);
                if (versionedLibrary == null) {
                    continue;
                }
@@ -8538,7 +8541,8 @@ public class PackageManagerService extends IPackageManager.Stub
            Set<String> libs = null;
            final int libCount = mSharedLibraries.size();
            for (int i = 0; i < libCount; i++) {
                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.valueAt(i);
                WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
                        mSharedLibraries.valueAt(i);
                if (versionedLib == null) {
                    continue;
                }
@@ -9846,7 +9850,7 @@ public class PackageManagerService extends IPackageManager.Stub
    private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        return liveComputer().queryIntentActivitiesInternal(intent,
        return snapshotComputer().queryIntentActivitiesInternal(intent,
                resolvedType, flags, userId);
    }
@@ -10333,7 +10337,7 @@ public class PackageManagerService extends IPackageManager.Stub
    private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
            String resolvedType, int flags, int userId, int callingUid,
            boolean includeInstantApps) {
        return liveComputer().queryIntentServicesInternal(intent,
        return snapshotComputer().queryIntentServicesInternal(intent,
                resolvedType, flags, userId, callingUid,
                includeInstantApps);
    }
@@ -12207,10 +12211,10 @@ public class PackageManagerService extends IPackageManager.Stub
    @Nullable
    private static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
            Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries) {
            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
        if (newLibraries != null) {
            final LongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
            final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
            SharedLibraryInfo info = null;
            if (versionedLib != null) {
                info = versionedLib.get(version);
@@ -12219,7 +12223,7 @@ public class PackageManagerService extends IPackageManager.Stub
                return info;
            }
        }
        final LongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
        final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
        if (versionedLib == null) {
            return null;
        }
@@ -12227,7 +12231,7 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    private SharedLibraryInfo getLatestSharedLibraVersionLPr(AndroidPackage pkg) {
        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                pkg.getStaticSharedLibName());
        if (versionedLib == null) {
            return null;
@@ -12532,8 +12536,8 @@ public class PackageManagerService extends IPackageManager.Stub
    private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
            Map<String, AndroidPackage> availablePackages,
            @NonNull final Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable final Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries)
            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
            throws PackageManagerException {
        if (pkg == null) {
            return null;
@@ -12630,8 +12634,8 @@ public class PackageManagerService extends IPackageManager.Stub
            @NonNull String packageName, boolean required, int targetSdk,
            @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
            @NonNull final Map<String, AndroidPackage> availablePackages,
            @NonNull final Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable final Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries)
            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
            throws PackageManagerException {
        final int libCount = requestedLibraries.size();
        for (int i = 0; i < libCount; i++) {
@@ -14052,7 +14056,7 @@ public class PackageManagerService extends IPackageManager.Stub
                long minVersionCode = Long.MIN_VALUE;
                long maxVersionCode = Long.MAX_VALUE;
                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                        pkg.getStaticSharedLibName());
                if (versionedLib != null) {
                    final int versionCount = versionedLib.size();
@@ -14280,8 +14284,8 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    private static boolean sharedLibExists(final String name, final long version,
            Map<String, LongSparseArray<SharedLibraryInfo>> librarySource) {
        LongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
        if (versionedLib != null && versionedLib.indexOfKey(version) >= 0) {
            return true;
        }
@@ -14291,9 +14295,9 @@ public class PackageManagerService extends IPackageManager.Stub
    @GuardedBy("mLock")
    private void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
        final String name = libraryInfo.getName();
        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
        if (versionedLib == null) {
            versionedLib = new LongSparseArray<>();
            versionedLib = new WatchedLongSparseArray<>();
            mSharedLibraries.put(name, versionedLib);
        }
        final String declaringPackageName = libraryInfo.getDeclaringPackage().getPackageName();
@@ -14304,7 +14308,7 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    private boolean removeSharedLibraryLPw(String name, long version) {
        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
        if (versionedLib == null) {
            return false;
        }
@@ -18299,7 +18303,7 @@ public class PackageManagerService extends IPackageManager.Stub
        public final Map<String, ScanResult> scannedPackages;
        public final Map<String, AndroidPackage> allPackages;
        public final Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource;
        public final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource;
        public final Map<String, InstallArgs> installArgs;
        public final Map<String, PackageInstalledInfo> installResults;
        public final Map<String, PrepareResult> preparedPackages;
@@ -18310,7 +18314,7 @@ public class PackageManagerService extends IPackageManager.Stub
                Map<String, InstallArgs> installArgs,
                Map<String, PackageInstalledInfo> installResults,
                Map<String, PrepareResult> preparedPackages,
                Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                Map<String, AndroidPackage> allPackages,
                Map<String, VersionInfo> versionInfos,
                Map<String, PackageSetting> lastStaticSharedLibSettings) {
@@ -18325,7 +18329,7 @@ public class PackageManagerService extends IPackageManager.Stub
        }
        private ReconcileRequest(Map<String, ScanResult> scannedPackages,
                Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                Map<String, AndroidPackage> allPackages,
                Map<String, VersionInfo> versionInfos,
                Map<String, PackageSetting> lastStaticSharedLibSettings) {
@@ -18422,7 +18426,7 @@ public class PackageManagerService extends IPackageManager.Stub
        combinedPackages.putAll(request.allPackages);
        final Map<String, LongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
                new ArrayMap<>();
        for (String installPackageName : scannedPackages.keySet()) {
@@ -18646,7 +18650,7 @@ public class PackageManagerService extends IPackageManager.Stub
     */
    private static List<SharedLibraryInfo> getAllowedSharedLibInfos(
            ScanResult scanResult,
            Map<String, LongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
        // Let's used the parsed package as scanResult.pkgSetting may be null
        final ParsedPackage parsedPackage = scanResult.request.parsedPackage;
        if (scanResult.staticSharedLibraryInfo == null
@@ -18716,7 +18720,7 @@ public class PackageManagerService extends IPackageManager.Stub
     * added.
     */
    private static boolean addSharedLibraryToPackageVersionMap(
            Map<String, LongSparseArray<SharedLibraryInfo>> target,
            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
            SharedLibraryInfo library) {
        final String name = library.getName();
        if (target.containsKey(name)) {
@@ -18728,7 +18732,7 @@ public class PackageManagerService extends IPackageManager.Stub
                return false;
            }
        } else {
            target.put(name, new LongSparseArray<>());
            target.put(name, new WatchedLongSparseArray<>());
        }
        target.get(name).put(library.getLongVersion(), library);
        return true;
@@ -23851,7 +23855,7 @@ public class PackageManagerService extends IPackageManager.Stub
                final int numSharedLibraries = mSharedLibraries.size();
                for (int index = 0; index < numSharedLibraries; index++) {
                    final String libName = mSharedLibraries.keyAt(index);
                    LongSparseArray<SharedLibraryInfo> versionedLib
                    WatchedLongSparseArray<SharedLibraryInfo> versionedLib
                            = mSharedLibraries.get(libName);
                    if (versionedLib == null) {
                        continue;
@@ -24200,7 +24204,7 @@ public class PackageManagerService extends IPackageManager.Stub
        final int count = mSharedLibraries.size();
        for (int i = 0; i < count; i++) {
            final String libName = mSharedLibraries.keyAt(i);
            LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
            WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
            if (versionedLib == null) {
                continue;
            }
+418 −0

File added.

Preview size limit exceeded, changes collapsed.

+163 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.junit.Assert.fail;

import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -618,6 +619,129 @@ public class WatcherTest {
        }
    }

    @Test
    public void testWatchedLongSparseArray() {
        final String name = "WatchedLongSparseArray";
        WatchableTester tester;

        // Create a few leaves
        Leaf leafA = new Leaf();
        Leaf leafB = new Leaf();
        Leaf leafC = new Leaf();
        Leaf leafD = new Leaf();

        // Test WatchedLongSparseArray
        WatchedLongSparseArray<Leaf> array = new WatchedLongSparseArray<>();
        array.put(INDEX_A, leafA);
        array.put(INDEX_B, leafB);
        tester = new WatchableTester(array, name);
        tester.verify(0, "Initial array - no registration");
        leafA.tick();
        tester.verify(0, "Updates with no registration");
        tester.register();
        tester.verify(0, "Updates with no registration");
        leafA.tick();
        tester.verify(1, "Updates with registration");
        leafB.tick();
        tester.verify(2, "Updates with registration");
        array.remove(INDEX_B);
        tester.verify(3, "Removed b");
        leafB.tick();
        tester.verify(3, "Updates with b not watched");
        array.put(INDEX_B, leafB);
        array.put(INDEX_C, leafB);
        tester.verify(5, "Added b twice");
        leafB.tick();
        tester.verify(6, "Changed b - single notification");
        array.remove(INDEX_C);
        tester.verify(7, "Removed first b");
        leafB.tick();
        tester.verify(8, "Changed b - single notification");
        array.remove(INDEX_B);
        tester.verify(9, "Removed second b");
        leafB.tick();
        tester.verify(9, "Updated leafB - no change");
        array.clear();
        tester.verify(10, "Cleared array");
        leafB.tick();
        tester.verify(10, "Change to b not in array");

        // Special methods
        array.put(INDEX_A, leafA);
        array.put(INDEX_B, leafB);
        array.put(INDEX_C, leafC);
        tester.verify(13, "Added c");
        leafC.tick();
        tester.verify(14, "Ticked c");
        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
        tester.verify(15, "Replaced c with d");
        leafC.tick();
        tester.verify(15, "Ticked c (c not registered)");
        leafD.tick();
        tester.verify(16, "Ticked d and c (c not registered)");
        array.append(INDEX_D, leafC);
        tester.verify(17, "Append c");
        leafC.tick();
        leafD.tick();
        tester.verify(19, "Ticked d and c");
        assertEquals("Verify four elements", 4, array.size());
        // Figure out which elements are at which indices.
        Leaf[] x = new Leaf[4];
        for (int i = 0; i < 4; i++) {
            x[i] = array.valueAt(i);
        }
        array.removeAt(1);
        tester.verify(20, "Removed one element");
        x[1].tick();
        tester.verify(20, "Ticked one removed element");
        x[2].tick();
        tester.verify(21, "Ticked one remaining element");

        // Snapshot
        {
            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
            tester.verify(21, "Generate snapshot (no changes)");
            // Verify that the snapshot is a proper copy of the source.
            assertEquals(name + " snap same size",
                         array.size(), arraySnap.size());
            for (int i = 0; i < array.size(); i++) {
                for (int j = 0; j < arraySnap.size(); j++) {
                    assertTrue(name + " elements differ",
                               array.valueAt(i) != arraySnap.valueAt(j));
                }
                assertTrue(name + " element copy",
                           array.valueAt(i).equals(arraySnap.valueAt(i)));
            }
            leafD.tick();
            tester.verify(22, "Tick after snapshot");
            // Verify that the array snapshot is sealed
            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
            assertTrue(!array.isSealed());
            assertTrue(arraySnap.isSealed());
        }
        // Recreate the snapshot since the test corrupted it.
        {
            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
            // Verify that elements are also snapshots
            final Leaf arraySnapElement = arraySnap.valueAt(0);
            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
        }
        // Verify copy-in/out
        {
            final String msg = name + " copy-in/out";
            LongSparseArray<Leaf> base = new LongSparseArray<>();
            array.copyTo(base);
            WatchedLongSparseArray<Leaf> copy = new WatchedLongSparseArray<>();
            copy.copyFrom(base);
            final int end = array.size();
            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
            for (int i = 0; i < end; i++) {
                final long key = array.keyAt(i);
                assertTrue(msg, array.get(i) == copy.get(i));
            }
        }
    }

    @Test
    public void testWatchedSparseBooleanArray() {
        final String name = "WatchedSparseBooleanArray";
@@ -733,4 +857,43 @@ public class WatcherTest {
            }
        }
    }

    @Test
    public void testNestedArrays() {
        final String name = "NestedArrays";
        WatchableTester tester;

        // Create a few leaves
        Leaf leafA = new Leaf();
        Leaf leafB = new Leaf();
        Leaf leafC = new Leaf();
        Leaf leafD = new Leaf();

        // Test nested arrays.
        WatchedLongSparseArray<Leaf> lsaA = new WatchedLongSparseArray<>();
        lsaA.put(2, leafA);
        WatchedLongSparseArray<Leaf> lsaB = new WatchedLongSparseArray<>();
        lsaB.put(4, leafB);
        WatchedLongSparseArray<Leaf> lsaC = new WatchedLongSparseArray<>();
        lsaC.put(6, leafC);

        WatchedArrayMap<String, WatchedLongSparseArray<Leaf>> array =
                new WatchedArrayMap<>();
        array.put("A", lsaA);
        array.put("B", lsaB);

        // Test WatchedSparseIntArray
        tester = new WatchableTester(array, name);
        tester.verify(0, "Initial array - no registration");
        tester.register();
        tester.verify(0, "Initial array - post registration");
        leafA.tick();
        tester.verify(1, "tick grand-leaf");
        lsaA.put(2, leafD);
        tester.verify(2, "replace leafA");
        leafA.tick();
        tester.verify(2, "tick unregistered leafA");
        leafD.tick();
        tester.verify(3, "tick leafD");
    }
}