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

Commit 25eba5c5 authored by Romain Guy's avatar Romain Guy
Browse files

Add a new OnDrawListener to ViewRoot

This can be used by app to efficiently listen for draw passes. This listener
is guaranteed to not generate any garbage unlike OnPreDrawListener.

Change-Id: Ida40d11a3f8a5d2617bafe722906ee5c9af48602
parent e313f721
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -24138,15 +24138,18 @@ package android.view {
  }
  public final class ViewTreeObserver {
    method public void addOnDrawListener(android.view.ViewTreeObserver.OnDrawListener);
    method public void addOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener);
    method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
    method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
    method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
    method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
    method public final void dispatchOnDraw();
    method public final void dispatchOnGlobalLayout();
    method public final boolean dispatchOnPreDraw();
    method public boolean isAlive();
    method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
    method public void removeOnDrawListener(android.view.ViewTreeObserver.OnDrawListener);
    method public void removeOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener);
    method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
    method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
@@ -24154,6 +24157,10 @@ package android.view {
    method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
  }
  public static abstract interface ViewTreeObserver.OnDrawListener {
    method public abstract void onDraw();
  }
  public static abstract interface ViewTreeObserver.OnGlobalFocusChangeListener {
    method public abstract void onGlobalFocusChanged(android.view.View, android.view.View);
  }
+145 −132
Original line number Diff line number Diff line
@@ -2039,9 +2039,10 @@ public final class ViewRootImpl implements ViewParent,

        scrollToRectOrFocus(null, false);

        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo.mViewScrollChanged) {
            attachInfo.mViewScrollChanged = false;
            attachInfo.mTreeObserver.dispatchOnScrollChanged();
        }

        int yoff;
@@ -2056,8 +2057,8 @@ public final class ViewRootImpl implements ViewParent,
            fullRedrawNeeded = true;
        }

        final float appScale = mAttachInfo.mApplicationScale;
        final boolean scalingRequired = mAttachInfo.mScalingRequired;
        final float appScale = attachInfo.mApplicationScale;
        final boolean scalingRequired = attachInfo.mScalingRequired;

        int resizeAlpha = 0;
        if (mResizeBuffer != null) {
@@ -2086,7 +2087,7 @@ public final class ViewRootImpl implements ViewParent,
        }

        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            attachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }

@@ -2099,8 +2100,10 @@ public final class ViewRootImpl implements ViewParent,
                    appScale + ", width=" + mWidth + ", height=" + mHeight);
        }

        attachInfo.mTreeObserver.dispatchOnDraw();

        if (!dirty.isEmpty() || mIsAnimating) {
            if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
                // Draw with hardware renderer.
                mIsAnimating = false;
                mHardwareYOffset = yoff;
@@ -2111,11 +2114,27 @@ public final class ViewRootImpl implements ViewParent,
                mPreviousDirty.set(dirty);
                dirty.setEmpty();

                if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this,
                if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
                        animating ? null : mCurrentDirty)) {
                    mPreviousDirty.set(0, 0, mWidth, mHeight);
                }
            } else {
            } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
                return;
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }

    /**
     * @return true if drawing was succesful, false if an error occured
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        Canvas canvas;
        try {
@@ -2139,7 +2158,7 @@ public final class ViewRootImpl implements ViewParent,

            if (left != dirty.left || top != dirty.top || right != dirty.right ||
                    bottom != dirty.bottom) {
                        mAttachInfo.mIgnoreDirtyState = true;
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
@@ -2154,14 +2173,14 @@ public final class ViewRootImpl implements ViewParent,
            } catch (RemoteException ex) {
            }
            mLayoutRequested = true;    // ask wm for a new surface next time.
                    return;
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "IllegalArgumentException locking surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
                    return;
            return false;
        }

        try {
@@ -2190,7 +2209,7 @@ public final class ViewRootImpl implements ViewParent,

            dirty.setEmpty();
            mIsAnimating = false;
                    mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
            attachInfo.mDrawingTime = SystemClock.uptimeMillis();
            mView.mPrivateFlags |= View.DRAWN;

            if (DEBUG_DRAW) {
@@ -2206,7 +2225,7 @@ public final class ViewRootImpl implements ViewParent,
                }
                canvas.setScreenDensity(scalingRequired
                        ? DisplayMetrics.DENSITY_DEVICE : 0);
                        mAttachInfo.mSetIgnoreDirtyState = false;
                attachInfo.mSetIgnoreDirtyState = false;

                final long drawStartTime;
                if (ViewDebug.DEBUG_LATENCY) {
@@ -2221,9 +2240,9 @@ public final class ViewRootImpl implements ViewParent,
                            + ((now - drawStartTime) * 0.000001f) + "ms");
                }
            } finally {
                        if (!mAttachInfo.mSetIgnoreDirtyState) {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                            mAttachInfo.mIgnoreDirtyState = false;
                    attachInfo.mIgnoreDirtyState = false;
                }
            }

@@ -2252,13 +2271,7 @@ public final class ViewRootImpl implements ViewParent,
                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
        return true;
    }

    void invalidateDisplayLists() {
+94 −25
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ public final class ViewTreeObserver {
    private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
    private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
    private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
    private ArrayList<OnDrawListener> mOnDrawListeners;

    private boolean mAlive = true;

@@ -89,6 +90,27 @@ public final class ViewTreeObserver {
        public boolean onPreDraw();
    }

    /**
     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
     */
    public interface OnDrawListener {
        /**
         * <p>Callback method to be invoked when the view tree is about to be drawn. At this point,
         * views cannot be modified in any way.</p>
         * 
         * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
         * current drawing pass.</p>
         * 
         * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong>
         * from this method.</p>
         *
         * @see android.view.View#onMeasure
         * @see android.view.View#onLayout
         * @see android.view.View#onDraw
         */
        public void onDraw();
    }

    /**
     * Interface definition for a callback to be invoked when the touch mode changes.
     */
@@ -172,10 +194,6 @@ public final class ViewTreeObserver {
            mTouchableInsets = val;
        }

        public int getTouchableInsets() {
            return mTouchableInsets;
        }
        
        int mTouchableInsets;
        
        void reset() {
@@ -186,25 +204,24 @@ public final class ViewTreeObserver {
        }

        @Override
        public boolean equals(Object o) {
            try {
                if (o == null) {
                    return false;
        public int hashCode() {
            int result = contentInsets != null ? contentInsets.hashCode() : 0;
            result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0);
            result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0);
            result = 31 * result + mTouchableInsets;
            return result;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            InternalInsetsInfo other = (InternalInsetsInfo)o;
                if (mTouchableInsets != other.mTouchableInsets) {
                    return false;
                }
                if (!contentInsets.equals(other.contentInsets)) {
                    return false;
                }
                if (!visibleInsets.equals(other.visibleInsets)) {
                    return false;
                }
                return touchableRegion.equals(other.touchableRegion);
            } catch (ClassCastException e) {
                return false;
            }
            return mTouchableInsets == other.mTouchableInsets &&
                    contentInsets.equals(other.contentInsets) &&
                    visibleInsets.equals(other.visibleInsets) &&
                    touchableRegion.equals(other.touchableRegion);
        }

        void set(InternalInsetsInfo other) {
@@ -419,6 +436,44 @@ public final class ViewTreeObserver {
        mOnPreDrawListeners.remove(victim);
    }

    /**
     * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
     *
     * @param listener The callback to add
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     */
    public void addOnDrawListener(OnDrawListener listener) {
        checkIsAlive();

        if (mOnDrawListeners == null) {
            mOnDrawListeners = new ArrayList<OnDrawListener>();
        }

        mOnDrawListeners.add(listener);
    }

    /**
     * <p>Remove a previously installed pre-draw callback.</p>
     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
     *
     * @param victim The callback to remove
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     *
     * @see #addOnDrawListener(OnDrawListener)
     */
    public void removeOnDrawListener(OnDrawListener victim) {
        checkIsAlive();
        if (mOnDrawListeners == null) {
            return;
        }
        mOnDrawListeners.remove(victim);
    }

    /**
     * Register a callback to be invoked when a view has been scrolled.
     *
@@ -601,6 +656,7 @@ public final class ViewTreeObserver {
     *
     * @return True if the current draw should be canceled and resceduled, false otherwise.
     */
    @SuppressWarnings("unchecked")
    public final boolean dispatchOnPreDraw() {
        // NOTE: we *must* clone the listener list to perform the dispatching.
        // The clone is a safe guard against listeners that
@@ -618,6 +674,19 @@ public final class ViewTreeObserver {
        return cancelDraw;
    }

    /**
     * Notifies registered listeners that the drawing pass is about to start.
     */
    public final void dispatchOnDraw() {
        if (mOnDrawListeners != null) {
            final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
            int numListeners = listeners.size();
            for (int i = 0; i < numListeners; ++i) {
                listeners.get(i).onDraw();
            }
        }
    }

    /**
     * Notifies registered listeners that the touch mode has changed.
     *