Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13922e03 authored by Romain Guy's avatar Romain Guy
Browse files

Fixes #1836075. Adds consistency checks for the View hierarchy. To enable...

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.
parent a2a34a57
Loading
Loading
Loading
Loading
+56 −1
Original line number Diff line number Diff line
@@ -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}.
+64 −0
Original line number Diff line number Diff line
@@ -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
+58 −2
Original line number Diff line number Diff line
@@ -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}
     */
+33 −19
Original line number Diff line number Diff line
@@ -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);

+58 −1
Original line number Diff line number Diff line
@@ -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
Loading