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

Commit 5bffbf24 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Keep local run queue in View to avoid posting to wrong thread"

parents 46c86c2b bea0c7da
Loading
Loading
Loading
Loading
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import com.android.internal.util.GrowingArrayUtils;

import android.os.Handler;

import java.util.ArrayList;

/**
 * Class used to enqueue pending work from Views when no Handler is attached.
 *
 * @hide Exposed for test framework only.
 */
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public void removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

    public int size() {
        return mCount;
    }

    public Runnable getRunnable(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].action;
    }

    public long getDelay(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].delay;
    }

    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}
+42 −13
Original line number Diff line number Diff line
@@ -3822,6 +3822,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    private static SparseArray<String> mAttributeMap;
    /**
     * Queue of pending runnables. Used to postpone calls to post() until this
     * view is attached and has a handler.
     */
    private HandlerActionQueue mRunQueue;
    /**
     * @hide
     */
@@ -13009,6 +13015,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return null;
    }
    /**
     * Returns the queue of runnable for this view.
     *
     * @return the queue of runnables for this view
     */
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
    /**
     * Gets the view root associated with the View.
     * @return The view root, or null if none.
@@ -13046,8 +13064,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
@@ -13075,8 +13095,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    }
@@ -13095,8 +13117,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            attachInfo.mViewRootImpl.mChoreographer.postCallback(
                    Choreographer.CALLBACK_ANIMATION, action, null);
        } else {
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().post(action);
            // Postpone the runnable until we know
            // on which thread it needs to run.
            getRunQueue().post(action);
        }
    }
@@ -13118,8 +13141,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                    Choreographer.CALLBACK_ANIMATION, action, null, delayMillis);
        } else {
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
            // Postpone the runnable until we know
            // on which thread it needs to run.
            getRunQueue().postDelayed(action, delayMillis);
        }
    }
@@ -13146,8 +13170,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_ANIMATION, action, null);
            }
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().removeCallbacks(action);
            getRunQueue().removeCallbacks(action);
        }
        return true;
    }
@@ -14565,7 +14588,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     *        this view
     */
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
@@ -14581,6 +14603,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
@@ -16866,7 +16893,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                        Choreographer.CALLBACK_ANIMATION, what, who,
                        Choreographer.subtractFrameDelay(delay));
            } else {
                ViewRootImpl.getRunQueue().postDelayed(what, delay);
                // Postpone the runnable until we know
                // on which thread it needs to run.
                getRunQueue().postDelayed(what, delay);
            }
        }
    }
@@ -16884,7 +16913,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_ANIMATION, what, who);
            }
            ViewRootImpl.getRunQueue().removeCallbacks(what);
            getRunQueue().removeCallbacks(what);
        }
    }
+4 −78
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
@@ -131,7 +130,7 @@ public final class ViewRootImpl implements ViewParent,
     */
    static final int MAX_TRACKBALL_DELAY = 250;

    static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
    static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();

    static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
    static boolean sFirstDrawComplete = false;
@@ -6759,89 +6758,16 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    static RunQueue getRunQueue() {
        RunQueue rq = sRunQueues.get();
    static HandlerActionQueue getRunQueue() {
        HandlerActionQueue rq = sRunQueues.get();
        if (rq != null) {
            return rq;
        }
        rq = new RunQueue();
        rq = new HandlerActionQueue();
        sRunQueues.set(rq);
        return rq;
    }

    /**
     * The run queue is used to enqueue pending work from Views when no Handler is
     * attached.  The work is executed during the next call to performTraversals on
     * the thread.
     * @hide
     */
    static final class RunQueue {
        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

        void post(Runnable action) {
            postDelayed(action, 0);
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }

        void removeCallbacks(Runnable action) {
            final HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;

            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;

                while (actions.remove(handlerAction)) {
                    // Keep going
                }
            }
        }

        void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

        private static class HandlerAction {
            Runnable action;
            long delay;

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

                HandlerAction that = (HandlerAction) o;
                return !(action != null ? !action.equals(that.action) : that.action != null);

            }

            @Override
            public int hashCode() {
                int result = action != null ? action.hashCode() : 0;
                result = 31 * result + (int) (delay ^ (delay >>> 32));
                return result;
            }
        }
    }

    /**
     * Class for managing the accessibility interaction connection
     * based on the global accessibility state.
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

public class HandlerActionQueueTest extends AndroidTestCase {

    @SmallTest
    public void testPostAndRemove() {
        HandlerActionQueue runQueue = new HandlerActionQueue();
        MockRunnable runnable1 = new MockRunnable();
        MockRunnable runnable2 = new MockRunnable();
        MockRunnable runnable3 = new MockRunnable();

        runQueue.post(runnable1);
        runQueue.post(runnable1);
        runQueue.post(runnable2);
        runQueue.postDelayed(runnable1, 100);
        runQueue.postDelayed(null, 500);
        assertEquals(5, runQueue.size());
        assertEquals(0, runQueue.getDelay(0));
        assertEquals(0, runQueue.getDelay(1));
        assertEquals(0, runQueue.getDelay(2));
        assertEquals(100, runQueue.getDelay(3));
        assertEquals(500, runQueue.getDelay(4));
        assertEquals(500, runQueue.getDelay(4));
        assertEquals(runnable1, runQueue.getRunnable(0));
        assertEquals(runnable1, runQueue.getRunnable(1));
        assertEquals(runnable2, runQueue.getRunnable(2));
        assertEquals(runnable1, runQueue.getRunnable(3));
        assertEquals(null, runQueue.getRunnable(4));

        runQueue.removeCallbacks(runnable1);
        assertEquals(2, runQueue.size());
        assertEquals(0, runQueue.getDelay(0));
        assertEquals(500, runQueue.getDelay(1));
        assertEquals(runnable2, runQueue.getRunnable(0));
        assertEquals(null, runQueue.getRunnable(1));

        try {
            assertNull(runQueue.getRunnable(2));
            assertFalse(true);
        } catch (IndexOutOfBoundsException e) {
            // Should throw an exception.
        }

        runQueue.removeCallbacks(runnable3);
        assertEquals(2, runQueue.size());

        runQueue.removeCallbacks(runnable2);
        assertEquals(1, runQueue.size());
        assertEquals(null, runQueue.getRunnable(0));

        runQueue.removeCallbacks(null);
        assertEquals(0, runQueue.size());
    }

    private static class MockRunnable implements Runnable {
        @Override
        public void run() {

        }
    }
}