Merge changes I3fb320ba,If0dfde20,I4eb66ffd into sc-dev

* changes:
  Add two more methods to the computer
  Add a WatchedLongSparseArray
  Disable snapshots for mocked tests
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8e2210e..2a3feb1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -316,7 +316,6 @@
 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.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 @@
 
     // 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 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 @@
         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 @@
             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 @@
     // 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 @@
      * 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 @@
         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 @@
         mIncrementalVersion = testParams.incrementalVersion;
 
         invalidatePackageInfoCache();
-        sSnapshotCorked = false;
     }
 
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest,
@@ -6220,6 +6220,7 @@
         // 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 @@
             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 @@
 
             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 @@
 
             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 @@
             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 @@
 
     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 @@
     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 @@
 
     @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 @@
                 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 @@
     }
 
     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 @@
 
     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 @@
             @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 @@
                 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 @@
     }
 
     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 @@
     @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 @@
     }
 
     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 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 @@
                 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 @@
         }
 
         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 @@
 
         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 @@
      */
     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 @@
      * 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 @@
                 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 @@
                 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 @@
         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;
             }
diff --git a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
new file mode 100644
index 0000000..bf23de1
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2020 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.annotation.Nullable;
+import android.util.LongSparseArray;
+
+/**
+ * A watched variant of {@link LongSparseArray}.  If a {@link Watchable} is stored in the
+ * array, the array registers with the {@link Watchable}.  The array registers only once
+ * with each {@link Watchable} no matter how many times the {@link Watchable} is stored in
+ * the array.
+ * @param <E> The element type, stored in the array.
+ */
+public class WatchedLongSparseArray<E> extends WatchableImpl
+        implements Snappable {
+
+    // The storage
+    private final LongSparseArray<E> mStorage;
+
+    // If true, the array is watching its children
+    private volatile boolean mWatching = false;
+
+    // The local observer
+    private final Watcher mObserver = new Watcher() {
+            @Override
+            public void onChange(@Nullable Watchable o) {
+                WatchedLongSparseArray.this.dispatchChange(o);
+            }
+        };
+
+    /**
+     * A private convenience function that notifies registered listeners that an element
+     * has been added to or removed from the array.  The what parameter is the array itself.
+     */
+    private void onChanged() {
+        dispatchChange(this);
+    }
+
+    /**
+     * A convenience function.  Register the object if it is {@link Watchable} and if the
+     * array is currently watching.  Note that the watching flag must be true if this
+     * function is to succeed.
+     */
+    private void registerChild(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            ((Watchable) o).registerObserver(mObserver);
+        }
+    }
+
+    /**
+     * A convenience function.  Unregister the object if it is {@link Watchable} and if
+     * the array is currently watching.  Note that the watching flag must be true if this
+     * function is to succeed.
+     */
+    private void unregisterChild(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            ((Watchable) o).unregisterObserver(mObserver);
+        }
+    }
+
+    /**
+     * A convenience function.  Unregister the object if it is {@link Watchable}, if the array is
+     * currently watching, and if the storage does not contain the object.  Note that the watching
+     * flag must be true if this function is to succeed.  This must be called after an object has
+     * been removed from the storage.
+     */
+    private void unregisterChildIf(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            if (mStorage.indexOfValue((E) o) == -1) {
+                ((Watchable) o).unregisterObserver(mObserver);
+            }
+        }
+    }
+
+    /**
+     * Register a {@link Watcher} with the array.  If this is the first Watcher than any
+     * array values that are {@link Watchable} are registered to the array itself.
+     */
+    @Override
+    public void registerObserver(@NonNull Watcher observer) {
+        super.registerObserver(observer);
+        if (registeredObserverCount() == 1) {
+            // The watching flag must be set true before any children are registered.
+            mWatching = true;
+            final int end = mStorage.size();
+            for (int i = 0; i < end; i++) {
+                registerChild(mStorage.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Unregister a {@link Watcher} from the array.  If this is the last Watcher than any
+     * array values that are {@link Watchable} are unregistered to the array itself.
+     */
+    @Override
+    public void unregisterObserver(@NonNull Watcher observer) {
+        super.unregisterObserver(observer);
+        if (registeredObserverCount() == 0) {
+            final int end = mStorage.size();
+            for (int i = 0; i < end; i++) {
+                unregisterChild(mStorage.valueAt(i));
+            }
+            // The watching flag must be true while children are unregistered.
+            mWatching = false;
+        }
+    }
+
+    /**
+     * Creates a new WatchedSparseArray containing no mappings.
+     */
+    public WatchedLongSparseArray() {
+        mStorage = new LongSparseArray();
+    }
+
+    /**
+     * Creates a new WatchedSparseArray containing no mappings that
+     * will not require any additional memory allocation to store the
+     * specified number of mappings.  If you supply an initial capacity of
+     * 0, the sparse array will be initialized with a light-weight
+     * representation not requiring any additional array allocations.
+     */
+    public WatchedLongSparseArray(int initialCapacity) {
+        mStorage = new LongSparseArray(initialCapacity);
+    }
+
+    /**
+     * Create a {@link WatchedLongSparseArray} from a {@link LongSparseArray}.
+     */
+    public WatchedLongSparseArray(@NonNull LongSparseArray<E> c) {
+        mStorage = c.clone();
+    }
+
+    /**
+     * The copy constructor does not copy the watcher data.
+     */
+    public WatchedLongSparseArray(@NonNull WatchedLongSparseArray<E> r) {
+        mStorage = r.mStorage.clone();
+    }
+
+    /**
+     * Make <this> a copy of src.  Any data in <this> is discarded.
+     */
+    public void copyFrom(@NonNull LongSparseArray<E> src) {
+        clear();
+        final int end = src.size();
+        for (int i = 0; i < end; i++) {
+            put(src.keyAt(i), src.valueAt(i));
+        }
+    }
+
+    /**
+     * Make dst a copy of <this>.  Any previous data in dst is discarded.
+     */
+    public void copyTo(@NonNull LongSparseArray<E> dst) {
+        dst.clear();
+        final int end = size();
+        for (int i = 0; i < end; i++) {
+            dst.put(keyAt(i), valueAt(i));
+        }
+    }
+
+    /**
+     * Return the underlying storage.  This breaks the wrapper but is necessary when
+     * passing the array to distant methods.
+     */
+    public LongSparseArray<E> untrackedStorage() {
+        return mStorage;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(long key) {
+        return mStorage.get(key);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    public E get(long key, E valueIfKeyNotFound) {
+        return mStorage.get(key, valueIfKeyNotFound);
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(long key) {
+        E old = mStorage.get(key, null);
+        mStorage.delete(key);
+        unregisterChildIf(old);
+        onChanged();
+
+    }
+
+    /**
+     * Alias for {@link #delete(long)}.
+     */
+    public void remove(long key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public void removeAt(int index) {
+        E old = mStorage.valueAt(index);
+        mStorage.removeAt(index);
+        unregisterChildIf(old);
+        onChanged();
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(long key, E value) {
+        E old = mStorage.get(key);
+        mStorage.put(key, value);
+        unregisterChildIf(old);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * Returns the number of key-value mappings that this LongSparseArray
+     * currently stores.
+     */
+    public int size() {
+        return mStorage.size();
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public long keyAt(int index) {
+        return mStorage.keyAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public E valueAt(int index) {
+        return mStorage.valueAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public void setValueAt(int index, E value) {
+        E old = mStorage.valueAt(index);
+        mStorage.setValueAt(index, value);
+        unregisterChildIf(old);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(long key) {
+        return mStorage.indexOfKey(key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(E value) {
+        return mStorage.indexOfValue(value);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
+     */
+    public int indexOfValueByValue(E value) {
+        return mStorage.indexOfValueByValue(value);
+    }
+
+    /**
+     * Removes all key-value mappings from this LongSparseArray.
+     */
+    public void clear() {
+        final int end = mStorage.size();
+        for (int i = 0; i < end; i++) {
+            unregisterChild(mStorage.valueAt(i));
+        }
+        mStorage.clear();
+        onChanged();
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(long key, E value) {
+        mStorage.append(key, value);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        return mStorage.toString();
+    }
+
+    /**
+     * Create a copy of the array.  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 returned snapshot is immutable.
+     * @return A new array whose elements are the elements of <this>.
+     */
+    public WatchedLongSparseArray<E> snapshot() {
+        WatchedLongSparseArray<E> l = new WatchedLongSparseArray<>(size());
+        snapshot(l, this);
+        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 WatchedLongSparseArray<E> 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 <E> void snapshot(@NonNull WatchedLongSparseArray<E> dst,
+            @NonNull WatchedLongSparseArray<E> src) {
+        if (dst.size() != 0) {
+            throw new IllegalArgumentException("snapshot destination is not empty");
+        }
+        final int end = src.size();
+        for (int i = 0; i < end; i++) {
+            final E val = Snapshots.maybeSnapshot(src.valueAt(i));
+            final long key = src.keyAt(i);
+            dst.put(key, val);
+        }
+        dst.seal();
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index b40d59c..57e95d7 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -22,6 +22,7 @@
 
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LongSparseArray;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -619,6 +620,129 @@
     }
 
     @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";
         WatchableTester tester;
@@ -733,4 +857,43 @@
             }
         }
     }
+
+    @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");
+    }
 }