Merge "guessPath not to depend on the first level of app directory"
diff --git a/README.version b/README.version
index 4de4834..3e8229a 100644
--- a/README.version
+++ b/README.version
@@ -17,3 +17,4 @@
         Exclude Stress#mockALot from presubmit (Ic9a2927ffa07924bd759429e31c56dc1b71a826c)
         Extend timeout of Stress#mockALot() for CTS. (Iad30a8cb07b38054b490b7006d11908fc752a024)
         Update to Mockito 2.25.0 and impl InlineMockMaker (29a8674036d345e4ec8635b1d38d8b2a4fe91980a, need upstreaming)
+	guessPath not to depend on the first level of app directory (I66f1d7036949c2f05e6d37bc270d47f8e77e51c1, need upstreaming)
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
index b2128d7..b222f36 100644
--- a/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
@@ -32,26 +32,28 @@
 public final class AppDataDirGuesserTest {
     @Test
     public void testGuessCacheDir_SimpleExample() {
-        guessCacheDirFor("/data/app/a.b.c.apk").shouldGive("/data/data/a.b.c/cache");
-        guessCacheDirFor("/data/app/a.b.c.tests.apk").shouldGive("/data/data/a.b.c.tests/cache");
+        guessCacheDirFor("/data/app/a.b.c-xxx/base.apk")
+                .shouldGive("/data/data/a.b.c/cache");
+        guessCacheDirFor("/data/app/a.b.c.tests-xxx/base.apk")
+                .shouldGive("/data/data/a.b.c.tests/cache");
     }
 
     @Test
     public void testGuessCacheDir_MultipleResultsSeparatedByColon() {
-        guessCacheDirFor("/data/app/a.b.c.apk:/data/app/d.e.f.apk")
+        guessCacheDirFor("/data/app/a.b.c-xxx/base.apk:/data/app/d.e.f-xxx/base.apk")
                 .shouldGive("/data/data/a.b.c/cache", "/data/data/d.e.f/cache");
     }
 
     @Test
     public void testGuessCacheDir_NotWriteableSkipped() {
-        guessCacheDirFor("/data/app/a.b.c.apk:/data/app/d.e.f.apk")
+        guessCacheDirFor("/data/app/a.b.c-xxx/base.apk:/data/app/d.e.f-xxx/base.apk")
                 .withNonWriteable("/data/data/a.b.c/cache")
                 .shouldGive("/data/data/d.e.f/cache");
     }
 
     @Test
     public void testGuessCacheDir_ForSecondaryUser() {
-        guessCacheDirFor("/data/app/a.b.c.apk:/data/app/d.e.f.apk")
+        guessCacheDirFor("/data/app/a.b.c-xxx/base.apk:/data/app/d.e.f-xxx/base.apk")
                 .withNonWriteable("/data/data/a.b.c", "/data/data/d.e.f")
                 .withProcessUid(1110009)
                 .shouldGive("/data/user/11/a.b.c/cache", "/data/user/11/d.e.f/cache");
@@ -59,13 +61,16 @@
 
     @Test
     public void testGuessCacheDir_StripHyphenatedSuffixes() {
-        guessCacheDirFor("/data/app/a.b.c-2.apk").shouldGive("/data/data/a.b.c/cache");
+        guessCacheDirFor("/data/app/a.b.c-2/base.apk")
+                .shouldGive("/data/data/a.b.c/cache");
     }
 
     @Test
     public void testGuessCacheDir_LeadingAndTrailingColonsIgnored() {
-        guessCacheDirFor("/data/app/a.b.c.apk:asdf:").shouldGive("/data/data/a.b.c/cache");
-        guessCacheDirFor(":asdf:/data/app/a.b.c.apk").shouldGive("/data/data/a.b.c/cache");
+        guessCacheDirFor("/data/app/a.b.c-xxx/base.apk:asdf:")
+                .shouldGive("/data/data/a.b.c/cache");
+        guessCacheDirFor(":asdf:/data/app/a.b.c-xxx/base.apk")
+                .shouldGive("/data/data/a.b.c/cache");
     }
 
     @Test
@@ -82,8 +87,38 @@
     @Test
     public void testGuessCacheDir_RealWorldExample() {
         String realPath = "/system/framework/android.test.runner.jar:" +
-                "/data/app/com.google.android.voicesearch.tests-2.apk:" +
-                "/data/app/com.google.android.voicesearch-1.apk";
+                "/data/app/com.google.android.voicesearch.tests-2/base.apk:" +
+                "/data/app/com.google.android.voicesearch-1/base.apk";
+        guessCacheDirFor(realPath)
+                .withNonWriteable("/data/data/com.google.android.voicesearch.tests/cache")
+                .shouldGive("/data/data/com.google.android.voicesearch/cache");
+    }
+
+    @Test
+    public void testGuessCacheDir_RealWorldExampleWithOneLevelSubDirectories() {
+        String realPath = "/system/framework/android.test.runner.jar:" +
+                "/data/app/com.google.android.voicesearch.tests-abcde/base.apk:" +
+                "/data/app/com.google.android.voicesearch-fghij/base.apk";
+        guessCacheDirFor(realPath)
+                .withNonWriteable("/data/data/com.google.android.voicesearch.tests/cache")
+                .shouldGive("/data/data/com.google.android.voicesearch/cache");
+    }
+
+    @Test
+    public void testGuessCacheDir_RealWorldExampleWithTwoLevelSubDirectories() {
+        String realPath = "/system/framework/android.test.runner.jar:" +
+                "/data/app/abcde/com.google.android.voicesearch.tests-fghij/base.apk:" +
+                "/data/app/klmno/com.google.android.voicesearch-pqrst/base.apk";
+        guessCacheDirFor(realPath)
+                .withNonWriteable("/data/data/com.google.android.voicesearch.tests/cache")
+                .shouldGive("/data/data/com.google.android.voicesearch/cache");
+    }
+
+    @Test
+    public void testGuessCacheDir_RealWorldExampleWithHyphensInPath() {
+        String realPath = "/system/framework/android.test.runner.jar:" +
+                "/data/app/a-b-c/com.google.android.voicesearch.tests-e-f-g/base.apk:" +
+                "/data/app/com.google.android.voicesearch-k-l-n/base.apk";
         guessCacheDirFor(realPath)
                 .withNonWriteable("/data/data/com.google.android.voicesearch.tests/cache")
                 .shouldGive("/data/data/com.google.android.voicesearch/cache");
diff --git a/dexmaker/src/main/java/com/android/dx/AppDataDirGuesser.java b/dexmaker/src/main/java/com/android/dx/AppDataDirGuesser.java
index cc1d803..db4d76d 100644
--- a/dexmaker/src/main/java/com/android/dx/AppDataDirGuesser.java
+++ b/dexmaker/src/main/java/com/android/dx/AppDataDirGuesser.java
@@ -136,20 +136,31 @@
 
     File[] guessPath(String input) {
         List<File> results = new ArrayList<>();
+        String apkPathRoot = "/data/app/";
         for (String potential : splitPathList(input)) {
-            if (!potential.startsWith("/data/app/")) {
+            if (!potential.startsWith(apkPathRoot)) {
                 continue;
             }
-            int start = "/data/app/".length();
             int end = potential.lastIndexOf(".apk");
             if (end != potential.length() - 4) {
                 continue;
             }
-            int dash = potential.indexOf("-");
-            if (dash != -1) {
-                end = dash;
+            int endSlash = potential.lastIndexOf("/", end);
+            if (endSlash == apkPathRoot.length() - 1) {
+                // Apks cannot be directly under /data/app
+                continue;
             }
-            String packageName = potential.substring(start, end);
+            int startSlash = potential.lastIndexOf("/", endSlash - 1);
+            if (startSlash == -1) {
+                continue;
+            }
+            // Look for the first dash after the package name
+            int dash = potential.indexOf("-", startSlash);
+            if (dash == -1) {
+                continue;
+            }
+            end = dash;
+            String packageName = potential.substring(startSlash + 1, end);
             File dataDir = getWriteableDirectory("/data/data/" + packageName);
 
             if (dataDir == null) {