Annotate binder services and tokens.

For all instances of android.os.Binder, try to annotate them by
retrieving their descriptor. If the instance is a also (sub)class of
'descriptor$Stub', mark it as a binder service. Otherwise, mark it as
a binder token.

Example:
android.server.wm.Session@1aa85368 binder service (android.view.IWindowSession)

Bug: 114365505
Test: ahat-tests (including new tests) passes, visual inspection
Change-Id: I7343467be5b745960c6e87c850c4125945de2f85
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index c82b314..01e00e9 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -74,6 +74,8 @@
     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 getBinderStubInterfaceName();
+    method public java.lang.String getBinderTokenDescriptor();
     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 877ecf4..df3b577 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -114,9 +114,20 @@
     }
 
     // Annotate BinderProxy with its interface name.
-    String binderInterface = inst.getBinderProxyInterfaceName();
-    if (binderInterface != null) {
-        formatted.appendFormat(" for %s", binderInterface);
+    String binderProxyInterface = inst.getBinderProxyInterfaceName();
+    if (binderProxyInterface != null) {
+      formatted.appendFormat(" for %s", binderProxyInterface);
+    }
+
+    // Annotate Binder tokens with their descriptor
+    String binderTokenDescriptor = inst.getBinderTokenDescriptor();
+    if (binderTokenDescriptor != null) {
+      formatted.appendFormat(" binder token (%s)", binderTokenDescriptor);
+    }
+    // Annotate Binder services with their interface name.
+    String binderStubInterface = inst.getBinderStubInterfaceName();
+    if (binderStubInterface != null) {
+      formatted.appendFormat(" binder service (%s)", binderStubInterface);
     }
 
     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 141bdd9..d2ba68d 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -160,6 +160,37 @@
     return null;
   }
 
+  @Override public String getBinderTokenDescriptor() {
+    String descriptor = getBinderDescriptor();
+    if (descriptor == null) {
+      return null;
+    }
+
+    if (isInstanceOfClass(descriptor + "$Stub")) {
+      // This is an instance of an auto-generated interface class, and
+      // therefore not a binder token.
+      return null;
+    }
+
+    return descriptor;
+  }
+
+  @Override public String getBinderStubInterfaceName() {
+    String descriptor = getBinderDescriptor();
+    if (descriptor == null || descriptor.isEmpty()) {
+      // Binder interface stubs always have a non-empty descriptor
+      return null;
+    }
+
+    // We only consider something a binder service if it's an instance of the
+    // auto-generated descriptor$Stub class.
+    if (isInstanceOfClass(descriptor + "$Stub")) {
+      return descriptor;
+    }
+
+    return null;
+  }
+
   @Override public AhatInstance getAssociatedBitmapInstance() {
     return getBitmapInfo() == null ? null : this;
   }
@@ -177,6 +208,25 @@
   }
 
   /**
+   * Returns the descriptor of an android.os.Binder object.
+   * If no descriptor is set, returns an empty string.
+   * If the object is not an android.os.Binder object, returns null.
+   */
+  private String getBinderDescriptor() {
+    if (isInstanceOfClass("android.os.Binder")) {
+      Value value = getField("mDescriptor");;
+
+      if (value == null) {
+        return "";
+      } else {
+        return value.asAhatInstance().asString();
+      }
+    } else {
+      return null;
+    }
+  }
+
+  /**
    * Read the given field from the given instance.
    * The field is assumed to be a byte[] field.
    * Returns null if the field value is null, not a byte[] or could not be read.
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 3aae11e..281c977 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -490,9 +490,10 @@
   }
 
   /**
-   * 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.
+   * 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
    */
@@ -501,6 +502,32 @@
   }
 
   /**
+   * Returns the descriptor of the Binder token associated with this object.
+   * Only applies to instances of android.os.Binder. If this is an instance of
+   * android.os.Binder with a subclass of the name "descriptor$Stub", the
+   * object in question is a binder stub, and this function will return null.
+   * In that case, @see AhatInstance#getBinderStubInterfaceName
+   *
+   * @return the descriptor of this object, if it's a binder token
+   */
+  public String getBinderTokenDescriptor() {
+    return null;
+  }
+
+  /**
+   * Returns the name of the Binder stub interface associated with this object.
+   * Only applies to instances which are a subclass of android.os.Binder,
+   * and are an instance of class 'descriptor$Stub', where descriptor
+   * is the descriptor of the android.os.Binder object.
+   *
+   * @return the name of the binder interface associated with this object,
+   *         or null if this is not a binder stub interface.
+   */
+  public String getBinderStubInterfaceName() {
+    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 50b8878..de2968f 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -124,9 +124,13 @@
     }
   }
 
-  private static class IDumpedManager {
-    public static class Stub {
-      public static class Proxy {
+  public interface IDumpedManager {
+    public static class Stub extends android.os.Binder implements IDumpedManager {
+      private static final java.lang.String DESCRIPTOR = "DumpedStuff$IDumpedManager";
+      public Stub() {
+        super(DESCRIPTOR);
+      }
+      public static class Proxy implements IDumpedManager {
         android.os.IBinder mRemote;
         Proxy(android.os.IBinder binderProxy) {
           mRemote = binderProxy;
@@ -135,9 +139,9 @@
     }
   }
 
-  private static class IBinderInterfaceImpostor {
+  public interface IBinderInterfaceImpostor {
     public static class Stub {
-      public static class Proxy {
+      public static class Proxy implements IBinderInterfaceImpostor {
         android.os.IBinder mFakeRemote = new android.os.BinderProxy();
         Proxy(android.os.IBinder binderProxy) {
           mFakeRemote = binderProxy;
@@ -153,6 +157,14 @@
     }
   }
 
+  private static class BinderService extends IDumpedManager.Stub {
+    // Intentionally empty
+  };
+
+  private static class FakeBinderService extends IBinderInterfaceImpostor.Stub {
+    // Intentionally empty
+  };
+
   public String basicString = "hello, world";
   public String nonAscii = "Sigma (Ʃ) is not ASCII";
   public String embeddedZero = "embedded\0...";  // Non-ASCII for string compression purposes.
@@ -194,6 +206,11 @@
   Object impostorBinderProxyObject = new IBinderInterfaceImpostor.Stub.Proxy(imposedBinderProxy);
   Object carrierBinderProxyObject = new BinderProxyCarrier(carriedBinderProxy);
 
+  Object binderService = new BinderService();
+  Object fakeBinderService = new FakeBinderService();
+  Object binderToken = new android.os.Binder();
+  Object namedBinderToken = new android.os.Binder("awesomeToken");
+
   // Allocate those objects that we need to not be GC'd before taking the heap
   // dump.
   public void shouldNotGc() {
diff --git a/tools/ahat/src/test-dump/android/os/Binder.java b/tools/ahat/src/test-dump/android/os/Binder.java
new file mode 100644
index 0000000..e89bb74
--- /dev/null
+++ b/tools/ahat/src/test-dump/android/os/Binder.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import java.lang.String;
+
+/** Fake android.os.Binder class that just holds a descriptor.
+ *
+ * Note that having this class will cause Proguard to issue warnings when
+ * building ahat-test-dump with 'mm' or 'mma':
+ *
+ * Warning: Library class android.net.wifi.rtt.IWifiRttManager$Stub extends
+ * program class android.os.Binder
+ *
+ * This is because when building for the device, proguard will include the
+ * framework jars, which contain Stub classes that extend android.os.Binder,
+ * which is defined again here.
+ *
+ * Since we don't actually run this code on the device, these warnings can
+ * be ignored.
+ */
+public class Binder implements IBinder {
+  public Binder() {
+    mDescriptor = null;
+  }
+
+  public Binder(String descriptor) {
+    mDescriptor = descriptor;
+  }
+
+  private String mDescriptor;
+}
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 57aa31f..af0a73b 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -563,4 +563,46 @@
     AhatInstance carriedObj = dump.getDumpedAhatInstance("carriedBinderProxy");
     assertNull(carriedObj.getBinderProxyInterfaceName());
   }
+
+  @Test
+  public void binderToken() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    // Tokens without a descriptor return an empty string
+    AhatInstance binderToken = dump.getDumpedAhatInstance("binderToken");
+    assertEquals("", binderToken.getBinderTokenDescriptor());
+
+    // Named binder tokens return their descriptor
+    AhatInstance namedBinderToken = dump.getDumpedAhatInstance("namedBinderToken");
+    assertEquals("awesomeToken", namedBinderToken.getBinderTokenDescriptor());
+
+    // Binder stubs aren't considered binder tokens
+    AhatInstance binderService = dump.getDumpedAhatInstance("binderService");
+    assertEquals(null, binderService.getBinderTokenDescriptor());
+  }
+
+  @Test
+  public void binderStub() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    // Regular binder service returns the interface name and no token descriptor
+    AhatInstance binderService = dump.getDumpedAhatInstance("binderService");
+    assertEquals("DumpedStuff$IDumpedManager", binderService.getBinderStubInterfaceName());
+
+    // Binder tokens aren't considered binder services
+    AhatInstance binderToken = dump.getDumpedAhatInstance("binderToken");
+    assertEquals(null, binderToken.getBinderStubInterfaceName());
+
+    // Named binder tokens aren't considered binder services
+    AhatInstance namedBinderToken = dump.getDumpedAhatInstance("namedBinderToken");
+    assertEquals(null, namedBinderToken.getBinderStubInterfaceName());
+
+    // Fake service returns null
+    AhatInstance fakeService = dump.getDumpedAhatInstance("fakeBinderService");
+    assertNull(fakeService.getBinderStubInterfaceName());
+
+    // Random non-binder object returns null
+    AhatInstance nonBinderObject = dump.getDumpedAhatInstance("anObject");
+    assertNull(nonBinderObject.getBinderStubInterfaceName());
+  }
 }