Exposes style and theme data, adds developer option.
Adds support for a String[] return type in ViewDebug; and in addition to that,
the hasAdjacentMapping method can use the String array as means to map a key to
its value.
Adds DEBUG_VIEW_ATTRIBUTES to Settings so that the heavy per-view
computations only affect those who opt in. This setting is used in
CoreSettingsObserver to avoid impacting start time.
Change-Id: I8f507e4e5361414c30d247e8d9815205feb5e91f
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d9ea671..2136209 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3494,6 +3494,20 @@
synchronized (mResourcesManager) {
mCoreSettings = coreSettings;
}
+ onCoreSettingsChange();
+ }
+
+ private void onCoreSettingsChange() {
+ boolean debugViewAttributes =
+ mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
+ if (debugViewAttributes != View.mDebugViewAttributes) {
+ View.mDebugViewAttributes = debugViewAttributes;
+
+ // request all activities to relaunch for the changes to take place
+ for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
+ requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, false);
+ }
+ }
}
private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
@@ -4324,6 +4338,9 @@
final boolean is24Hr = "24".equals(mCoreSettings.getString(Settings.System.TIME_12_24));
DateFormat.set24HourTimePref(is24Hr);
+ View.mDebugViewAttributes =
+ mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
+
/**
* For system applications on userdebug/eng builds, log stack
* traces of disk and network access to dropbox for analysis.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5f046c5..a13a928 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -45,6 +45,7 @@
import android.util.AttributeSet;
import android.view.DisplayAdjustments;
import android.view.Display;
+import android.view.ViewDebug;
import android.view.WindowManager;
import java.io.File;
@@ -420,6 +421,7 @@
/**
* Return the Theme object associated with this Context.
*/
+ @ViewDebug.ExportedProperty(deepExport = true)
public abstract Resources.Theme getTheme();
/**
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index fff21aa..31813c10 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -1638,12 +1639,41 @@
/*package*/ String getKey() {
return mKey;
}
+
+ private String getResourceNameFromHexString(String hexString) {
+ return getResourceName(Integer.parseInt(hexString, 16));
+ }
+
+ /**
+ * Parses {@link #mKey} and returns a String array that holds pairs of adjacent Theme data:
+ * resource name followed by whether or not it was forced, as specified by
+ * {@link #applyStyle(int, boolean)}.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
+ public String[] getTheme() {
+ String[] themeData = mKey.split(" ");
+ String[] themes = new String[themeData.length * 2];
+ String theme;
+ boolean forced;
+
+ for (int i = 0, j = themeData.length - 1; i < themes.length; i += 2, --j) {
+ theme = themeData[j];
+ forced = theme.endsWith("!");
+ themes[i] = forced ?
+ getResourceNameFromHexString(theme.substring(0, theme.length() - 1)) :
+ getResourceNameFromHexString(theme);
+ themes[i + 1] = forced ? "forced" : "not forced";
+ }
+ return themes;
+ }
}
/**
* Generate a new Theme object for this set of Resources. It initially
* starts out empty.
- *
+ *
* @return Theme The newly created Theme container.
*/
public final Theme newTheme() {
@@ -1653,7 +1683,7 @@
/**
* Retrieve a set of basic attribute values from an AttributeSet, not
* performing styling of them using a theme and/or style resources.
- *
+ *
* @param set The current attribute values to retrieve.
* @param attrs The specific attributes to be retrieved.
* @return Returns a TypedArray holding an array of the attribute values.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 20e26e6..9e0e618 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5183,6 +5183,12 @@
public static final String ADB_ENABLED = "adb_enabled";
/**
+ * Whether Views are allowed to save their attribute data.
+ * @hide
+ */
+ public static final String DEBUG_VIEW_ATTRIBUTES = "debug_view_attributes";
+
+ /**
* Whether assisted GPS should be enabled or not.
* @hide
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e56581b..cec7e63 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -700,6 +700,13 @@
public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout";
/**
+ * When set to true, this view will save its attribute data.
+ *
+ * @hide
+ */
+ public static boolean mDebugViewAttributes = false;
+
+ /**
* Used to mark a View that has no ID.
*/
public static final int NO_ID = -1;
@@ -3254,6 +3261,7 @@
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
+ @ViewDebug.ExportedProperty(deepExport = true)
protected Context mContext;
private final Resources mResources;
@@ -3524,6 +3532,18 @@
GhostView mGhostView;
/**
+ * Holds pairs of adjacent attribute data: attribute name followed by its value.
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "attributes", hasAdjacentMapping = true)
+ public String[] mAttributes;
+
+ /**
+ * Maps a Resource id to its name.
+ */
+ private static SparseArray<String> mAttributeMap;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -3641,6 +3661,10 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
+ if (mDebugViewAttributes) {
+ saveAttributeData(attrs, a);
+ }
+
Drawable background = null;
int leftPadding = -1;
@@ -4136,6 +4160,51 @@
mRenderNode = RenderNode.create(getClass().getName());
}
+ private static SparseArray<String> getAttributeMap() {
+ if (mAttributeMap == null) {
+ mAttributeMap = new SparseArray<String>();
+ }
+ return mAttributeMap;
+ }
+
+ private void saveAttributeData(AttributeSet attrs, TypedArray a) {
+ int length = ((attrs == null ? 0 : attrs.getAttributeCount()) + a.getIndexCount()) * 2;
+ mAttributes = new String[length];
+
+ int i = 0;
+ if (attrs != null) {
+ for (i = 0; i < attrs.getAttributeCount(); i += 2) {
+ mAttributes[i] = attrs.getAttributeName(i);
+ mAttributes[i + 1] = attrs.getAttributeValue(i);
+ }
+
+ }
+
+ SparseArray<String> attributeMap = getAttributeMap();
+ for (int j = 0; j < a.length(); ++j) {
+ if (a.hasValue(j)) {
+ try {
+ int resourceId = a.getResourceId(j, 0);
+ if (resourceId == 0) {
+ continue;
+ }
+
+ String resourceName = attributeMap.get(resourceId);
+ if (resourceName == null) {
+ resourceName = a.getResources().getResourceName(resourceId);
+ attributeMap.put(resourceId, resourceName);
+ }
+
+ mAttributes[i] = resourceName;
+ mAttributes[i + 1] = a.getText(j).toString();
+ i += 2;
+ } catch (Resources.NotFoundException e) {
+ // if we can't get the resource name, we just ignore it
+ }
+ }
+ }
+ }
+
public String toString() {
StringBuilder out = new StringBuilder(128);
out.append(getClass().getName());
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4d9a8cc..6c66eb0 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -187,6 +187,15 @@
* @return true if the supported values should be formatted as a hex string.
*/
boolean formatToHexString() default false;
+
+ /**
+ * Indicates whether or not the key to value mappings are held in adjacent indices.
+ *
+ * Note: Applies only to fields and methods that return String[].
+ *
+ * @return true if the key to value mappings are held in adjacent indices.
+ */
+ boolean hasAdjacentMapping() default false;
}
/**
@@ -1056,7 +1065,6 @@
Class<?> klass, String prefix) throws IOException {
final Method[] methods = getExportedPropertyMethods(klass);
-
int count = methods.length;
for (int i = 0; i < count; i++) {
final Method method = methods[i];
@@ -1108,6 +1116,19 @@
exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
continue;
+ } else if (returnType == String[].class) {
+ final String[] array = (String[]) methodValue;
+ if (property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j], "()",
+ array[j + 1] == null ? "null" : array[j + 1]);
+ }
+
+ }
+ }
+
+ continue;
} else if (!returnType.isPrimitive()) {
if (property.deepExport()) {
dumpViewProperties(context, methodValue, out, prefix + property.prefix());
@@ -1187,6 +1208,18 @@
exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
continue;
+ } else if (type == String[].class) {
+ final String[] array = (String[]) field.get(view);
+ if (property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j], "",
+ array[j + 1] == null ? "null" : array[j + 1]);
+ }
+ }
+ }
+
+ continue;
} else if (!type.isPrimitive()) {
if (property.deepExport()) {
dumpViewProperties(context, field.get(view), out, prefix +