summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/HandlerActionQueue.java127
-rw-r--r--core/java/android/view/View.java55
-rw-r--r--core/java/android/view/ViewRootImpl.java82
-rw-r--r--core/tests/coretests/src/android/view/HandlerActionQueueTest.java80
4 files changed, 253 insertions, 91 deletions
diff --git a/core/java/android/view/HandlerActionQueue.java b/core/java/android/view/HandlerActionQueue.java
new file mode 100644
index 000000000000..4758a3408dc2
--- /dev/null
+++ b/core/java/android/view/HandlerActionQueue.java
@@ -0,0 +1,127 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 16d9bf398673..665069c6a1ae 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3823,6 +3823,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
*/
String mStartActivityRequestWho;
@@ -13010,6 +13016,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * 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.
* @hide
@@ -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);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8bbaf36d4094..d26e91408666 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -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,90 +6758,17 @@ 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.
*/
diff --git a/core/tests/coretests/src/android/view/HandlerActionQueueTest.java b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java
new file mode 100644
index 000000000000..fd8f23abc45c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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() {
+
+ }
+ }
+}