Merge changes 1591,1596 into donut

* changes:
  Add a new feature to android.os.Debug to add the ability to inject only specific fields when calling setFieldsOn().
  Fixes #1836075. Adds consistency checks for the View hierarchy. To enable them, you need a debug build and ViewDebug.sConsistencyCheckEnabled set to true in debug.prop. This change also lets you easily enable drawing and layout profiling in ViewRoot by setting ViewRoot.sProfileDrawing, ViewRoot.sProfileLayout and ViewRoot.sShowFps in debug.prop with a debug build.
This commit is contained in:
Android (Google) Code Review
2009-05-14 11:59:33 -07:00
7 changed files with 317 additions and 37 deletions

View File

@ -30,6 +30,10 @@ import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
@ -856,6 +860,15 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
* Equivalent to <code>setFieldsOn(cl, false)</code>.
*
* @see #setFieldsOn(Class, boolean)
*/
public static void setFieldsOn(Class<?> cl) {
setFieldsOn(cl, false);
}
/**
* Reflectively sets static fields of a class based on internal debugging
* properties. This method is a no-op if android.util.Config.DEBUG is
@ -868,7 +881,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* Class setup: define a class whose only fields are non-final, static
* primitive types (except for "char") or Strings. In a static block
* after the field definitions/initializations, pass the class to
* this method, Debug.setFieldsOn(). Example:
* this method, Debug.setFieldsOn(). Example:
* <pre>
* package com.example;
*
@ -880,12 +893,18 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* public static String ns = null;
* public static boolean b = false;
* public static int i = 5;
* @Debug.DebugProperty
* public static float f = 0.1f;
* @@Debug.DebugProperty
* public static double d = 0.5d;
*
* // This MUST appear AFTER all fields are defined and initialized!
* static {
* // Sets all the fields
* Debug.setFieldsOn(MyDebugVars.class);
*
* // Sets only the fields annotated with @Debug.DebugProperty
* // Debug.setFieldsOn(MyDebugVars.class, true);
* }
* }
* </pre>
@ -898,25 +917,31 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* {@hide}
*
* @param cl The class to (possibly) modify
* @param partial If false, sets all static fields, otherwise, only set
* fields with the {@link android.os.Debug.DebugProperty}
* annotation
* @throws IllegalArgumentException if any fields are final or non-static,
* or if the type of the field does not match the type of
* the internal debugging property value.
*/
public static void setFieldsOn(Class<?> cl) {
public static void setFieldsOn(Class<?> cl, boolean partial) {
if (Config.DEBUG) {
if (debugProperties != null) {
/* Only look for fields declared directly by the class,
* so we don't mysteriously change static fields in superclasses.
*/
for (Field field : cl.getDeclaredFields()) {
final String propertyName = cl.getName() + "." + field.getName();
boolean isStatic = Modifier.isStatic(field.getModifiers());
boolean isFinal = Modifier.isFinal(field.getModifiers());
if (!isStatic || isFinal) {
throw new IllegalArgumentException(propertyName +
" must be static and non-final");
if (!partial || field.getAnnotation(DebugProperty.class) != null) {
final String propertyName = cl.getName() + "." + field.getName();
boolean isStatic = Modifier.isStatic(field.getModifiers());
boolean isFinal = Modifier.isFinal(field.getModifiers());
if (!isStatic || isFinal) {
throw new IllegalArgumentException(propertyName +
" must be static and non-final");
}
modifyFieldIfSet(field, debugProperties, propertyName);
}
modifyFieldIfSet(field, debugProperties, propertyName);
}
}
} else {
@ -925,4 +950,15 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
") called in non-DEBUG build");
}
}
/**
* Annotation to put on fields you want to set with
* {@link Debug#setFieldsOn(Class, boolean)}.
*
* @hide
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugProperty {
}
}

View File

@ -49,6 +49,7 @@ import android.util.Poolable;
import android.util.Pool;
import android.util.Pools;
import android.util.PoolableManager;
import android.util.Config;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.animation.Animation;
import android.view.inputmethod.InputConnection;
@ -5641,7 +5642,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
}
if (ViewRoot.PROFILE_DRAWING) {
if (Config.DEBUG && ViewDebug.profileDrawing) {
EventLog.writeEvent(60002, hashCode());
}
@ -7165,6 +7166,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
tags.put(key, tag);
}
/**
* @param consistency The type of consistency. See ViewDebug for more information.
*
* @hide
*/
protected boolean dispatchConsistencyCheck(int consistency) {
return onConsistencyCheck(consistency);
}
/**
* Method that subclasses should implement to check their consistency. The type of
* consistency check is indicated by the bit field passed as a parameter.
*
* @param consistency The type of consistency. See ViewDebug for more information.
*
* @throws IllegalStateException if the view is in an inconsistent state.
*
* @hide
*/
protected boolean onConsistencyCheck(int consistency) {
boolean result = true;
final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0;
if (checkLayout) {
if (getParent() == null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " does not have a parent.");
}
if (mAttachInfo == null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " is not attached to a window.");
}
}
if (checkDrawing) {
// Do not check the DIRTY/DRAWN flags because views can call invalidate()
// from their draw() method
if ((mPrivateFlags & DRAWN) != DRAWN &&
(mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " was invalidated but its drawing cache is valid.");
}
}
return result;
}
/**
* Prints information about this view in the log output, with the tag
* {@link #VIEW_LOG_TAG}.

View File

@ -53,6 +53,27 @@ import java.lang.reflect.AccessibleObject;
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
*/
public class ViewDebug {
/**
* Log tag used to log errors related to the consistency of the view hierarchy.
*
* @hide
*/
public static final String CONSISTENCY_LOG_TAG = "ViewConsistency";
/**
* Flag indicating the consistency check should check layout-related properties.
*
* @hide
*/
public static final int CONSISTENCY_LAYOUT = 0x1;
/**
* Flag indicating the consistency check should check drawing-related properties.
*
* @hide
*/
public static final int CONSISTENCY_DRAWING = 0x2;
/**
* Enables or disables view hierarchy tracing. Any invoker of
* {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
@ -79,6 +100,49 @@ public class ViewDebug {
*/
static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
/**
* Profiles drawing times in the events log.
*
* @hide
*/
@Debug.DebugProperty
public static boolean profileDrawing = false;
/**
* Profiles layout times in the events log.
*
* @hide
*/
@Debug.DebugProperty
public static boolean profileLayout = false;
/**
* Profiles real fps (times between draws) and displays the result.
*
* @hide
*/
@Debug.DebugProperty
public static boolean showFps = false;
/**
* <p>Enables or disables views consistency check. Even when this property is enabled,
* view consistency checks happen only if {@link android.util.Config#DEBUG} is set
* to true. The value of this property can be configured externally in one of the
* following files:</p>
* <ul>
* <li>/system/debug.prop</li>
* <li>/debug.prop</li>
* <li>/data/debug.prop</li>
* </ul>
* @hide
*/
@Debug.DebugProperty
public static boolean consistencyCheckEnabled = false;
static {
Debug.setFieldsOn(ViewDebug.class, true);
}
/**
* This annotation can be used to mark fields and methods to be dumped by
* the view server. Only non-void methods with no arguments can be annotated

View File

@ -32,6 +32,7 @@ import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.util.Config;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
@ -1404,7 +1405,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Clear the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
child.mPrivateFlags |= DRAWN;
child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
(child.mPrivateFlags & DRAW_ANIMATION) == 0) {
@ -1494,7 +1495,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
cachePaint.setAlpha(255);
mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
}
if (ViewRoot.PROFILE_DRAWING) {
if (Config.DEBUG && ViewDebug.profileDrawing) {
EventLog.writeEvent(60003, hashCode());
}
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
@ -2749,6 +2750,61 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
/**
* @hide
*/
@Override
protected boolean dispatchConsistencyCheck(int consistency) {
boolean result = super.dispatchConsistencyCheck(consistency);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
if (!children[i].dispatchConsistencyCheck(consistency)) result = false;
}
return result;
}
/**
* @hide
*/
@Override
protected boolean onConsistencyCheck(int consistency) {
boolean result = super.onConsistencyCheck(consistency);
final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0;
if (checkLayout) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
if (children[i].getParent() != this) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + children[i] + " has no parent/a parent that is not " + this);
}
}
}
if (checkDrawing) {
// If this group is dirty, check that the parent is dirty as well
if ((mPrivateFlags & DIRTY_MASK) != 0) {
final ViewParent parent = getParent();
if (parent != null && !(parent instanceof ViewRoot)) {
if ((((View) parent).mPrivateFlags & DIRTY_MASK) == 0) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"ViewGroup " + this + " is dirty but its parent is not: " + this);
}
}
}
}
return result;
}
/**
* {@inheritDoc}
*/

View File

@ -75,13 +75,6 @@ public final class ViewRoot extends Handler implements ViewParent,
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean WATCH_POINTER = false;
static final boolean PROFILE_DRAWING = false;
private static final boolean PROFILE_LAYOUT = false;
// profiles real fps (times between draws) and displays the result
private static final boolean SHOW_FPS = false;
// used by SHOW_FPS
private static int sDrawTime;
/**
* Maximum time we allow the user to roll the trackball enough to generate
* a key event, before resetting the counters.
@ -97,6 +90,8 @@ public final class ViewRoot extends Handler implements ViewParent,
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
private static int sDrawTime;
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
final TrackballAxis mTrackballAxisY = new TrackballAxis();
@ -796,7 +791,7 @@ public final class ViewRoot extends Handler implements ViewParent,
final Rect frame = mWinFrame;
boolean initialized = false;
boolean contentInsetsChanged = false;
boolean visibleInsetsChanged = false;
boolean visibleInsetsChanged;
try {
boolean hadSurface = mSurface.isValid();
int fl = 0;
@ -937,14 +932,22 @@ public final class ViewRoot extends Handler implements ViewParent,
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(
"ViewRoot", "Laying out " + host + " to (" +
host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
long startTime;
if (PROFILE_LAYOUT) {
long startTime = 0L;
if (Config.DEBUG && ViewDebug.profileLayout) {
startTime = SystemClock.elapsedRealtime();
}
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
if (PROFILE_LAYOUT) {
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) {
throw new IllegalStateException("The view hierarchy is an inconsistent state,"
+ "please refer to the logs with the tag "
+ ViewDebug.CONSISTENCY_LOG_TAG + " for more infomation.");
}
}
if (Config.DEBUG && ViewDebug.profileLayout) {
EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime);
}
@ -960,10 +963,11 @@ public final class ViewRoot extends Handler implements ViewParent,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mAppScale != 1.0f) {
mTransparentRegion.scale(mAppScale);
}
// TODO: scale the region, like:
// Region uses native methods. We probabl should have ScalableRegion class.
// Region does not have equals method ?
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
// reconfigure window manager
@ -1168,6 +1172,9 @@ public final class ViewRoot extends Handler implements ViewParent,
canvas.scale(scale, scale);
}
mView.draw(canvas);
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
} finally {
canvas.restoreToCount(saveCount);
}
@ -1175,7 +1182,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
if (SHOW_FPS) {
if (Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@ -1216,7 +1223,7 @@ public final class ViewRoot extends Handler implements ViewParent,
try {
if (!dirty.isEmpty() || mIsAnimating) {
long startTime;
long startTime = 0L;
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
@ -1224,7 +1231,7 @@ public final class ViewRoot extends Handler implements ViewParent,
//canvas.drawARGB(255, 255, 0, 0);
}
if (PROFILE_DRAWING) {
if (Config.DEBUG && ViewDebug.profileDrawing) {
startTime = SystemClock.elapsedRealtime();
}
@ -1259,11 +1266,15 @@ public final class ViewRoot extends Handler implements ViewParent,
canvas.scale(scale, scale);
}
mView.draw(canvas);
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
} finally {
canvas.restoreToCount(saveCount);
}
if (SHOW_FPS) {
if (Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@ -1271,7 +1282,7 @@ public final class ViewRoot extends Handler implements ViewParent,
sDrawTime = now;
}
if (PROFILE_DRAWING) {
if (Config.DEBUG && ViewDebug.profileDrawing) {
EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
}
}
@ -1878,6 +1889,9 @@ public final class ViewRoot extends Handler implements ViewParent,
} else {
didFinish = false;
}
if (event != null) {
event.scale(mAppScaleInverted);
}
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);

View File

@ -3190,7 +3190,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Reclaim views on screen
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams();
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't reclaim header or footer views, or views that should be ignored
if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
views.add(child);
@ -3204,6 +3204,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
removeAllViewsInLayout();
}
/**
* @hide
*/
@Override
protected boolean onConsistencyCheck(int consistency) {
boolean result = super.onConsistencyCheck(consistency);
final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
if (checkLayout) {
// The active recycler must be empty
final View[] activeViews = mRecycler.mActiveViews;
int count = activeViews.length;
for (int i = 0; i < count; i++) {
if (activeViews[i] != null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"AbsListView " + this + " has a view in its active recycler: " +
activeViews[i]);
}
}
// All views in the recycler must NOT be on screen and must NOT have a parent
final ArrayList<View> scrap = mRecycler.mCurrentScrap;
if (!checkScrap(scrap)) result = false;
final ArrayList<View>[] scraps = mRecycler.mScrapViews;
count = scraps.length;
for (int i = 0; i < count; i++) {
if (!checkScrap(scraps[i])) result = false;
}
}
return result;
}
private boolean checkScrap(ArrayList<View> scrap) {
if (scrap == null) return true;
boolean result = true;
final int count = scrap.size();
for (int i = 0; i < count; i++) {
final View view = scrap.get(i);
if (view.getParent() != null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
" has a view in its scrap heap still attached to a parent: " + view);
}
if (indexOfChild(view) >= 0) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
" has a view in its scrap heap that is also a direct child: " + view);
}
}
return result;
}
/**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources

View File

@ -8,8 +8,6 @@ import android.widget.LinearLayout;
public class IconMerger extends LinearLayout {
private static final boolean SPEW = false;
StatusBarService service;
StatusBarIcon moreIcon;
@ -29,7 +27,7 @@ public class IconMerger extends LinearLayout {
int fitRight = -1;
for (i=N-1; i>=0; i--) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (child.getVisibility() != GONE) {
fitRight = child.getRight();
break;
}
@ -45,7 +43,7 @@ public class IconMerger extends LinearLayout {
moreView = child;
startIndex = i+1;
}
else if (child != null && child.getVisibility() != GONE) {
else if (child.getVisibility() != GONE) {
fitLeft = child.getLeft();
break;
}
@ -71,7 +69,7 @@ public class IconMerger extends LinearLayout {
int number = 0;
for (i=startIndex; i<N; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (child.getVisibility() != GONE) {
int childLeft = child.getLeft();
int childRight = child.getRight();
if (childLeft < breakingPoint) {