/**
   * Bitmaps and bitmap byte arrays are sometimes held by native gc roots, so they aren't included
   * in the retained size because their root dominator is a native gc root. To fix this, we check if
   * the leaking instance is a dominator for each bitmap instance and then add the bitmap size.
   *
   * <p>From experience, we've found that bitmap created in code (Bitmap.createBitmap()) are
   * correctly accounted for, however bitmaps set in layouts are not.
   */
  private int computeIgnoredBitmapRetainedSize(Snapshot snapshot, Instance leakingInstance) {
    int bitmapRetainedSize = 0;
    ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");

    for (Instance bitmapInstance : bitmapClass.getInstancesList()) {
      if (isIgnoredDominator(leakingInstance, bitmapInstance)) {
        ArrayInstance mBufferInstance = fieldValue(classInstanceValues(bitmapInstance), "mBuffer");
        // Native bitmaps have mBuffer set to null. We sadly can't account for them.
        if (mBufferInstance == null) {
          continue;
        }
        long bufferSize = mBufferInstance.getTotalRetainedSize();
        long bitmapSize = bitmapInstance.getTotalRetainedSize();
        // Sometimes the size of the buffer isn't accounted for in the bitmap retained size. Since
        // the buffer is large, it's easy to detect by checking for bitmap size < buffer size.
        if (bitmapSize < bufferSize) {
          bitmapSize += bufferSize;
        }
        bitmapRetainedSize += bitmapSize;
      }
    }
    return bitmapRetainedSize;
  }
  private LeakTraceElement buildLeakElement(LeakNode node) {
    if (node.parent == null) {
      // Ignore any root node.
      return null;
    }
    Instance holder = node.parent.instance;

    if (holder instanceof RootObj) {
      return null;
    }
    LeakTraceElement.Type type = node.referenceType;
    String referenceName = node.referenceName;

    LeakTraceElement.Holder holderType;
    String className;
    String extra = null;
    List<String> fields = new ArrayList<>();
    if (holder instanceof ClassObj) {
      ClassObj classObj = (ClassObj) holder;
      holderType = CLASS;
      className = classObj.getClassName();
      for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
        Field field = entry.getKey();
        Object value = entry.getValue();
        fields.add("static " + field.getName() + " = " + value);
      }
    } else if (holder instanceof ArrayInstance) {
      ArrayInstance arrayInstance = (ArrayInstance) holder;
      holderType = ARRAY;
      className = arrayInstance.getClassObj().getClassName();
      if (arrayInstance.getArrayType() == Type.OBJECT) {
        Object[] values = arrayInstance.getValues();
        for (int i = 0; i < values.length; i++) {
          fields.add("[" + i + "] = " + values[i]);
        }
      }
    } else {
      ClassInstance classInstance = (ClassInstance) holder;
      ClassObj classObj = holder.getClassObj();
      for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
        fields.add("static " + fieldToString(entry));
      }
      for (ClassInstance.FieldValue field : classInstance.getValues()) {
        fields.add(fieldToString(field));
      }
      className = classObj.getClassName();

      if (extendsThread(classObj)) {
        holderType = THREAD;
        String threadName = threadName(holder);
        extra = "(named '" + threadName + "')";
      } else if (className.matches(ANONYMOUS_CLASS_NAME_PATTERN)) {
        String parentClassName = classObj.getSuperClassObj().getClassName();
        if (Object.class.getName().equals(parentClassName)) {
          holderType = OBJECT;
          try {
            // This is an anonymous class implementing an interface. The API does not give access
            // to the interfaces implemented by the class. We check if it's in the class path and
            // use that instead.
            Class<?> actualClass = Class.forName(classObj.getClassName());
            Class<?>[] interfaces = actualClass.getInterfaces();
            if (interfaces.length > 0) {
              Class<?> implementedInterface = interfaces[0];
              extra = "(anonymous implementation of " + implementedInterface.getName() + ")";
            } else {
              extra = "(anonymous subclass of java.lang.Object)";
            }
          } catch (ClassNotFoundException ignored) {
          }
        } else {
          holderType = OBJECT;
          // Makes it easier to figure out which anonymous class we're looking at.
          extra = "(anonymous subclass of " + parentClassName + ")";
        }
      } else {
        holderType = OBJECT;
      }
    }
    return new LeakTraceElement(
        referenceName, type, holderType, className, extra, node.exclusion, fields);
  }