1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.timeout; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.verifyZeroInteractions; 27 28 import android.os.SystemClock; 29 import android.view.Choreographer; 30 31 import androidx.test.annotation.UiThreadTest; 32 import androidx.test.filters.MediumTest; 33 import androidx.test.runner.AndroidJUnit4; 34 35 import org.junit.Before; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.mockito.ArgumentCaptor; 39 40 @MediumTest 41 @RunWith(AndroidJUnit4.class) 42 public class ChoreographerTest { 43 private static final long NOMINAL_VSYNC_PERIOD = 16; 44 private static final long DELAY_PERIOD = NOMINAL_VSYNC_PERIOD * 5; 45 private static final long NANOS_PER_MS = 1000000; 46 private static final Object TOKEN = new Object(); 47 48 private Choreographer mChoreographer; 49 50 @UiThreadTest 51 @Before 52 public void setup() { 53 mChoreographer = Choreographer.getInstance(); 54 } 55 56 @Test 57 public void testFrameDelay() { 58 assertTrue(Choreographer.getFrameDelay() > 0); 59 60 long oldFrameDelay = Choreographer.getFrameDelay(); 61 long newFrameDelay = oldFrameDelay * 2; 62 Choreographer.setFrameDelay(newFrameDelay); 63 assertEquals(newFrameDelay, Choreographer.getFrameDelay()); 64 65 Choreographer.setFrameDelay(oldFrameDelay); 66 } 67 68 @Test 69 public void testPostCallbackWithoutDelay() { 70 final Runnable addedCallback1 = mock(Runnable.class); 71 final Runnable addedCallback2 = mock(Runnable.class); 72 final Runnable removedCallback = mock(Runnable.class); 73 try { 74 // Add and remove a few callbacks. 75 mChoreographer.postCallback( 76 Choreographer.CALLBACK_ANIMATION, addedCallback1, null); 77 mChoreographer.postCallback( 78 Choreographer.CALLBACK_ANIMATION, addedCallback2, null); 79 mChoreographer.postCallback( 80 Choreographer.CALLBACK_ANIMATION, removedCallback, null); 81 mChoreographer.removeCallbacks( 82 Choreographer.CALLBACK_ANIMATION, removedCallback, null); 83 84 // We expect the remaining callbacks to have been invoked once. 85 verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 30).times(1)).run(); 86 verify(addedCallback2, timeout(NOMINAL_VSYNC_PERIOD * 30).times(1)).run(); 87 verifyZeroInteractions(removedCallback); 88 89 // If we post a callback again, then it should be invoked again. 90 mChoreographer.postCallback( 91 Choreographer.CALLBACK_ANIMATION, addedCallback1, null); 92 93 verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 30).times(2)).run(); 94 verify(addedCallback2, times(1)).run(); 95 verifyZeroInteractions(removedCallback); 96 97 // If the token matches, the the callback should be removed. 98 mChoreographer.postCallback( 99 Choreographer.CALLBACK_ANIMATION, addedCallback1, null); 100 mChoreographer.postCallback( 101 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN); 102 mChoreographer.removeCallbacks( 103 Choreographer.CALLBACK_ANIMATION, null, TOKEN); 104 verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 30).times(3)).run(); 105 verifyZeroInteractions(removedCallback); 106 107 // If the action and token matches, then the callback should be removed. 108 // If only the token matches, then the callback should not be removed. 109 mChoreographer.postCallback( 110 Choreographer.CALLBACK_ANIMATION, addedCallback1, TOKEN); 111 mChoreographer.postCallback( 112 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN); 113 mChoreographer.removeCallbacks( 114 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN); 115 verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 30).times(4)).run(); 116 verifyZeroInteractions(removedCallback); 117 } finally { 118 mChoreographer.removeCallbacks( 119 Choreographer.CALLBACK_ANIMATION, addedCallback1, null); 120 mChoreographer.removeCallbacks( 121 Choreographer.CALLBACK_ANIMATION, addedCallback2, null); 122 mChoreographer.removeCallbacks( 123 Choreographer.CALLBACK_ANIMATION, removedCallback, null); 124 } 125 } 126 127 @Test 128 public void testPostCallbackWithDelay() { 129 final Runnable addedCallback = mock(Runnable.class); 130 final Runnable removedCallback = mock(Runnable.class); 131 try { 132 // Add and remove a few callbacks. 133 mChoreographer.postCallbackDelayed( 134 Choreographer.CALLBACK_ANIMATION, addedCallback, null, DELAY_PERIOD); 135 mChoreographer.postCallbackDelayed( 136 Choreographer.CALLBACK_ANIMATION, removedCallback, null, DELAY_PERIOD); 137 mChoreographer.removeCallbacks( 138 Choreographer.CALLBACK_ANIMATION, removedCallback, null); 139 140 // Sleep for a couple of frames. 141 SystemClock.sleep(NOMINAL_VSYNC_PERIOD * 3); 142 143 // The callbacks should not have been invoked yet because of the delay. 144 verifyZeroInteractions(addedCallback); 145 verifyZeroInteractions(removedCallback); 146 147 // We expect the remaining callbacks to have been invoked. 148 verify(addedCallback, timeout(DELAY_PERIOD * 3).times(1)).run(); 149 verifyZeroInteractions(removedCallback); 150 151 // If the token matches, the the callback should be removed. 152 mChoreographer.postCallbackDelayed( 153 Choreographer.CALLBACK_ANIMATION, addedCallback, null, DELAY_PERIOD); 154 mChoreographer.postCallbackDelayed( 155 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN, DELAY_PERIOD); 156 mChoreographer.removeCallbacks( 157 Choreographer.CALLBACK_ANIMATION, null, TOKEN); 158 verify(addedCallback, timeout(DELAY_PERIOD * 3).times(2)).run(); 159 verifyZeroInteractions(removedCallback); 160 161 // If the action and token matches, then the callback should be removed. 162 // If only the token matches, then the callback should not be removed. 163 mChoreographer.postCallbackDelayed( 164 Choreographer.CALLBACK_ANIMATION, addedCallback, TOKEN, DELAY_PERIOD); 165 mChoreographer.postCallbackDelayed( 166 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN, DELAY_PERIOD); 167 mChoreographer.removeCallbacks( 168 Choreographer.CALLBACK_ANIMATION, removedCallback, TOKEN); 169 verify(addedCallback, timeout(DELAY_PERIOD * 3).times(3)).run(); 170 verifyZeroInteractions(removedCallback); 171 } finally { 172 mChoreographer.removeCallbacks( 173 Choreographer.CALLBACK_ANIMATION, addedCallback, null); 174 mChoreographer.removeCallbacks( 175 Choreographer.CALLBACK_ANIMATION, removedCallback, null); 176 } 177 } 178 179 @Test(expected=IllegalArgumentException.class) 180 public void testPostNullCallback() { 181 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, null, TOKEN); 182 } 183 184 @Test(expected=IllegalArgumentException.class) 185 public void testPostNullCallbackDelayed() { 186 mChoreographer.postCallbackDelayed( Choreographer.CALLBACK_ANIMATION, null, TOKEN, 187 DELAY_PERIOD); 188 } 189 190 @Test 191 public void testPostFrameCallbackWithoutDelay() { 192 final Choreographer.FrameCallback addedFrameCallback1 = 193 mock(Choreographer.FrameCallback.class); 194 final Choreographer.FrameCallback addedFrameCallback2 = 195 mock(Choreographer.FrameCallback.class); 196 final Choreographer.FrameCallback removedFrameCallback = 197 mock(Choreographer.FrameCallback.class); 198 try { 199 // Add and remove a few callbacks. 200 long postTimeNanos = System.nanoTime(); 201 mChoreographer.postFrameCallback(addedFrameCallback1); 202 mChoreographer.postFrameCallback(addedFrameCallback2); 203 mChoreographer.postFrameCallback(removedFrameCallback); 204 mChoreographer.removeFrameCallback(removedFrameCallback); 205 206 // We expect the remaining callbacks to have been invoked once. 207 ArgumentCaptor<Long> frameTimeNanosCaptor1 = ArgumentCaptor.forClass(Long.class); 208 ArgumentCaptor<Long> frameTimeNanosCaptor2 = ArgumentCaptor.forClass(Long.class); 209 verify(addedFrameCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)) 210 .doFrame(frameTimeNanosCaptor1.capture()); 211 verify(addedFrameCallback2, times(1)).doFrame(frameTimeNanosCaptor2.capture()); 212 verifyZeroInteractions(removedFrameCallback); 213 214 assertTimeDeltaLessThan(frameTimeNanosCaptor1.getValue() - postTimeNanos, 215 NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS); 216 assertTimeDeltaLessThan(frameTimeNanosCaptor2.getValue() - postTimeNanos, 217 NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS); 218 assertTimeDeltaLessThan( 219 Math.abs(frameTimeNanosCaptor2.getValue() - frameTimeNanosCaptor1.getValue()), 220 NOMINAL_VSYNC_PERIOD * NANOS_PER_MS); 221 222 // If we post a callback again, then it should be invoked again. 223 postTimeNanos = System.nanoTime(); 224 mChoreographer.postFrameCallback(addedFrameCallback1); 225 226 verify(addedFrameCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(2)) 227 .doFrame(frameTimeNanosCaptor1.capture()); 228 verify(addedFrameCallback2, times(1)).doFrame(frameTimeNanosCaptor2.capture()); 229 verifyZeroInteractions(removedFrameCallback); 230 assertTimeDeltaLessThan(frameTimeNanosCaptor1.getAllValues().get(1) - postTimeNanos, 231 NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS); 232 } finally { 233 mChoreographer.removeFrameCallback(addedFrameCallback1); 234 mChoreographer.removeFrameCallback(addedFrameCallback2); 235 mChoreographer.removeFrameCallback(removedFrameCallback); 236 } 237 } 238 239 @Test 240 public void testPostFrameCallbackWithDelay() { 241 final Choreographer.FrameCallback addedFrameCallback = 242 mock(Choreographer.FrameCallback.class); 243 final Choreographer.FrameCallback removedFrameCallback = 244 mock(Choreographer.FrameCallback.class); 245 try { 246 // Add and remove a few callbacks. 247 long postTimeNanos = System.nanoTime(); 248 mChoreographer.postFrameCallbackDelayed(addedFrameCallback, DELAY_PERIOD); 249 mChoreographer.postFrameCallbackDelayed(removedFrameCallback, DELAY_PERIOD); 250 mChoreographer.removeFrameCallback(removedFrameCallback); 251 252 // Sleep for a couple of frames. 253 SystemClock.sleep(NOMINAL_VSYNC_PERIOD * 3); 254 255 // The callbacks should not have been invoked yet because of the delay. 256 verifyZeroInteractions(addedFrameCallback); 257 verifyZeroInteractions(removedFrameCallback); 258 259 // We expect the remaining callbacks to have been invoked. 260 ArgumentCaptor<Long> frameTimeNanosCaptor = ArgumentCaptor.forClass(Long.class); 261 verify(addedFrameCallback, timeout(DELAY_PERIOD * 3).times(1)) 262 .doFrame(frameTimeNanosCaptor.capture()); 263 verifyZeroInteractions(removedFrameCallback); 264 assertTimeDeltaLessThan(frameTimeNanosCaptor.getValue() - postTimeNanos, 265 (NOMINAL_VSYNC_PERIOD * 10 + DELAY_PERIOD) * NANOS_PER_MS); 266 } finally { 267 mChoreographer.removeFrameCallback(addedFrameCallback); 268 mChoreographer.removeFrameCallback(removedFrameCallback); 269 } 270 } 271 272 private void assertTimeDeltaLessThan(long deltaNanos, long thresholdNanos) { 273 if (deltaNanos >= thresholdNanos) { 274 fail("Expected time delta less than " + thresholdNanos + " nanos, actually " 275 + " was " + deltaNanos + " nanos."); 276 } 277 } 278 279 @Test(expected=IllegalArgumentException.class) 280 public void testPostNullFrameCallback() { 281 mChoreographer.postFrameCallback(null); 282 } 283 284 @Test(expected=IllegalArgumentException.class) 285 public void testPostNullFrameCallbackDelayed() { 286 mChoreographer.postFrameCallbackDelayed(null, DELAY_PERIOD); 287 } 288 289 @Test(expected=IllegalArgumentException.class) 290 public void testRemoveNullFrameCallback() { 291 mChoreographer.removeFrameCallback(null); 292 } 293 } 294