Implement Choreographer.removeFrameCallback()
diff --git a/robolectric-shadows/shadows-core/src/main/java/org/robolectric/shadows/ShadowChoreographer.java b/robolectric-shadows/shadows-core/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
index 04625bd..51f2c02 100644
--- a/robolectric-shadows/shadows-core/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
+++ b/robolectric-shadows/shadows-core/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
@@ -41,7 +41,7 @@
 
   @Implementation
   public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) {
-    ShadowApplication.getInstance().getForegroundThreadScheduler().postDelayed(action, delayMillis);
+    ShadowApplication.getInstance().getForegroundThreadScheduler().postDelayed(action, delayMillis, action);
   }
 
   @Implementation
@@ -51,7 +51,12 @@
       public void run() {
         callback.doFrame(getFrameTimeNanos());
       }
-    }, delayMillis);
+    }, delayMillis, callback);
+  }
+
+  @Implementation
+  public void removeFrameCallback(Choreographer.FrameCallback callback) {
+    ShadowApplication.getInstance().getForegroundThreadScheduler().removeWithToken(callback);
   }
 
   @Implementation
diff --git a/robolectric-utils/src/main/java/org/robolectric/util/Scheduler.java b/robolectric-utils/src/main/java/org/robolectric/util/Scheduler.java
index 3f773b4..f298525 100644
--- a/robolectric-utils/src/main/java/org/robolectric/util/Scheduler.java
+++ b/robolectric-utils/src/main/java/org/robolectric/util/Scheduler.java
@@ -69,7 +69,17 @@
    * @param runnable    Runnable to add.
    */
   public synchronized void post(Runnable runnable) {
-    postDelayed(runnable, 0);
+    post(runnable, null);
+  }
+
+  /**
+   * Add a runnable to the queue.
+   *
+   * @param runnable    Runnable to add.
+   * @param token       Token for runnable.
+   */
+  public synchronized void post(Runnable runnable, Object token) {
+    postDelayed(runnable, 0, token);
   }
 
   /**
@@ -79,10 +89,21 @@
    * @param delayMillis Delay in millis.
    */
   public synchronized void postDelayed(Runnable runnable, long delayMillis) {
+    postDelayed(runnable, delayMillis, null);
+  }
+
+  /**
+   * Add a runnable to the queue to be run after a delay.
+   *
+   * @param runnable    Runnable to add.
+   * @param delayMillis Delay in millis.
+   * @param token       Token for runnable.
+   */
+  public synchronized void postDelayed(Runnable runnable, long delayMillis, Object token) {
     if ((!isConstantlyIdling && (paused || delayMillis > 0)) || Thread.currentThread() != associatedThread) {
-      queueRunnableAndSort(runnable, currentTime + delayMillis);
+      queueRunnableAndSort(runnable, currentTime + delayMillis, token);
     } else {
-      runOrQueueRunnable(runnable, currentTime + delayMillis);
+      runOrQueueRunnable(runnable, currentTime + delayMillis, token);
     }
   }
 
@@ -92,10 +113,20 @@
    * @param runnable  Runnable to add.
    */
   public synchronized void postAtFrontOfQueue(Runnable runnable) {
+    postAtFrontOfQueue(runnable, null);
+  }
+
+  /**
+   * Add a runnable to the head of the queue.
+   *
+   * @param runnable  Runnable to add.
+   * @param token       Token for runnable.
+   */
+  public synchronized void postAtFrontOfQueue(Runnable runnable, Object token) {
     if (paused || Thread.currentThread() != associatedThread) {
       runnables.add(0, new ScheduledRunnable(runnable, currentTime));
     } else {
-      runOrQueueRunnable(runnable, currentTime);
+      runOrQueueRunnable(runnable, currentTime, token);
     }
   }
 
@@ -115,6 +146,22 @@
   }
 
   /**
+   * Remove a runnable from the queue with the specified token.
+   *
+   * @param token Token to remove.
+   */
+  public synchronized void removeWithToken(Object token) {
+    if (token == null) throw new NullPointerException("Token must not be null");
+    ListIterator<ScheduledRunnable> iterator = runnables.listIterator();
+    while (iterator.hasNext()) {
+      ScheduledRunnable next = iterator.next();
+      if (next.token == token) {
+        iterator.remove();
+      }
+    }
+  }
+
+  /**
    * Run all runnables in the queue.
    *
    * @return  True if a runnable was executed.
@@ -220,9 +267,9 @@
     return size() > 0 && runnables.get(0).scheduledTime <= endingTime;
   }
 
-  private void runOrQueueRunnable(Runnable runnable, long scheduledTime) {
+  private void runOrQueueRunnable(Runnable runnable, long scheduledTime, Object token) {
     if (isExecutingRunnable) {
-      queueRunnableAndSort(runnable, scheduledTime);
+      queueRunnableAndSort(runnable, scheduledTime, token);
       return;
     }
     isExecutingRunnable = true;
@@ -244,18 +291,26 @@
     }
   }
 
-  private void queueRunnableAndSort(Runnable runnable, long scheduledTime) {
-    runnables.add(new ScheduledRunnable(runnable, scheduledTime));
+  private void queueRunnableAndSort(Runnable runnable, long scheduledTime, Object token) {
+    runnables.add(new ScheduledRunnable(runnable, scheduledTime, token));
     Collections.sort(runnables);
   }
 
   private class ScheduledRunnable implements Comparable<ScheduledRunnable> {
     private final Runnable runnable;
     private final long scheduledTime;
+    private final Object token;
 
     private ScheduledRunnable(Runnable runnable, long scheduledTime) {
       this.runnable = runnable;
       this.scheduledTime = scheduledTime;
+      token = null;
+    }
+
+    private ScheduledRunnable(Runnable runnable, long scheduledTime, Object token) {
+      this.runnable = runnable;
+      this.scheduledTime = scheduledTime;
+      this.token = token;
     }
 
     @Override
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
index 3e01830..0f24fd6 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
@@ -3,10 +3,15 @@
 import android.view.Choreographer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 import org.robolectric.TestRunners;
 import org.robolectric.util.TimeUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
 @RunWith(TestRunners.WithDefaults.class)
 public class ShadowChoreographerTest {
@@ -24,6 +29,16 @@
   }
 
   @Test
+  public void removeFrameCallback_shouldRemoveCallback() {
+    Choreographer instance = ShadowChoreographer.getInstance();
+    Choreographer.FrameCallback callback = mock(Choreographer.FrameCallback.class);
+    instance.postFrameCallbackDelayed(callback, 1000);
+    instance.removeFrameCallback(callback);
+    ShadowApplication.getInstance().getForegroundThreadScheduler().advanceToLastPostedRunnable();
+    verify(callback, never()).doFrame(anyInt());
+  }
+
+  @Test
   public void reset_shouldResetFrameInterval() {
     ShadowChoreographer.setFrameInterval(1);
     assertThat(ShadowChoreographer.getFrameInterval()).isEqualTo(1);
@@ -31,4 +46,4 @@
     ShadowChoreographer.reset();
     assertThat(ShadowChoreographer.getFrameInterval()).isEqualTo(10 * TimeUtils.NANOS_PER_MS);
   }
-}
\ No newline at end of file
+}
diff --git a/robolectric/src/test/java/org/robolectric/util/SchedulerTest.java b/robolectric/src/test/java/org/robolectric/util/SchedulerTest.java
index 010ecf2..5299e53 100644
--- a/robolectric/src/test/java/org/robolectric/util/SchedulerTest.java
+++ b/robolectric/src/test/java/org/robolectric/util/SchedulerTest.java
@@ -140,6 +140,17 @@
   }
 
   @Test
+  public void removeWithToken_ShouldRemoveAllInstancesOfRunnableFromQueue() {
+    Object token = new Object();
+    scheduler.post(new TestRunnable(), token);
+    scheduler.post(new TestRunnable(), token);
+    scheduler.post(new TestRunnable());
+    assertThat(scheduler.size()).isEqualTo(3);
+    scheduler.removeWithToken(token);
+    assertThat(scheduler.size()).isEqualTo(1);
+  }
+
+  @Test
   public void reset_shouldUnPause() throws Exception {
     scheduler.pause();