Add helper methods for View attribute debugging
Adding abilities to debug:
- Attribute resolution stack (which resources are looked
at when resolving an attribute)
- Attribute value source (where did each attribute value
get defined)
- Get explicit style id (if a view had it set via style="...")
This feature will be behind Settings.Global flag that Android
Studio will set to the debugged application package ID.
Bug: 111439551
Test: atest CtsViewTestCases:android.view.cts.ViewStyleTest
Change-Id: Ib6f9fc81000bb867b5b94a68953c99b0bc802d6c
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 21d66e5..cc419b8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4565,16 +4565,25 @@
}
private void onCoreSettingsChange() {
- boolean debugViewAttributes =
- mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
- if (debugViewAttributes != View.mDebugViewAttributes) {
- View.mDebugViewAttributes = debugViewAttributes;
-
+ if (updateDebugViewAttributeState()) {
// request all activities to relaunch for the changes to take place
relaunchAllActivities();
}
}
+ private boolean updateDebugViewAttributeState() {
+ boolean previousState = View.sDebugViewAttributes;
+
+ View.sDebugViewAttributesApplicationPackage = mCoreSettings.getString(
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, "");
+ String currentPackage = (mBoundApplication != null && mBoundApplication.appInfo != null)
+ ? mBoundApplication.appInfo.packageName : "";
+ View.sDebugViewAttributes =
+ mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0
+ || View.sDebugViewAttributesApplicationPackage.equals(currentPackage);
+ return previousState != View.sDebugViewAttributes;
+ }
+
private void relaunchAllActivities() {
for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
final Activity activity = entry.getValue().activity;
@@ -5950,8 +5959,7 @@
// true : use 24 hour format.
DateFormat.set24HourTimePref(is24Hr);
- View.mDebugViewAttributes =
- mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
+ updateDebugViewAttributeState();
StrictMode.initThreadDefaults(data.appInfo);
StrictMode.initVmDefaults(data.appInfo);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 9e0a9ba..faf17e0 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1051,6 +1051,14 @@
}
}
+ int[] getAttributeResolutionStack(long themePtr, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int xmlStyle) {
+ synchronized (this) {
+ return nativeAttributeResolutionStack(
+ mObject, themePtr, xmlStyle, defStyleAttr, defStyleRes);
+ }
+ }
+
@UnsupportedAppUsage
boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
@Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
@@ -1419,6 +1427,8 @@
private static native @Nullable String nativeGetLastResourceResolution(long ptr);
// Style attribute retrieval native methods.
+ private static native int[] nativeAttributeResolutionStack(long ptr, long themePtr,
+ @StyleRes int xmlStyleRes, @AttrRes int defStyleAttr, @StyleRes int defStyleRes);
private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs,
long outValuesAddress, long outIndicesAddress);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 59db49e..a2ae994 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1725,6 +1725,68 @@
public void rebase() {
mThemeImpl.rebase();
}
+
+ /**
+ * Returns the resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise if not
+ * specified or otherwise not applicable.
+ * <p>
+ * Each {@link android.view.View} can have an explicit style specified in the layout file.
+ * This style is used first during the {@link android.view.View} attribute resolution, then
+ * if an attribute is not defined there the resource system looks at default style and theme
+ * as fallbacks.
+ *
+ * @param set The base set of attribute values.
+ *
+ * @return The resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise
+ * if not specified or otherwise not applicable.
+ */
+ @StyleRes
+ public int getExplicitStyle(@Nullable AttributeSet set) {
+ if (set == null) {
+ return ID_NULL;
+ }
+ int styleAttr = set.getStyleAttribute();
+ if (styleAttr == ID_NULL) {
+ return ID_NULL;
+ }
+ String styleAttrType = getResources().getResourceTypeName(styleAttr);
+ if ("attr".equals(styleAttrType)) {
+ TypedValue explicitStyle = new TypedValue();
+ boolean resolved = resolveAttribute(styleAttr, explicitStyle, true);
+ if (resolved) {
+ return explicitStyle.resourceId;
+ }
+ } else if ("style".equals(styleAttrType)) {
+ return styleAttr;
+ }
+ return ID_NULL;
+ }
+
+ /**
+ * Returns the ordered list of resource ID that are considered when resolving attribute
+ * values when making an equivalent call to
+ * {@link #obtainStyledAttributes(AttributeSet, int[], int, int)} . The list will include
+ * a set of explicit styles ({@code explicitStyleRes} and it will include the default styles
+ * ({@code defStyleAttr} and {@code defStyleRes}).
+ *
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ * @param explicitStyleRes A resource identifier of an explicit style resource.
+ * @return ordered list of resource ID that are considered when resolving attribute values.
+ */
+ public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
+ return mThemeImpl.getAttributeResolutionStack(
+ defStyleAttr, defStyleRes, explicitStyleRes);
+ }
}
static class ThemeKey implements Cloneable {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 9898079..da064c9 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1488,6 +1488,32 @@
}
}
}
+
+ /**
+ * Returns the ordered list of resource ID that are considered when resolving attribute
+ * values when making an equivalent call to
+ * {@link #obtainStyledAttributes(Resources.Theme, AttributeSet, int[], int, int)}. The list
+ * will include a set of explicit styles ({@code explicitStyleRes} and it will include the
+ * default styles ({@code defStyleAttr} and {@code defStyleRes}).
+ *
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ * @param explicitStyleRes A resource identifier of an explicit style resource.
+ * @return ordered list of resource ID that are considered when resolving attribute values.
+ */
+ public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
+ synchronized (mKey) {
+ return mAssets.getAttributeResolutionStack(
+ mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
+ }
+ }
}
private static class LookupStack {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a8976aa..e95d6046 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9300,6 +9300,13 @@
public static final String DEBUG_VIEW_ATTRIBUTES = "debug_view_attributes";
/**
+ * Which application package is allowed to save View attribute data.
+ * @hide
+ */
+ public static final String DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE =
+ "debug_view_attributes_application_package";
+
+ /**
* Whether assisted GPS should be enabled or not.
* @hide
*/
@@ -14022,6 +14029,7 @@
INSTANT_APP_SETTINGS.add(TRANSITION_ANIMATION_SCALE);
INSTANT_APP_SETTINGS.add(ANIMATOR_DURATION_SCALE);
INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES);
+ INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE);
INSTANT_APP_SETTINGS.add(WTF_IS_FATAL);
INSTANT_APP_SETTINGS.add(SEND_ACTION_APP_ERROR);
INSTANT_APP_SETTINGS.add(ZEN_MODE);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 04ffa33..e56861e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -35,6 +35,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.StyleRes;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.annotation.UnsupportedAppUsage;
@@ -91,6 +92,7 @@
import android.util.Pools.SynchronizedPool;
import android.util.Property;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.StateSet;
import android.util.SuperNotCalledException;
import android.util.TypedValue;
@@ -820,7 +822,14 @@
*
* @hide
*/
- public static boolean mDebugViewAttributes = false;
+ public static boolean sDebugViewAttributes = false;
+
+ /**
+ * When set to this application package view will save its attribute data.
+ *
+ * @hide
+ */
+ public static String sDebugViewAttributesApplicationPackage;
/**
* Used to mark a View that has no ID.
@@ -5078,6 +5087,15 @@
@LayoutRes
private int mSourceLayoutId = ID_NULL;
+ @Nullable
+ private SparseIntArray mAttributeSourceResId;
+
+ @Nullable
+ private int[] mAttributeResolutionStack;
+
+ @StyleRes
+ private int mExplicitStyle;
+
/**
* Cached reference to the {@link ContentCaptureSession}, is reset on {@link #invalidate()}.
*/
@@ -5253,7 +5271,11 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
- if (mDebugViewAttributes) {
+ retrieveExplicitStyle(context.getTheme(), attrs);
+ saveAttributeDataForStyleable(context, com.android.internal.R.styleable.View, attrs, a,
+ defStyleAttr, defStyleRes);
+
+ if (sDebugViewAttributes) {
saveAttributeData(attrs, a);
}
@@ -5915,6 +5937,84 @@
}
/**
+ * Returns the ordered list of resource ID that are considered when resolving attribute values
+ * for this {@link View}. The list will include layout resource ID if the View is inflated from
+ * XML. It will also include a set of explicit styles if specified in XML using
+ * {@code style="..."}. Finally, it will include the default styles resolved from the theme.
+ *
+ * <p>
+ * <b>Note:</b> this method will only return actual values if the view attribute debugging
+ * is enabled in Android developer options.
+ *
+ * @return ordered list of resource ID that are considered when resolving attribute values for
+ * this {@link View}.
+ */
+ @NonNull
+ public List<Integer> getAttributeResolutionStack() {
+ ArrayList<Integer> stack = new ArrayList<>();
+ if (!sDebugViewAttributes) {
+ return stack;
+ }
+ if (mSourceLayoutId != ID_NULL) {
+ stack.add(mSourceLayoutId);
+ }
+ for (int i = 0; i < mAttributeResolutionStack.length; i++) {
+ stack.add(mAttributeResolutionStack[i]);
+ }
+ return stack;
+ }
+
+ /**
+ * Returns the mapping of attribute resource ID to source resource ID where the attribute value
+ * was set. Source resource ID can either be a layout resource ID, if the value was set in XML
+ * within the View tag, or a style resource ID, if the attribute was set in a style. The source
+ * resource value will be one of the resource IDs from {@link #getAttributeSourceResourceMap()}.
+ *
+ * <p>
+ * <b>Note:</b> this method will only return actual values if the view attribute debugging
+ * is enabled in Android developer options.
+ *
+ * @return mapping of attribute resource ID to source resource ID where the attribute value
+ * was set.
+ */
+ @NonNull
+ public Map<Integer, Integer> getAttributeSourceResourceMap() {
+ HashMap<Integer, Integer> map = new HashMap<>();
+ if (!sDebugViewAttributes) {
+ return map;
+ }
+ for (int i = 0; i < mAttributeSourceResId.size(); i++) {
+ map.put(mAttributeSourceResId.keyAt(i), mAttributeSourceResId.valueAt(i));
+ }
+ return map;
+ }
+
+ /**
+ * Returns the resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise if not
+ * specified or otherwise not applicable.
+ * <p>
+ * Each {@link View} can have an explicit style specified in the layout file.
+ * This style is used first during the {@link View} attribute resolution, then if an attribute
+ * is not defined there the resource system looks at default style and theme as fallbacks.
+ *
+ * <p>
+ * <b>Note:</b> this method will only return actual values if the view attribute debugging
+ * is enabled in Android developer options.
+ *
+ * @return The resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise
+ * if not specified or otherwise not applicable.
+ */
+ @StyleRes
+ public int getExplicitStyle() {
+ if (!sDebugViewAttributes) {
+ return ID_NULL;
+ }
+ return mExplicitStyle;
+ }
+
+ /**
* An implementation of OnClickListener that attempts to lazily load a
* named click handling method from a parent or ancestor context.
*/
@@ -6000,6 +6100,46 @@
return mAttributeMap;
}
+ private void retrieveExplicitStyle(@NonNull Resources.Theme theme,
+ @Nullable AttributeSet attrs) {
+ if (!sDebugViewAttributes) {
+ return;
+ }
+ mExplicitStyle = theme.getExplicitStyle(attrs);
+ }
+
+ /**
+ * Stores debugging information about attributes. This should be called in a constructor by
+ * every custom {@link View} that uses a custom styleable.
+ * @param context Context under which this view is created.
+ * @param styleable A reference to styleable array R.styleable.Foo
+ * @param attrs AttributeSet used to construct this view.
+ * @param t Resolved {@link TypedArray} returned by a call to
+ * {@link Resources#obtainAttributes(AttributeSet, int[])}.
+ * @param defStyleAttr Default style attribute passed into the view constructor.
+ * @param defStyleRes Default style resource passed into the view constructor.
+ */
+ public final void saveAttributeDataForStyleable(@NonNull Context context,
+ @NonNull int[] styleable, @Nullable AttributeSet attrs, @NonNull TypedArray t,
+ int defStyleAttr, int defStyleRes) {
+ if (!sDebugViewAttributes) {
+ return;
+ }
+
+ mAttributeResolutionStack = context.getTheme().getAttributeResolutionStack(
+ defStyleAttr, defStyleRes, mExplicitStyle);
+
+ if (mAttributeSourceResId == null) {
+ mAttributeSourceResId = new SparseIntArray();
+ }
+
+ final int indexCount = t.getIndexCount();
+ for (int j = 0; j < indexCount; ++j) {
+ final int index = t.getIndex(j);
+ mAttributeSourceResId.append(styleable[index], t.getSourceResourceId(index, 0));
+ }
+ }
+
private void saveAttributeData(@Nullable AttributeSet attrs, @NonNull TypedArray t) {
final int attrsCount = attrs == null ? 0 : attrs.getAttributeCount();
final int indexCount = t.getIndexCount();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 659b71f..3da450d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1055,6 +1055,8 @@
int inputType = EditorInfo.TYPE_NULL;
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
+ saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
+ defStyleAttr, defStyleRes);
int firstBaselineToTopHeight = -1;
int lastBaselineToBottomHeight = -1;
int lineHeight = -1;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 4101c04..d493ddf 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1105,6 +1105,46 @@
return array;
}
+static jintArray NativeAttributeResolutionStack(
+ JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jlong theme_ptr, jint xml_style_res,
+ jint def_style_attr, jint def_style_resid) {
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ // Load default style from attribute, if specified...
+ uint32_t def_style_flags = 0u;
+ if (def_style_attr != 0) {
+ Res_value value;
+ if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
+ if (value.dataType == Res_value::TYPE_REFERENCE) {
+ def_style_resid = value.data;
+ }
+ }
+ }
+
+ auto style_stack = assetmanager->GetBagResIdStack(xml_style_res);
+ auto def_style_stack = assetmanager->GetBagResIdStack(def_style_resid);
+
+ jintArray array = env->NewIntArray(style_stack.size() + def_style_stack.size());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < style_stack.size(); i++) {
+ jint attr_resid = style_stack[i];
+ env->SetIntArrayRegion(array, i, 1, &attr_resid);
+ }
+ for (uint32_t i = 0; i < def_style_stack.size(); i++) {
+ jint attr_resid = def_style_stack[i];
+ env->SetIntArrayRegion(array, style_stack.size() + i, 1, &attr_resid);
+ }
+ return array;
+}
+
static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
@@ -1456,6 +1496,7 @@
(void*)NativeGetSizeConfigurations},
// Style attribute related methods.
+ {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
{"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
{"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
{"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a160451..d577653 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -259,6 +259,8 @@
optional SettingProto app = 1;
// Whether views are allowed to save their attribute data.
optional SettingProto view_attributes = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Which application package is allowed to save view attribute data.
+ optional SettingProto view_attributes_application_package = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Debug debug = 37;
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 2cb925a..ec57f79 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -210,6 +210,7 @@
Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
Settings.Global.DEBUG_APP,
Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE,
Settings.Global.DEFAULT_DNS_SERVER,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,