[AGP UTP] fix avdmanager cache

Allow Avd Manager cache to support multiple managers from different avd
folder. This is required for allowing ice box support with Gradle
Managed Devices, because we need to support caching of avd managers from
both the standard avd folder and the gradle managed avd folder.

Test: pre-existing. Added basic equality checks for the CacheKey
Bug: 141510559
Change-Id: I559ff54f3e35281149c1a5917678ae399615df05
diff --git a/sdklib/BUILD b/sdklib/BUILD
index d874aa4..8e0a310 100644
--- a/sdklib/BUILD
+++ b/sdklib/BUILD
@@ -30,6 +30,8 @@
         "//tools/base/device_validator:studio.android.sdktools.dvlib[module]",
         "//tools/base/layoutlib-api:studio.android.sdktools.layoutlib-api[module]",
         "//tools/adt/idea/.idea/libraries:truth[test]",
+        "//tools/adt/idea/.idea/libraries:guava-testlib[test]",
+        "//tools/adt/idea/.idea/libraries:jimfs[test]",
         "//tools/base/repository:studio.android.sdktools.repository[module]",
         "//tools/base/testutils:studio.android.sdktools.testutils[module, test]",
     ],
@@ -124,6 +126,8 @@
         "//tools/base/repository:tools.repository",
         "//tools/base/repository:tools.testlib",
         "//tools/base/testutils:tools.testutils",
+        "//tools/base/third_party:com.google.guava_guava-testlib",
+        "//tools/base/third_party:com.google.jimfs_jimfs",
         "//tools/base/third_party:com.google.truth_truth",
         "//tools/base/third_party:junit_junit",
         "//tools/base/third_party:org.apache.httpcomponents_httpcore",
diff --git a/sdklib/android.sdktools.sdklib.iml b/sdklib/android.sdktools.sdklib.iml
index 612efe1..e10a9cc 100644
--- a/sdklib/android.sdktools.sdklib.iml
+++ b/sdklib/android.sdktools.sdklib.iml
@@ -15,7 +15,9 @@
     <orderEntry type="module" module-name="android.sdktools.dvlib" exported="" />
     <orderEntry type="module" module-name="android.sdktools.layoutlib-api" exported="" />
     <orderEntry type="library" scope="TEST" name="truth" level="project" />
+    <orderEntry type="library" scope="TEST" name="guava-testlib" level="project" />
+    <orderEntry type="library" scope="TEST" name="jimfs" level="project" />
     <orderEntry type="module" module-name="android.sdktools.repository" exported="" />
     <orderEntry type="module" module-name="android.sdktools.testutils" scope="TEST" />
   </component>
-</module>
\ No newline at end of file
+</module>
diff --git a/sdklib/build.gradle b/sdklib/build.gradle
index 1d1eefd..d39b444 100755
--- a/sdklib/build.gradle
+++ b/sdklib/build.gradle
@@ -20,6 +20,8 @@
     implementation libs.apache_httpmime
     implementation libs.apache_httpcore
 
+    testImplementation libs.guava_testlib
+    testImplementation libs.jimfs
     testImplementation libs.junit
     testImplementation libs.truth
     testImplementation project(':base:testutils')
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
index 1fa0b0f..d055cf1 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/avd/AvdManager.java
@@ -51,9 +51,6 @@
 import com.android.utils.ILogger;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Charsets;
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Table;
 import com.google.common.io.Closeables;
 import java.io.BufferedReader;
 import java.io.File;
@@ -73,6 +70,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
@@ -396,11 +394,46 @@
 
     private class AvdMgrException extends Exception { };
 
-    // A map where the keys are the locations of the SDK and the values are the corresponding
-    // AvdManagers. This prevents us from creating multiple AvdManagers for the same SDK and having
-    // them get out of sync.
-    private static final Table<String, FileOp, WeakReference<AvdManager>> mManagers =
-        HashBasedTable.create();
+    /** A key containing all the values that will make an AvdManager unique. */
+    protected static final class AvdManagerCacheKey {
+        /**
+         * The location of the user's Android SDK. Something like /home/user/Android/Sdk on Linux.
+         */
+        @NonNull private final Path mSdkLocation;
+        /**
+         * The location of the user's AVD folder. Something like /home/user/.android/avd on Linux.
+         */
+        @NonNull private final Path mAvdHomeFolder;
+
+        protected AvdManagerCacheKey(@NonNull Path sdkLocation, @NonNull Path avdHomeFolder) {
+            mSdkLocation = sdkLocation;
+            mAvdHomeFolder = avdHomeFolder;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mSdkLocation, mAvdHomeFolder);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof AvdManagerCacheKey)) {
+                return false;
+            }
+
+            AvdManagerCacheKey otherKey = (AvdManagerCacheKey) other;
+            return mSdkLocation.equals(otherKey.mSdkLocation)
+                    && mAvdHomeFolder.equals(otherKey.mAvdHomeFolder);
+        }
+    }
+
+    /**
+     * A map for caching AvdManagers based on the AvdHomeFolder and SdkHandler. This prevents us
+     * from creating multiple AvdManagers for the same SDK and AVD which could have them get out of
+     * sync.
+     */
+    private static final Map<AvdManagerCacheKey, WeakReference<AvdManager>> mManagers =
+            new HashMap<>();
 
     private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<>();
     private AvdInfo[] mValidAvdList;
@@ -445,7 +478,7 @@
     @Nullable
     public static AvdManager getInstance(
             @NonNull AndroidSdkHandler sdkHandler,
-            @NonNull File baseAvdFolder,
+            @NonNull File avdHomeFolder,
             @NonNull ILogger log)
             throws AndroidLocationsException {
         if (sdkHandler.getLocation() == null) {
@@ -454,20 +487,22 @@
         synchronized(mManagers) {
             AvdManager manager;
             FileOp fop = sdkHandler.getFileOp();
-            WeakReference<AvdManager> ref = mManagers.get(sdkHandler.getLocation().toString(), fop);
+            AvdManagerCacheKey key =
+                    new AvdManagerCacheKey(
+                            sdkHandler.getLocation(), avdHomeFolder.toPath().toAbsolutePath());
+            WeakReference<AvdManager> ref = mManagers.get(key);
             if (ref != null && (manager = ref.get()) != null) {
                 return manager;
             }
             try {
-                manager = new AvdManager(sdkHandler, baseAvdFolder, log, fop);
+                manager = new AvdManager(sdkHandler, avdHomeFolder, log, fop);
             } catch (AndroidLocationsException e) {
                 throw e;
-            }
-            catch (Exception e) {
+            } catch (Exception e) {
                 log.warning("Exception during AvdManager initialization: %1$s", e);
                 return null;
             }
-            mManagers.put(sdkHandler.getLocation().toString(), fop, new WeakReference<>(manager));
+            mManagers.put(key, new WeakReference<>(manager));
             return manager;
         }
     }
@@ -1537,7 +1572,7 @@
         }
 
         if (properties == null) {
-            properties = Maps.newHashMap();
+            properties = new HashMap<>();
         }
 
         if (!properties.containsKey(AVD_INI_ANDROID_API) &&
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerCacheKeyTest.java b/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerCacheKeyTest.java
new file mode 100644
index 0000000..5b16789
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerCacheKeyTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.sdklib.internal.avd;
+
+import static com.android.sdklib.internal.avd.AvdManager.AvdManagerCacheKey;
+
+import com.android.annotations.NonNull;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import com.google.common.testing.EqualsTester;
+import java.nio.file.FileSystem;
+import org.junit.Test;
+
+public class AvdManagerCacheKeyTest {
+    private final @NonNull FileSystem myFileSystem = Jimfs.newFileSystem(Configuration.unix());
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(
+                        new AvdManagerCacheKey(
+                                myFileSystem.getPath("/path/to/sdk"),
+                                myFileSystem.getPath("/path/to/avd")),
+                        new AvdManagerCacheKey(
+                                myFileSystem.getPath("/path/to/sdk"),
+                                myFileSystem.getPath("/path/to/avd")))
+                .addEqualityGroup(
+                        new AvdManagerCacheKey(
+                                myFileSystem.getPath("/path/to/other/sdk"),
+                                myFileSystem.getPath("/path/to/avd")))
+                .addEqualityGroup(
+                        new AvdManagerCacheKey(
+                                myFileSystem.getPath("/path/to/sdk"),
+                                myFileSystem.getPath("/path/to/other/avd")))
+                .addEqualityGroup(
+                        new AvdManagerCacheKey(
+                                myFileSystem.getPath("/path/to/other/sdk"),
+                                myFileSystem.getPath("/path/to/other/avd")))
+                .testEquals();
+    }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerTest.java b/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerTest.java
index 0bea14b..0c6b10c 100644
--- a/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/avd/AvdManagerTest.java
@@ -382,7 +382,7 @@
                         + mAvdFolder.toAbsolutePath()
                         + "/sdcard.img");
 
-        // Copy this AVD to an AVD with a different name and a slightly different configuration
+        // Copy this AVDa to an AVD with a different name and a slightly different configuration
         HashMap<String, String> newAvdConfig = new HashMap<>();
         newAvdConfig.put("testKey2", "newValue2");
 
diff --git a/third_party/BUILD b/third_party/BUILD
index 817a67e..c26991f 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -250,6 +250,21 @@
 
 # ATTENTION: This file is generated from tools/buildSrc/base/dependencies.properties, see top of this file.
 maven_java_library(
+    name = "com.google.guava_guava-testlib",
+    export_artifact = "//prebuilts/tools/common/m2/repository/com/google/guava/guava-testlib/25.1-jre:jar",
+    visibility = ["//visibility:public"],
+    exports = [
+        ":com.google.code.findbugs_jsr305",
+        ":com.google.errorprone_error_prone_annotations",
+        ":com.google.guava_guava",
+        ":com.google.j2objc_j2objc-annotations",
+        ":junit_junit",
+        ":org.checkerframework_checker-qual",
+    ],
+)
+
+# ATTENTION: This file is generated from tools/buildSrc/base/dependencies.properties, see top of this file.
+maven_java_library(
     name = "com.google.guava_listenablefuture",
     export_artifact = "//prebuilts/tools/common/m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava:jar",
     visibility = ["//visibility:public"],
@@ -894,7 +909,7 @@
 # ATTENTION: This file is generated from tools/buildSrc/base/dependencies.properties, see top of this file.
 maven_java_library(
     name = "net.bytebuddy_byte-buddy",
-    export_artifact = "//prebuilts/tools/common/m2/repository/net/bytebuddy/byte-buddy/1.9.10:jar",
+    export_artifact = "//prebuilts/tools/common/m2/repository/net/bytebuddy/byte-buddy/1.10.13:jar",
     visibility = ["//visibility:public"],
     exports = [],
 )
@@ -902,7 +917,7 @@
 # ATTENTION: This file is generated from tools/buildSrc/base/dependencies.properties, see top of this file.
 maven_java_library(
     name = "net.bytebuddy_byte-buddy-agent",
-    export_artifact = "//prebuilts/tools/common/m2/repository/net/bytebuddy/byte-buddy-agent/1.9.10:jar",
+    export_artifact = "//prebuilts/tools/common/m2/repository/net/bytebuddy/byte-buddy-agent/1.10.13:jar",
     visibility = ["//visibility:public"],
     exports = [],
 )