Refactor Inspector API

InspectionHelper has been renamed to InspectionCompanion for clarity
about its lifecycle. The APIs needed to be accessed from the context of
an inspection agent injected by the profiler have been made public.

With a tightend focus on platfrom views and XML resource IDs, the
@InspectableProperty annotation now has affordances for specifying an
attribute resource ID and for defining @IntDef flag and enum mappings.
@InspectableChildren has been removed, as this will be special cased in
the injected inspector. Additionally, support for attribute ID is now
provided in all PropertyMapper methods.

Additionally, PropertyMapper and PropertyReader now have support for
Gravity ints, @ColorInt, @ColorLong, and Color objects.

Test: mmma frameworks/base
Bug: 120224687
Change-Id: If455e2d1d9693eac39c33fc35f892baf75671ba4
diff --git a/Android.bp b/Android.bp
index f40aab1..0d4cb07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -777,9 +777,11 @@
 java_library_host {
     name: "inspector-annotation",
     srcs: [
-        "core/java/android/view/inspector/InspectableChildren.java",
         "core/java/android/view/inspector/InspectableNodeName.java",
         "core/java/android/view/inspector/InspectableProperty.java",
+        // Needed for the ResourceId.ID_NULL constant
+        "core/java/android/content/res/ResourceId.java",
+        "core/java/android/annotation/AnyRes.java",
     ],
 }
 
diff --git a/api/current.txt b/api/current.txt
index abd3c31..7c8d841 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -51842,6 +51842,86 @@
 
 }
 
+package android.view.inspector {
+
+  public abstract interface InspectionCompanion<T> {
+    method public default java.lang.String getNodeName();
+    method public abstract void mapProperties(android.view.inspector.PropertyMapper);
+    method public abstract void readProperties(T, android.view.inspector.PropertyReader);
+  }
+
+  public static class InspectionCompanion.UninitializedPropertyMapException extends java.lang.RuntimeException {
+    ctor public InspectionCompanion.UninitializedPropertyMapException();
+  }
+
+  public final class IntEnumMapping {
+    method public java.lang.String nameOf(int);
+  }
+
+  public static final class IntEnumMapping.Builder {
+    ctor public IntEnumMapping.Builder();
+    method public android.view.inspector.IntEnumMapping.Builder addValue(java.lang.String, int);
+    method public android.view.inspector.IntEnumMapping build();
+    method public void clear();
+  }
+
+  public final class IntFlagMapping {
+    method public java.lang.String[] namesOf(int);
+  }
+
+  public static final class IntFlagMapping.Builder {
+    ctor public IntFlagMapping.Builder();
+    method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int);
+    method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int, int);
+    method public android.view.inspector.IntFlagMapping build();
+    method public void clear();
+  }
+
+  public abstract interface PropertyMapper {
+    method public abstract int mapBoolean(java.lang.String, int);
+    method public abstract int mapByte(java.lang.String, int);
+    method public abstract int mapChar(java.lang.String, int);
+    method public abstract int mapColor(java.lang.String, int);
+    method public abstract int mapDouble(java.lang.String, int);
+    method public abstract int mapFloat(java.lang.String, int);
+    method public abstract int mapGravity(java.lang.String, int);
+    method public abstract int mapInt(java.lang.String, int);
+    method public abstract int mapIntEnum(java.lang.String, int, android.view.inspector.IntEnumMapping);
+    method public abstract int mapIntFlag(java.lang.String, int, android.view.inspector.IntFlagMapping);
+    method public abstract int mapLong(java.lang.String, int);
+    method public abstract int mapObject(java.lang.String, int);
+    method public abstract int mapShort(java.lang.String, int);
+  }
+
+  public static class PropertyMapper.PropertyConflictException extends java.lang.RuntimeException {
+    ctor public PropertyMapper.PropertyConflictException(java.lang.String, java.lang.String, java.lang.String);
+  }
+
+  public abstract interface PropertyReader {
+    method public abstract void readBoolean(int, boolean);
+    method public abstract void readByte(int, byte);
+    method public abstract void readChar(int, char);
+    method public abstract void readColor(int, int);
+    method public abstract void readColor(int, long);
+    method public abstract void readColor(int, android.graphics.Color);
+    method public abstract void readDouble(int, double);
+    method public abstract void readFloat(int, float);
+    method public abstract void readGravity(int, int);
+    method public abstract void readInt(int, int);
+    method public abstract void readIntEnum(int, int);
+    method public abstract void readIntFlag(int, int);
+    method public abstract void readLong(int, long);
+    method public abstract void readObject(int, java.lang.Object);
+    method public abstract void readShort(int, short);
+  }
+
+  public static class PropertyReader.PropertyTypeMismatchException extends java.lang.RuntimeException {
+    ctor public PropertyReader.PropertyTypeMismatchException(int, java.lang.String, java.lang.String, java.lang.String);
+    ctor public PropertyReader.PropertyTypeMismatchException(int, java.lang.String, java.lang.String);
+  }
+
+}
+
 package android.view.intelligence {
 
   public final class IntelligenceManager {
diff --git a/core/java/android/view/inspector/ChildTraverser.java b/core/java/android/view/inspector/ChildTraverser.java
deleted file mode 100644
index b775de5..0000000
--- a/core/java/android/view/inspector/ChildTraverser.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.view.inspector;
-
-import android.annotation.NonNull;
-
-/**
- * Interface for visiting all the child nodes of an inspectable object.
- *
- * Inspectable objects may return a collection of children as an array, an {@link Iterable} or an
- * {@link java.util.Iterator}. This provides a unified API for traversing across all the children
- * of an inspectable node.
- *
- * This interface is consumed by {@link InspectionHelper#traverseChildren(Object, ChildTraverser)}
- * and may be implemented as a lambda.
- *
- * @see InspectionHelper#traverseChildren(Object, ChildTraverser)
- * @hide
- */
-@FunctionalInterface
-public interface ChildTraverser {
-    /**
-     * Visit one child object of a parent inspectable object.
-     *
-     * The iteration interface will filter null values out before passing them to this method, but
-     * some child objects may not be inspectable. It is up to the implementor to determine their
-     * inspectablity and what to do with them.
-     *
-     * @param child A child object, guaranteed not to be null.
-     */
-    void traverseChild(@NonNull Object child);
-}
diff --git a/core/java/android/view/inspector/InspectableChildren.java b/core/java/android/view/inspector/InspectableChildren.java
deleted file mode 100644
index de8fa29..0000000
--- a/core/java/android/view/inspector/InspectableChildren.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.view.inspector;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Marks a getter for an inspectable node's inspectable children.
- *
- * This annotation can be applied to any getter that returns a collection of objects, either an
- * array, an {@link Iterable} or a {@link java.util.Iterator}. The getter may return null, which
- * will be treated as an empty collection. Additionally, the inspector will discard any null
- * entries in the collection.
- *
- * By default, this annotation is inherited. At runtime, the inspector introspects on the class
- * hierachy and uses the annotated getter from the bottommost class, if different from any
- * annoated getters of the parent class. If a class inherits from a parent class with an annotated
- * getter, but does not include this annotation, the child class will be traversed using the
- * getter annotated on the parent. This holds true even if the child class overrides the getter.
- *
- * @see InspectionHelper#traverseChildren(Object, ChildTraverser)
- * @see InspectionHelper#hasChildTraversal()
- * @hide
- */
-@Target({METHOD})
-@Retention(SOURCE)
-public @interface InspectableChildren {
-}
diff --git a/core/java/android/view/inspector/InspectableNodeName.java b/core/java/android/view/inspector/InspectableNodeName.java
index 716409c..ea94ad4 100644
--- a/core/java/android/view/inspector/InspectableNodeName.java
+++ b/core/java/android/view/inspector/InspectableNodeName.java
@@ -34,7 +34,7 @@
  * This annotation does not inherit. If a class extends an annotated parent class, but does not
  * annotate itself, its node name will be inferred from its Java name.
  *
- * @see InspectionHelper#getNodeName()
+ * @see InspectionCompanion#getNodeName()
  * @hide
  */
 @Target({TYPE})
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index b0fd503..5b957156 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -17,8 +17,11 @@
 package android.view.inspector;
 
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.content.res.ResourceId;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
@@ -31,8 +34,8 @@
  * but on a different getter, the inspector will use the child's getter when inspecting instances
  * of the child, and the parent's otherwise.
  *
- * @see InspectionHelper#mapProperties(PropertyMapper)
- * @see InspectionHelper#readProperties(Object, PropertyReader)
+ * @see InspectionCompanion#mapProperties(PropertyMapper)
+ * @see InspectionCompanion#readProperties(Object, PropertyReader)
  * @hide
  */
 @Target({METHOD})
@@ -46,5 +49,171 @@
      *
      * @return The name of the property.
      */
-    String value() default "";
+    String name() default "";
+
+    /**
+     * If the property is inflated from XML, the resource ID of its XML attribute.
+     *
+     * If left as {ID_NULL}, and {@link #hasAttributeId()} is true, the attribute ID will be
+     * inferred from {@link #name()}.
+     *
+     * @return The attribute ID of the property or {@link ResourceId#ID_NULL}
+     */
+    int attributeId() default ResourceId.ID_NULL;
+
+    /**
+     * If this property has an attribute ID.
+     *
+     * Set to false if the annotated property does not have an attribute ID, that is, it is not
+     * inflated from an XML attribute. This will prevent the automatic inference of the attribute
+     * ID if {@link #attributeId()} is set to {@link ResourceId#ID_NULL}.
+     *
+     * @return Whether to infer an attribute ID if not supplied
+     */
+    boolean hasAttributeId() default true;
+
+    /**
+     * Specify how to interpret a value type packed into a primitive integer.
+     *
+     * @return A {@link ValueType}
+     */
+    ValueType valueType() default ValueType.INFERRED;
+
+    /**
+     * For enumerations packed into primitive {int} properties, map the values to string names.
+     *
+     * Note that {@link #enumMapping()} cannot be used simultaneously with {@link #flagMapping()}.
+     *
+     * @return An array of {@link EnumMap}, empty if not applicable
+     * @see android.annotation.IntDef
+     * @see IntEnumMapping
+     */
+    EnumMap[] enumMapping() default {};
+
+    /**
+     * For flags packed into primitive {int} properties, model the string names of the flags.
+     *
+     * Note that {@link #flagMapping()} cannot be used simultaneously with {@link #enumMapping()}.
+     *
+     * @return An array of {@link FlagMap}, empty if not applicable
+     * @see android.annotation.IntDef
+     * @see IntFlagMapping
+     */
+    FlagMap[] flagMapping() default {};
+
+
+    /**
+     * One entry in an enumeration packed into a primitive {int}.
+     *
+     * @see IntEnumMapping
+     * @hide
+     */
+    @Target({TYPE})
+    @Retention(SOURCE)
+    @interface EnumMap {
+        /**
+         * The string name of this enumeration value.
+         *
+         * @return A string name
+         */
+        String name();
+
+        /**
+         * The integer value of this enumeration value.
+         *
+         * @return An integer value
+         */
+        int value();
+    }
+
+    /**
+     * One flag value of many that may be packed into a primitive {int}.
+     *
+     * @see IntFlagMapping
+     * @hide
+     */
+    @Target({TYPE})
+    @Retention(SOURCE)
+    @interface FlagMap {
+        /**
+         * The string name of this flag.
+         *
+         * @return A string name
+         */
+        String name();
+
+        /**
+         * A target value that the property's value must equal after masking.
+         *
+         * If a mask is not supplied (i.e., {@link #mask()} is 0), the target will be reused as the
+         * mask. This handles the common case where no flags mutually exclude each other.
+         *
+         * @return The target value to compare against
+         */
+        int target();
+
+        /**
+         * A mask that the property will be bitwise anded with before comparing to the target.
+         *
+         * If set to 0 (the default), the value of {@link #target()} will be used as a mask. Zero
+         * was chosen as the default since bitwise and with zero is always zero.
+         *
+         * @return A mask, or 0 to use the target as a mask
+         */
+        int mask() default 0;
+    }
+
+    /**
+     * The type of value packed into a primitive {int}.
+     *
+     * @hide
+     */
+    enum ValueType {
+        /**
+         * No special handling, property is considered to be a numeric value.
+         */
+        NONE,
+
+        /**
+         * The default the annotation processor infers the value type from context.
+         */
+        INFERRED,
+
+        /**
+         * Value packs an enumeration.
+         *
+         * This is inferred if {@link #enumMapping()} is specified.
+         *
+         * @see EnumMap
+         */
+        INT_ENUM,
+
+        /**
+         * Value packs flags, of which many may be enabled at once.
+         *
+         * This is inferred if {@link #flagMapping()} is specified.
+         *
+         * @see FlagMap
+         */
+        INT_FLAG,
+
+        /**
+         * Value packs color information.
+         *
+         * This is inferred from {@link android.annotation.ColorInt}, or
+         * {@link android.annotation.ColorLong} on the getter method.
+         *
+         * @see android.graphics.Color
+         */
+        COLOR,
+
+        /**
+         * Value packs gravity information.
+         *
+         * This type is not inferred, and is non-trivial to represent using {@link FlagMap}.
+         *
+         * @see android.view.Gravity
+         */
+        GRAVITY
+    }
 }
diff --git a/core/java/android/view/inspector/InspectionCompanion.java b/core/java/android/view/inspector/InspectionCompanion.java
new file mode 100644
index 0000000..ce0aee8
--- /dev/null
+++ b/core/java/android/view/inspector/InspectionCompanion.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An interface for companion objects used to inspect views.
+ *
+ * Inspection companions only need to handle the properties and node name of the specific class
+ * they are defined for, not anything from a parent class. At runtime, the inspector instantiates
+ * one instance of each inspection companion, and handles visiting them in the correct inheritance
+ * order for each type it inspects.
+ *
+ * Properties are read from the top of the type tree to the bottom, so that classes that override
+ * a property in their parent class can overwrite it in the reader. In general, properties will
+ * cleanly inherit through their getters, and the inspector runtime will read the properties of a
+ * parent class via the parent's inspection companion, and the child companion will only read
+ * properties added or changed since the parent was defined.
+ *
+ * Only one child traversal is considered for each class. If a descendant class defines a
+ * different child traversal than its parent, only the bottom traversal is used. If a class does
+ * not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's
+ * traversal will be used.
+ *
+ * @param <T> The type of inspectable this is the companion to
+ */
+public interface InspectionCompanion<T> {
+    /**
+     * Map the string names of the properties this companion knows about to integer IDs.
+     *
+     * Each companion is responsible for storing the integer IDs of all its properties. This is the
+     * only method that is allowed to modify the stored IDs.
+     *
+     * Calling {@link #readProperties(T, PropertyReader)} before calling this results in
+     * undefined behavior.
+     *
+     * @param propertyMapper A {@link PropertyMapper} maps string names to IDs.
+     */
+    void mapProperties(@NonNull PropertyMapper propertyMapper);
+
+    /**
+     * Read the values of an instance of this companion's type into a {@link PropertyReader}.
+     *
+     * This method needs to return the property IDs stored by
+     * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties
+     * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is
+     * called before {mapProperties}.
+     *
+     * @param inspectable A object of type {@link T} to read the properties of.
+     * @param propertyReader An object which receives the property IDs and values.
+     */
+    void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader);
+
+    /**
+     * Get an optional name to display to developers for inspection nodes of this companion's type.
+     *
+     * The default implementation returns null, which will cause the runtime to use the class's
+     * simple name as defined by {@link Class#getSimpleName()} as the node name.
+     *
+     * If the type of this companion is inflated from XML, this method should be overridden to
+     * return the string used as the tag name for this type in XML.
+     *
+     * @return A string to use as the node name, or null to use the simple class name fallback.
+     */
+    @Nullable
+    default String getNodeName() {
+        return null;
+    }
+
+    /**
+     * Thrown by {@link #readProperties(Object, PropertyReader)} if called before
+     * {@link #mapProperties(PropertyMapper)}.
+     */
+    class UninitializedPropertyMapException extends RuntimeException {
+        public UninitializedPropertyMapException() {
+            super("Unable to read properties of an inspectable before mapping their IDs.");
+        }
+    }
+}
diff --git a/core/java/android/view/inspector/InspectionHelper.java b/core/java/android/view/inspector/InspectionHelper.java
deleted file mode 100644
index 27a9704..0000000
--- a/core/java/android/view/inspector/InspectionHelper.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 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.view.inspector;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-/**
- * An interface for companion objects used to inspect views.
- *
- * Inspection helpers only need to handle the properties, name and traversal of the specific class
- * they are defined for, not anything from a parent class. At runtime, the inspector instantiates
- * one instance of each inspection helper, and handles visiting them in the correct inheritance
- * order for each type it inspects.
- *
- * Properties are read from the top of the type tree to the bottom, so that classes that override
- * a property in their parent class can overwrite it in the reader. In general, properties will
- * cleanly inherit through their getters, and the inspector runtime will read the properties of a
- * parent class via the parent's inspection helper, and the child helper will only read properties
- * added or changed since the parent was defined.
- *
- * Only one child traversal is considered for each class. If a descendant class defines a
- * different child traversal than its parent, only the bottom traversal is used. If a class does
- * not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's
- * traversal will be used.
- *
- * @param <T> The type of inspectable this helper operates on
- * @hide
- */
-public interface InspectionHelper<T> {
-    /**
-     * Map the string names of the properties this helper knows about to integer IDs.
-     *
-     * Each helper is responsible for storing the integer IDs of all its properties. This is the
-     * only method that is allowed to modify the stored IDs.
-     *
-     * Calling {@link #readProperties(T, PropertyReader)} before calling this results in
-     * undefined behavior.
-     *
-     * @param propertyMapper A {@link PropertyMapper} or lambda which maps string names to IDs.
-     */
-    void mapProperties(@NonNull PropertyMapper propertyMapper);
-
-    /**
-     * Read the values of an instance of this helper's type into a {@link PropertyReader}.
-     *
-     * This method needs to return the property IDs stored by
-     * {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties
-     * have been mapped and throw a {@link UninitializedPropertyMapException} if this method is
-     * called before {mapProperties}.
-     *
-     * @param inspectable A object of type {@link T} to read the properties of.
-     * @param propertyReader An object which receives the property IDs and values.
-     */
-    void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader);
-
-    /**
-     * Query if this inspectable type can potentially have child nodes.
-     *
-     * E.g.: any descendant of {@link android.view.ViewGroup} can have child nodes, but a leaf
-     * view like {@link android.widget.ImageView} may not.
-     *
-     * The default implementation always returns false. If an implementing class overrides this, it
-     * should also define {@link #traverseChildren(T, ChildTraverser)}.
-     *
-     * @return True if this inspectable type can potentially have child nodes, false otherwise.
-     */
-    default boolean hasChildTraversal() {
-        return false;
-    }
-
-    /**
-     * Traverse the child nodes of an instance of this helper's type into a {@link ChildTraverser}.
-     *
-     * This provides the ability to traverse over a variety of collection APIs (e.g.: arrays,
-     * {@link Iterable}, or {@link java.util.Iterator}) in a uniform fashion. The traversal must be
-     * in the order defined by this helper's type. If the getter returns null, the helper must
-     * treat it as an empty collection.
-     *
-     * The default implementation throws a {@link NoChildTraversalException}. If
-     * {@link #hasChildTraversal()} returns is overriden to return true, it is expected that the
-     * implementing class will also override this method and provide a traversal.
-     *
-     * @param inspectable An object of type {@link T} to traverse the child nodes of.
-     * @param childTraverser A {@link ChildTraverser} or lamba to receive the children in order.
-     * @throws NoChildTraversalException If there is no defined child traversal
-     */
-    default void traverseChildren(
-            @NonNull T inspectable,
-            @SuppressWarnings("unused") @NonNull ChildTraverser childTraverser) {
-        throw new NoChildTraversalException(inspectable.getClass());
-    }
-
-    /**
-     * Get an optional name to display to developers for inspection nodes of this helper's type.
-     *
-     * The default implementation returns null, which will cause the runtime to use the class's
-     * simple name as defined by {@link Class#getSimpleName()} as the node name.
-     *
-     * If the type of this helper is inflated from XML, this method should be overridden to return
-     * the string used as the tag name for this type in XML.
-     *
-     * @return A string to use as the node name, or null to use the simple class name fallback.
-     */
-    @Nullable
-    default String getNodeName() {
-        return null;
-    }
-
-    /**
-     * Thrown by {@link #readProperties(Object, PropertyReader)} if called before
-     * {@link #mapProperties(PropertyMapper)}.
-     */
-    class UninitializedPropertyMapException extends RuntimeException {
-        public UninitializedPropertyMapException() {
-            super("Unable to read properties of an inspectable before mapping their IDs.");
-        }
-    }
-
-    /**
-     * Thrown by {@link #traverseChildren(Object, ChildTraverser)} if no child traversal exists.
-     */
-    class NoChildTraversalException extends RuntimeException {
-        public NoChildTraversalException(Class cls) {
-            super(String.format(
-                    "Class %s does not have a defined child traversal. Cannot traverse children.",
-                    cls.getCanonicalName()
-            ));
-        }
-    }
-}
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
new file mode 100644
index 0000000..69f6dce
--- /dev/null
+++ b/core/java/android/view/inspector/IntEnumMapping.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Maps the values of an {int} property to string names for properties that encode enumerations.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for enumerations packed into primitive {int} properties.
+ *
+ * This class is immutable, and must be constructed by a {@link Builder}.
+ *
+ * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
+ */
+public final class IntEnumMapping {
+    private final Value[] mValues;
+
+    /**
+     * Map from a property value to a string name.
+     *
+     * @param value The value of a property
+     * @return The name of the enumeration value, null if the value is not mapped
+     */
+    @Nullable
+    public String nameOf(int value) {
+        for (Value valueTuple : mValues) {
+            if (valueTuple.mValue == value) {
+                return valueTuple.mName;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Create a new instance from a builder.
+     *
+     * This constructor is private, use {@link Builder#build()} instead.
+     *
+     * @param builder A builder to create from
+     */
+    private IntEnumMapping(Builder builder) {
+        mValues = builder.mValues.toArray(new Value[builder.mValues.size()]);
+    }
+
+    /**
+     * A builder for {@link IntEnumMapping}
+     */
+    public static final class Builder {
+        private final ArrayList<Value> mValues;
+
+        public Builder() {
+            mValues = new ArrayList<>();
+        }
+
+        /**
+         * Add a new entry to this mapping.
+         *
+         * @param name Name of the enumeration value
+         * @param value Int value of the enumeration value
+         * @return This builder
+         */
+        @NonNull
+        public Builder addValue(@NonNull String name, int value) {
+            mValues.add(new Value(name, value));
+            return this;
+        }
+
+        /**
+         * Clear the builder, allowing for recycling.
+         */
+        public void clear() {
+            mValues.clear();
+        }
+
+        /**
+         * Build a new {@link IntEnumMapping} from this builder
+         *
+         * @return A new mapping
+         */
+        @NonNull
+        public IntEnumMapping build() {
+            return new IntEnumMapping(this);
+        }
+    }
+
+    /**
+     * Inner class that holds the name and value of an enumeration value.
+     */
+    private static final class Value {
+        @NonNull private final String mName;
+        private final int mValue;
+
+        private Value(@NonNull String name, int value) {
+            mName = name;
+            mValue = value;
+        }
+    }
+}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
new file mode 100644
index 0000000..dcb87e1
--- /dev/null
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.view.inspector;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * Maps the values of an {int} property to arrays of string for properties that encode flags.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for flag values packed into primitive {int} properties.
+ *
+ * Each flag has a
+ *
+ * This class is immutable, and must be constructed by a {@link Builder}.
+ *
+ * @see PropertyMapper#mapIntFlag(String, int, IntFlagMapping)
+ */
+public final class IntFlagMapping {
+    private final Flag[] mFlags;
+
+    /**
+     * Get an array of the names of enabled flags for a given property value.
+     *
+     * @param value The value of the property
+     * @return The names of the enabled flags
+     */
+    @NonNull
+    public String[] namesOf(int value) {
+        ArrayList<String> enabledFlagNames = new ArrayList<>(mFlags.length);
+
+        for (Flag flag : mFlags) {
+            if (flag.isEnabledFor(value)) {
+                enabledFlagNames.add(flag.mName);
+            }
+        }
+
+        return enabledFlagNames.toArray(new String[enabledFlagNames.size()]);
+    }
+
+    /**
+     * Create a new instance from a builder.
+     *
+     * This constructor is private, use {@link Builder#build()} instead.
+     *
+     * @param builder A builder to create from
+     */
+    private IntFlagMapping(Builder builder) {
+        mFlags = builder.mFlags.toArray(new Flag[builder.mFlags.size()]);
+    }
+
+    /**
+     * A builder for {@link IntFlagMapping}.
+     */
+    public static final class Builder {
+        private ArrayList<Flag> mFlags;
+
+        public Builder() {
+            mFlags = new ArrayList<>();
+        }
+
+        /**
+         * Add a new flag without a mask.
+         *
+         * The target value will be used as a mask, to handle the common case where flag values
+         * are not mutually exclusive. The flag will be considered enabled for a property value if
+         * the result of bitwise anding the target and the value equals the target, that is:
+         * {(value & target) == target}.
+         *
+         * @param name The name of the flag
+         * @param target The value to compare against
+         * @return This builder
+         */
+        @NonNull
+        public Builder addFlag(@NonNull String name, int target) {
+            mFlags.add(new Flag(name, target, target));
+            return this;
+        }
+
+        /**
+         * Add a new flag with a mask.
+         *
+         * The flag will be considered enabled for a property value if the result of bitwise anding
+         * the value and the mask equals the target, that is: {(value & mask) == target}.
+         *
+         * @param name The name of the flag
+         * @param target The value to compare against
+         * @param mask A bit mask
+         * @return This builder
+         */
+        @NonNull
+        public Builder addFlag(@NonNull String name, int target, int mask) {
+            mFlags.add(new Flag(name, target, mask));
+            return this;
+        }
+
+        /**
+         * Clear the builder, allowing for recycling.
+         */
+        public void clear() {
+            mFlags.clear();
+        }
+
+        /**
+         * Build a new {@link IntFlagMapping} from this builder.
+         *
+         * @return A new mapping
+         */
+        @NonNull
+        public IntFlagMapping build() {
+            return new IntFlagMapping(this);
+        }
+    }
+
+    /**
+     * Inner class that holds the name, mask, and target value of a flag
+     */
+    private static final class Flag {
+        @NonNull private final String mName;
+        private final int mTarget;
+        private final int mMask;
+
+        private Flag(@NonNull String name, int target, int mask) {
+            mName = name;
+            mTarget = target;
+            mMask = mask;
+        }
+
+        /**
+         * Compare the supplied property value against the mask and taget.
+         *
+         * @param value The value to check
+         * @return True if this flag is enabled
+         */
+        private boolean isEnabledFor(int value) {
+            return (value & mMask) == mTarget;
+        }
+    }
+}
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index 35550bd..5fb291b 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -16,102 +16,160 @@
 
 package android.view.inspector;
 
+import android.annotation.AttrRes;
 import android.annotation.NonNull;
 
 /**
  * An interface for mapping the string names of inspectable properties to integer identifiers.
  *
- * This interface is consumed by {@link InspectionHelper#mapProperties(PropertyMapper)}.
+ * This interface is consumed by {@link InspectionCompanion#mapProperties(PropertyMapper)}.
  *
  * Mapping properties to IDs enables quick comparisons against shadow copies of inspectable
  * objects without performing a large number of string comparisons.
  *
- * @see InspectionHelper#mapProperties(PropertyMapper)
- * @hide
+ * @see InspectionCompanion#mapProperties(PropertyMapper)
  */
 public interface PropertyMapper {
     /**
      * Map a string name to an integer ID for a primitive boolean property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapBoolean(@NonNull String name);
+    int mapBoolean(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive byte property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapByte(@NonNull String name);
+    int mapByte(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive char property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapChar(@NonNull String name);
+    int mapChar(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive double property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapDouble(@NonNull String name);
+    int mapDouble(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive float property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapFloat(@NonNull String name);
+    int mapFloat(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive int property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapInt(@NonNull String name);
+    int mapInt(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive long property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapLong(@NonNull String name);
+    int mapLong(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for a primitive short property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapShort(@NonNull String name);
+    int mapShort(@NonNull String name, @AttrRes int attributeId);
 
     /**
      * Map a string name to an integer ID for an object property.
      *
      * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
-    int mapObject(@NonNull String name);
+    int mapObject(@NonNull String name, @AttrRes int attributeId);
 
     /**
+     * Map a string name to an integer ID for a color property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     * @see android.graphics.Color
+     */
+    int mapColor(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for a gravity property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     * @see android.view.Gravity
+     */
+    int mapGravity(@NonNull String name, @AttrRes int attributeId);
+
+    /**
+     * Map a string name to an integer ID for an enumeration packed into an int property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @param mapping A mapping from int to String
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapIntEnum(
+            @NonNull String name,
+            @AttrRes int attributeId,
+            @NonNull IntEnumMapping mapping);
+
+    /**
+     * Map a string name to an integer ID for a flag set packed into an int property.
+     *
+     * @param name The name of the property
+     * @param attributeId If the property is from an XML attribute, the resource ID of the property
+     * @param mapping A mapping from int to an array of strings
+     * @return An integer ID for the property
+     * @throws PropertyConflictException If the property name is already mapped as another type.
+     */
+    int mapIntFlag(
+            @NonNull String name,
+            @AttrRes int attributeId,
+            @NonNull IntFlagMapping mapping);
+    /**
      * Thrown from a map method if a property name is already mapped as different type.
      */
     class PropertyConflictException extends RuntimeException {
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index df81c10..fd83e8d 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -16,19 +16,21 @@
 
 package android.view.inspector;
 
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Color;
 
 /**
  * An interface for reading the properties of an inspectable object.
  *
- * Used as the parameter for {@link InspectionHelper#readProperties(Object, PropertyReader)}.
+ * Used as the parameter for {@link InspectionCompanion#readProperties(Object, PropertyReader)}.
  * It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
  * implementation is able to work with primitives. Implementations should be prepared to accept
  * {null} as the value of {@link PropertyReader#readObject(int, Object)}.
  *
- * @see InspectionHelper#readProperties(Object, PropertyReader)
- * @hide
+ * @see InspectionCompanion#readProperties(Object, PropertyReader)
  */
 public interface PropertyReader {
     /**
@@ -115,6 +117,60 @@
     void readObject(int id, @Nullable Object value);
 
     /**
+     * Read a color packed into a {@link ColorInt} as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @ColorInt int value);
+
+    /**
+     * Read a color packed into a {@link ColorLong} as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @ColorLong long value);
+
+    /**
+     * Read a {@link Color} object as a property.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a color
+     */
+    void readColor(int id, @Nullable Color value);
+
+    /**
+     * Read {@link android.view.Gravity} packed into an primitive {int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a gravity property
+     */
+    void readGravity(int id, int value);
+
+    /**
+     * Read an enumeration packed into a primitive {int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+     */
+    void readIntEnum(int id, int value);
+
+    /**
+     * Read a flag packed into a primitive {int}.
+     *
+     * @param id Identifier of the property from a {@link PropertyMapper}
+     * @param value Value of the property
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an object
+     */
+    void readIntFlag(int id, int value);
+
+    /**
      * Thrown if a client calls a typed read method for a property of a different type.
      */
     class PropertyTypeMismatchException extends RuntimeException {