Annotate BinderProxy objects with their interface

Example: android.os.BinderProxy@1ae2d838 for android.app.job.IJobScheduler

Test: Added fake Binder stub and proxy classes, verified that a class ending with $Stub$Proxy and holding a reference to a BinderProxy object is marked as the BinderProxy interface
Bug:114363695
Change-Id: I19dd24cafbb4c022dda583408f02cc2fae362825
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index 7aa994a..c82b314 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -73,6 +73,7 @@
     method public com.android.ahat.heapdump.AhatInstance getAssociatedBitmapInstance();
     method public com.android.ahat.heapdump.AhatClassObj getAssociatedClassForOverhead();
     method public com.android.ahat.heapdump.AhatInstance getBaseline();
+    method public java.lang.String getBinderProxyInterfaceName();
     method public java.lang.String getClassName();
     method public com.android.ahat.heapdump.AhatClassObj getClassObj();
     method public java.lang.String getDexCacheLocation(int);
diff --git a/tools/ahat/src/main/com/android/ahat/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java
index ab88c04..877ecf4 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -112,6 +112,13 @@
       formatted.append(" overhead for ");
       formatted.append(summarize(cls));
     }
+
+    // Annotate BinderProxy with its interface name.
+    String binderInterface = inst.getBinderProxyInterfaceName();
+    if (binderInterface != null) {
+        formatted.appendFormat(" for %s", binderInterface);
+    }
+
     return formatted;
   }
 
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index 0511798..141bdd9 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -145,6 +145,21 @@
     return null;
   }
 
+  @Override public String getBinderProxyInterfaceName() {
+    if (isInstanceOfClass("android.os.BinderProxy")) {
+      for (AhatInstance inst : getReverseReferences()) {
+        String className = inst.getClassName();
+        if (className.endsWith("$Stub$Proxy")) {
+          Value value = inst.getField("mRemote");
+          if (value != null && value.asAhatInstance() == this) {
+            return className.substring(0, className.lastIndexOf("$Stub$Proxy"));
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   @Override public AhatInstance getAssociatedBitmapInstance() {
     return getBitmapInfo() == null ? null : this;
   }
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index c85a057..3aae11e 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -490,6 +490,17 @@
   }
 
   /**
+   * Returns the name of the Binder proxy interface associated with this object. Only applies to
+   * instances of android.os.BinderProxy. If this is an instance of BinderProxy,
+   * returns the fully qualified binder interface name, otherwise returns null.
+   *
+   * @return the name of the binder interface associated with this object
+   */
+  public String getBinderProxyInterfaceName() {
+    return null;
+  }
+
+  /**
    * Returns the android.graphics.Bitmap instance associated with this object.
    * Instances of android.graphics.Bitmap return themselves. If this is a
    * byte[] array containing pixel data for an instance of
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index 804a3a3..50b8878 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -124,6 +124,35 @@
     }
   }
 
+  private static class IDumpedManager {
+    public static class Stub {
+      public static class Proxy {
+        android.os.IBinder mRemote;
+        Proxy(android.os.IBinder binderProxy) {
+          mRemote = binderProxy;
+        }
+      }
+    }
+  }
+
+  private static class IBinderInterfaceImpostor {
+    public static class Stub {
+      public static class Proxy {
+        android.os.IBinder mFakeRemote = new android.os.BinderProxy();
+        Proxy(android.os.IBinder binderProxy) {
+          mFakeRemote = binderProxy;
+        }
+      }
+    }
+  }
+
+  private static class BinderProxyCarrier {
+    android.os.IBinder mRemote;
+    BinderProxyCarrier(android.os.IBinder binderProxy) {
+      mRemote = binderProxy;
+    }
+  }
+
   public String basicString = "hello, world";
   public String nonAscii = "Sigma (Ʃ) is not ASCII";
   public String embeddedZero = "embedded\0...";  // Non-ASCII for string compression purposes.
@@ -158,6 +187,12 @@
   public int[] modifiedArray;
   public Object objectAllocatedAtKnownSite;
   public Object objectAllocatedAtKnownSubSite;
+  public android.os.IBinder correctBinderProxy = new android.os.BinderProxy();
+  public android.os.IBinder imposedBinderProxy = new android.os.BinderProxy();
+  public android.os.IBinder carriedBinderProxy = new android.os.BinderProxy();
+  Object correctBinderProxyObject = new IDumpedManager.Stub.Proxy(correctBinderProxy);
+  Object impostorBinderProxyObject = new IBinderInterfaceImpostor.Stub.Proxy(imposedBinderProxy);
+  Object carrierBinderProxyObject = new BinderProxyCarrier(carriedBinderProxy);
 
   // Allocate those objects that we need to not be GC'd before taking the heap
   // dump.
diff --git a/tools/ahat/src/test-dump/android/os/BinderProxy.java b/tools/ahat/src/test-dump/android/os/BinderProxy.java
new file mode 100644
index 0000000..5f35c61
--- /dev/null
+++ b/tools/ahat/src/test-dump/android/os/BinderProxy.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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 android.os;
+
+/** Fake android.os.BinderProxy class that does absolutely nothing. */
+public class BinderProxy implements IBinder {}
diff --git a/tools/ahat/src/test-dump/android/os/IBinder.java b/tools/ahat/src/test-dump/android/os/IBinder.java
new file mode 100644
index 0000000..6f01468
--- /dev/null
+++ b/tools/ahat/src/test-dump/android/os/IBinder.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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 android.os;
+
+/** Fake android.os.IBinder that means nothing. */
+public interface IBinder {}
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 196eb1e..57aa31f 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -549,4 +549,18 @@
     // Other kinds of objects should not have associated classes for overhead.
     assertNull(cls.getAssociatedClassForOverhead());
   }
+
+  @Test
+  public void binderProxy() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance correctObj = dump.getDumpedAhatInstance("correctBinderProxy");
+    assertEquals("DumpedStuff$IDumpedManager", correctObj.getBinderProxyInterfaceName());
+
+    AhatInstance imposedObj = dump.getDumpedAhatInstance("imposedBinderProxy");
+    assertNull(imposedObj.getBinderProxyInterfaceName());
+
+    AhatInstance carriedObj = dump.getDumpedAhatInstance("carriedBinderProxy");
+    assertNull(carriedObj.getBinderProxyInterfaceName());
+  }
 }