Choreographer.java revision 0e40462e11d27eb859b829b112cecb8c6f0d7afb
1/*
2 * Copyright (C) 2011 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
17package android.view;
18
19import android.hardware.display.DisplayManagerGlobal;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.SystemClock;
24import android.os.SystemProperties;
25import android.util.Log;
26import android.util.TimeUtils;
27
28import java.io.PrintWriter;
29
30/**
31 * Coordinates the timing of animations, input and drawing.
32 * <p>
33 * The choreographer receives timing pulses (such as vertical synchronization)
34 * from the display subsystem then schedules work to occur as part of rendering
35 * the next display frame.
36 * </p><p>
37 * Applications typically interact with the choreographer indirectly using
38 * higher level abstractions in the animation framework or the view hierarchy.
39 * Here are some examples of things you can do using the higher-level APIs.
40 * </p>
41 * <ul>
42 * <li>To post an animation to be processed on a regular time basis synchronized with
43 * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
44 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
45 * frame, use {@link View#postOnAnimation}.</li>
46 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
47 * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
48 * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
49 * next display frame, use {@link View#postInvalidateOnAnimation()} or
50 * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
51 * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
52 * sync with display frame rendering, do nothing.  This already happens automatically.
53 * {@link View#onDraw} will be called at the appropriate time.</li>
54 * </ul>
55 * <p>
56 * However, there are a few cases where you might want to use the functions of the
57 * choreographer directly in your application.  Here are some examples.
58 * </p>
59 * <ul>
60 * <li>If your application does its rendering in a different thread, possibly using GL,
61 * or does not use the animation framework or view hierarchy at all
62 * and you want to ensure that it is appropriately synchronized with the display, then use
63 * {@link Choreographer#postFrameCallback}.</li>
64 * <li>... and that's about it.</li>
65 * </ul>
66 * <p>
67 * Each {@link Looper} thread has its own choreographer.  Other threads can
68 * post callbacks to run on the choreographer but they will run on the {@link Looper}
69 * to which the choreographer belongs.
70 * </p>
71 */
72public final class Choreographer {
73    private static final String TAG = "Choreographer";
74    private static final boolean DEBUG = false;
75
76    // The default amount of time in ms between animation frames.
77    // When vsync is not enabled, we want to have some idea of how long we should
78    // wait before posting the next animation message.  It is important that the
79    // default value be less than the true inter-frame delay on all devices to avoid
80    // situations where we might skip frames by waiting too long (we must compensate
81    // for jitter and hardware variations).  Regardless of this value, the animation
82    // and display loop is ultimately rate-limited by how fast new graphics buffers can
83    // be dequeued.
84    private static final long DEFAULT_FRAME_DELAY = 10;
85
86    // The number of milliseconds between animation frames.
87    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
88
89    // Thread local storage for the choreographer.
90    private static final ThreadLocal<Choreographer> sThreadInstance =
91            new ThreadLocal<Choreographer>() {
92        @Override
93        protected Choreographer initialValue() {
94            Looper looper = Looper.myLooper();
95            if (looper == null) {
96                throw new IllegalStateException("The current thread must have a looper!");
97            }
98            return new Choreographer(looper);
99        }
100    };
101
102    // Enable/disable vsync for animations and drawing.
103    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
104            "debug.choreographer.vsync", true);
105
106    // Enable/disable using the frame time instead of returning now.
107    private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
108            "debug.choreographer.frametime", true);
109
110    // Set a limit to warn about skipped frames.
111    // Skipped frames imply jank.
112    private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
113            "debug.choreographer.skipwarning", 30);
114
115    private static final int MSG_DO_FRAME = 0;
116    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
117    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
118
119    // All frame callbacks posted by applications have this token.
120    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
121        public String toString() { return "FRAME_CALLBACK_TOKEN"; }
122    };
123
124    private final Object mLock = new Object();
125
126    private final Looper mLooper;
127    private final FrameHandler mHandler;
128
129    // The display event receiver can only be accessed by the looper thread to which
130    // it is attached.  We take care to ensure that we post message to the looper
131    // if appropriate when interacting with the display event receiver.
132    private final FrameDisplayEventReceiver mDisplayEventReceiver;
133
134    private CallbackRecord mCallbackPool;
135
136    private final CallbackQueue[] mCallbackQueues;
137
138    private boolean mFrameScheduled;
139    private boolean mCallbacksRunning;
140    private long mLastFrameTimeNanos;
141    private long mFrameIntervalNanos;
142
143    /**
144     * Callback type: Input callback.  Runs first.
145     * @hide
146     */
147    public static final int CALLBACK_INPUT = 0;
148
149    /**
150     * Callback type: Animation callback.  Runs before traversals.
151     * @hide
152     */
153    public static final int CALLBACK_ANIMATION = 1;
154
155    /**
156     * Callback type: Traversal callback.  Handles layout and draw.  Runs last
157     * after all other asynchronous messages have been handled.
158     * @hide
159     */
160    public static final int CALLBACK_TRAVERSAL = 2;
161
162    private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
163
164    private Choreographer(Looper looper) {
165        mLooper = looper;
166        mHandler = new FrameHandler(looper);
167        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
168        mLastFrameTimeNanos = Long.MIN_VALUE;
169
170        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
171
172        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
173        for (int i = 0; i <= CALLBACK_LAST; i++) {
174            mCallbackQueues[i] = new CallbackQueue();
175        }
176    }
177
178    private static float getRefreshRate() {
179        DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
180                Display.DEFAULT_DISPLAY);
181        return di.refreshRate;
182    }
183
184    /**
185     * Gets the choreographer for the calling thread.  Must be called from
186     * a thread that already has a {@link android.os.Looper} associated with it.
187     *
188     * @return The choreographer for this thread.
189     * @throws IllegalStateException if the thread does not have a looper.
190     */
191    public static Choreographer getInstance() {
192        return sThreadInstance.get();
193    }
194
195    /**
196     * The amount of time, in milliseconds, between each frame of the animation.
197     * <p>
198     * This is a requested time that the animation will attempt to honor, but the actual delay
199     * between frames may be different, depending on system load and capabilities. This is a static
200     * function because the same delay will be applied to all animations, since they are all
201     * run off of a single timing loop.
202     * </p><p>
203     * The frame delay may be ignored when the animation system uses an external timing
204     * source, such as the display refresh rate (vsync), to govern animations.
205     * </p>
206     *
207     * @return the requested time between frames, in milliseconds
208     * @hide
209     */
210    public static long getFrameDelay() {
211        return sFrameDelay;
212    }
213
214    /**
215     * The amount of time, in milliseconds, between each frame of the animation.
216     * <p>
217     * This is a requested time that the animation will attempt to honor, but the actual delay
218     * between frames may be different, depending on system load and capabilities. This is a static
219     * function because the same delay will be applied to all animations, since they are all
220     * run off of a single timing loop.
221     * </p><p>
222     * The frame delay may be ignored when the animation system uses an external timing
223     * source, such as the display refresh rate (vsync), to govern animations.
224     * </p>
225     *
226     * @param frameDelay the requested time between frames, in milliseconds
227     * @hide
228     */
229    public static void setFrameDelay(long frameDelay) {
230        sFrameDelay = frameDelay;
231    }
232
233    /**
234     * Subtracts typical frame delay time from a delay interval in milliseconds.
235     * <p>
236     * This method can be used to compensate for animation delay times that have baked
237     * in assumptions about the frame delay.  For example, it's quite common for code to
238     * assume a 60Hz frame time and bake in a 16ms delay.  When we call
239     * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
240     * posting the animation callback but let the animation timer take care of the remaining
241     * frame delay time.
242     * </p><p>
243     * This method is somewhat conservative about how much of the frame delay it
244     * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
245     * default is 10ms even though many parts of the system assume 16ms.  Consequently,
246     * we might still wait 6ms before posting an animation callback that we want to run
247     * on the next frame, but this is much better than waiting a whole 16ms and likely
248     * missing the deadline.
249     * </p>
250     *
251     * @param delayMillis The original delay time including an assumed frame delay.
252     * @return The adjusted delay time with the assumed frame delay subtracted out.
253     * @hide
254     */
255    public static long subtractFrameDelay(long delayMillis) {
256        final long frameDelay = sFrameDelay;
257        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
258    }
259
260    /**
261     * @return The refresh rate as the nanoseconds between frames
262     * @hide
263     */
264    public long getFrameIntervalNanos() {
265        return mFrameIntervalNanos;
266    }
267
268    void dump(String prefix, PrintWriter writer) {
269        String innerPrefix = prefix + "  ";
270        writer.print(prefix); writer.println("Choreographer:");
271        writer.print(innerPrefix); writer.print("mFrameScheduled=");
272                writer.println(mFrameScheduled);
273        writer.print(innerPrefix); writer.print("mLastFrameTime=");
274                writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
275    }
276
277    /**
278     * Posts a callback to run on the next frame.
279     * <p>
280     * The callback runs once then is automatically removed.
281     * </p>
282     *
283     * @param callbackType The callback type.
284     * @param action The callback action to run during the next frame.
285     * @param token The callback token, or null if none.
286     *
287     * @see #removeCallbacks
288     * @hide
289     */
290    public void postCallback(int callbackType, Runnable action, Object token) {
291        postCallbackDelayed(callbackType, action, token, 0);
292    }
293
294    /**
295     * Posts a callback to run on the next frame after the specified delay.
296     * <p>
297     * The callback runs once then is automatically removed.
298     * </p>
299     *
300     * @param callbackType The callback type.
301     * @param action The callback action to run during the next frame after the specified delay.
302     * @param token The callback token, or null if none.
303     * @param delayMillis The delay time in milliseconds.
304     *
305     * @see #removeCallback
306     * @hide
307     */
308    public void postCallbackDelayed(int callbackType,
309            Runnable action, Object token, long delayMillis) {
310        if (action == null) {
311            throw new IllegalArgumentException("action must not be null");
312        }
313        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
314            throw new IllegalArgumentException("callbackType is invalid");
315        }
316
317        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
318    }
319
320    private void postCallbackDelayedInternal(int callbackType,
321            Object action, Object token, long delayMillis) {
322        if (DEBUG) {
323            Log.d(TAG, "PostCallback: type=" + callbackType
324                    + ", action=" + action + ", token=" + token
325                    + ", delayMillis=" + delayMillis);
326        }
327
328        synchronized (mLock) {
329            final long now = SystemClock.uptimeMillis();
330            final long dueTime = now + delayMillis;
331            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
332
333            if (dueTime <= now) {
334                scheduleFrameLocked(now);
335            } else {
336                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
337                msg.arg1 = callbackType;
338                msg.setAsynchronous(true);
339                mHandler.sendMessageAtTime(msg, dueTime);
340            }
341        }
342    }
343
344    /**
345     * Removes callbacks that have the specified action and token.
346     *
347     * @param callbackType The callback type.
348     * @param action The action property of the callbacks to remove, or null to remove
349     * callbacks with any action.
350     * @param token The token property of the callbacks to remove, or null to remove
351     * callbacks with any token.
352     *
353     * @see #postCallback
354     * @see #postCallbackDelayed
355     * @hide
356     */
357    public void removeCallbacks(int callbackType, Runnable action, Object token) {
358        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
359            throw new IllegalArgumentException("callbackType is invalid");
360        }
361
362        removeCallbacksInternal(callbackType, action, token);
363    }
364
365    private void removeCallbacksInternal(int callbackType, Object action, Object token) {
366        if (DEBUG) {
367            Log.d(TAG, "RemoveCallbacks: type=" + callbackType
368                    + ", action=" + action + ", token=" + token);
369        }
370
371        synchronized (mLock) {
372            mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
373            if (action != null && token == null) {
374                mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
375            }
376        }
377    }
378
379    /**
380     * Posts a frame callback to run on the next frame.
381     * <p>
382     * The callback runs once then is automatically removed.
383     * </p>
384     *
385     * @param callback The frame callback to run during the next frame.
386     *
387     * @see #postFrameCallbackDelayed
388     * @see #removeFrameCallback
389     */
390    public void postFrameCallback(FrameCallback callback) {
391        postFrameCallbackDelayed(callback, 0);
392    }
393
394    /**
395     * Posts a frame callback to run on the next frame after the specified delay.
396     * <p>
397     * The callback runs once then is automatically removed.
398     * </p>
399     *
400     * @param callback The frame callback to run during the next frame.
401     * @param delayMillis The delay time in milliseconds.
402     *
403     * @see #postFrameCallback
404     * @see #removeFrameCallback
405     */
406    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
407        if (callback == null) {
408            throw new IllegalArgumentException("callback must not be null");
409        }
410
411        postCallbackDelayedInternal(CALLBACK_ANIMATION,
412                callback, FRAME_CALLBACK_TOKEN, delayMillis);
413    }
414
415    /**
416     * Removes a previously posted frame callback.
417     *
418     * @param callback The frame callback to remove.
419     *
420     * @see #postFrameCallback
421     * @see #postFrameCallbackDelayed
422     */
423    public void removeFrameCallback(FrameCallback callback) {
424        if (callback == null) {
425            throw new IllegalArgumentException("callback must not be null");
426        }
427
428        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
429    }
430
431    /**
432     * Gets the time when the current frame started.
433     * <p>
434     * This method provides the time in milliseconds when the frame started being rendered.
435     * The frame time provides a stable time base for synchronizing animations
436     * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
437     * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
438     * time helps to reduce inter-frame jitter because the frame time is fixed at the time
439     * the frame was scheduled to start, regardless of when the animations or drawing
440     * callback actually runs.  All callbacks that run as part of rendering a frame will
441     * observe the same frame time so using the frame time also helps to synchronize effects
442     * that are performed by different callbacks.
443     * </p><p>
444     * Please note that the framework already takes care to process animations and
445     * drawing using the frame time as a stable time base.  Most applications should
446     * not need to use the frame time information directly.
447     * </p><p>
448     * This method should only be called from within a callback.
449     * </p>
450     *
451     * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
452     *
453     * @throws IllegalStateException if no frame is in progress.
454     * @hide
455     */
456    public long getFrameTime() {
457        return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
458    }
459
460    /**
461     * Same as {@link #getFrameTime()} but with nanosecond precision.
462     *
463     * @return The frame start time, in the {@link System#nanoTime()} time base.
464     *
465     * @throws IllegalStateException if no frame is in progress.
466     * @hide
467     */
468    public long getFrameTimeNanos() {
469        synchronized (mLock) {
470            if (!mCallbacksRunning) {
471                throw new IllegalStateException("This method must only be called as "
472                        + "part of a callback while a frame is in progress.");
473            }
474            return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
475        }
476    }
477
478    private void scheduleFrameLocked(long now) {
479        if (!mFrameScheduled) {
480            mFrameScheduled = true;
481            if (USE_VSYNC) {
482                if (DEBUG) {
483                    Log.d(TAG, "Scheduling next frame on vsync.");
484                }
485
486                // If running on the Looper thread, then schedule the vsync immediately,
487                // otherwise post a message to schedule the vsync from the UI thread
488                // as soon as possible.
489                if (isRunningOnLooperThreadLocked()) {
490                    scheduleVsyncLocked();
491                } else {
492                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
493                    msg.setAsynchronous(true);
494                    mHandler.sendMessageAtFrontOfQueue(msg);
495                }
496            } else {
497                final long nextFrameTime = Math.max(
498                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
499                if (DEBUG) {
500                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
501                }
502                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
503                msg.setAsynchronous(true);
504                mHandler.sendMessageAtTime(msg, nextFrameTime);
505            }
506        }
507    }
508
509    void doFrame(long frameTimeNanos, int frame) {
510        final long startNanos;
511        synchronized (mLock) {
512            if (!mFrameScheduled) {
513                return; // no work to do
514            }
515
516            startNanos = System.nanoTime();
517            final long jitterNanos = startNanos - frameTimeNanos;
518            if (jitterNanos >= mFrameIntervalNanos) {
519                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
520                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
521                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
522                            + "The application may be doing too much work on its main thread.");
523                }
524                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
525                if (DEBUG) {
526                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
527                            + "which is more than the frame interval of "
528                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
529                            + "Skipping " + skippedFrames + " frames and setting frame "
530                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
531                }
532                frameTimeNanos = startNanos - lastFrameOffset;
533            }
534
535            if (frameTimeNanos < mLastFrameTimeNanos) {
536                if (DEBUG) {
537                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
538                            + "previously skipped frame.  Waiting for next vsync.");
539                }
540                scheduleVsyncLocked();
541                return;
542            }
543
544            mFrameScheduled = false;
545            mLastFrameTimeNanos = frameTimeNanos;
546        }
547
548        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
549        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
550        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
551
552        if (DEBUG) {
553            final long endNanos = System.nanoTime();
554            Log.d(TAG, "Frame " + frame + ": Finished, took "
555                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
556                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
557        }
558    }
559
560    void doCallbacks(int callbackType, long frameTimeNanos) {
561        CallbackRecord callbacks;
562        synchronized (mLock) {
563            // We use "now" to determine when callbacks become due because it's possible
564            // for earlier processing phases in a frame to post callbacks that should run
565            // in a following phase, such as an input event that causes an animation to start.
566            final long now = SystemClock.uptimeMillis();
567            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
568            if (callbacks == null) {
569                return;
570            }
571            mCallbacksRunning = true;
572        }
573        try {
574            for (CallbackRecord c = callbacks; c != null; c = c.next) {
575                if (DEBUG) {
576                    Log.d(TAG, "RunCallback: type=" + callbackType
577                            + ", action=" + c.action + ", token=" + c.token
578                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
579                }
580                c.run(frameTimeNanos);
581            }
582        } finally {
583            synchronized (mLock) {
584                mCallbacksRunning = false;
585                do {
586                    final CallbackRecord next = callbacks.next;
587                    recycleCallbackLocked(callbacks);
588                    callbacks = next;
589                } while (callbacks != null);
590            }
591        }
592    }
593
594    void doScheduleVsync() {
595        synchronized (mLock) {
596            if (mFrameScheduled) {
597                scheduleVsyncLocked();
598            }
599        }
600    }
601
602    void doScheduleCallback(int callbackType) {
603        synchronized (mLock) {
604            if (!mFrameScheduled) {
605                final long now = SystemClock.uptimeMillis();
606                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
607                    scheduleFrameLocked(now);
608                }
609            }
610        }
611    }
612
613    private void scheduleVsyncLocked() {
614        mDisplayEventReceiver.scheduleVsync();
615    }
616
617    private boolean isRunningOnLooperThreadLocked() {
618        return Looper.myLooper() == mLooper;
619    }
620
621    private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
622        CallbackRecord callback = mCallbackPool;
623        if (callback == null) {
624            callback = new CallbackRecord();
625        } else {
626            mCallbackPool = callback.next;
627            callback.next = null;
628        }
629        callback.dueTime = dueTime;
630        callback.action = action;
631        callback.token = token;
632        return callback;
633    }
634
635    private void recycleCallbackLocked(CallbackRecord callback) {
636        callback.action = null;
637        callback.token = null;
638        callback.next = mCallbackPool;
639        mCallbackPool = callback;
640    }
641
642    /**
643     * Implement this interface to receive a callback when a new display frame is
644     * being rendered.  The callback is invoked on the {@link Looper} thread to
645     * which the {@link Choreographer} is attached.
646     */
647    public interface FrameCallback {
648        /**
649         * Called when a new display frame is being rendered.
650         * <p>
651         * This method provides the time in nanoseconds when the frame started being rendered.
652         * The frame time provides a stable time base for synchronizing animations
653         * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
654         * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
655         * time helps to reduce inter-frame jitter because the frame time is fixed at the time
656         * the frame was scheduled to start, regardless of when the animations or drawing
657         * callback actually runs.  All callbacks that run as part of rendering a frame will
658         * observe the same frame time so using the frame time also helps to synchronize effects
659         * that are performed by different callbacks.
660         * </p><p>
661         * Please note that the framework already takes care to process animations and
662         * drawing using the frame time as a stable time base.  Most applications should
663         * not need to use the frame time information directly.
664         * </p>
665         *
666         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
667         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
668         * to convert it to the {@link SystemClock#uptimeMillis()} time base.
669         */
670        public void doFrame(long frameTimeNanos);
671    }
672
673    private final class FrameHandler extends Handler {
674        public FrameHandler(Looper looper) {
675            super(looper);
676        }
677
678        @Override
679        public void handleMessage(Message msg) {
680            switch (msg.what) {
681                case MSG_DO_FRAME:
682                    doFrame(System.nanoTime(), 0);
683                    break;
684                case MSG_DO_SCHEDULE_VSYNC:
685                    doScheduleVsync();
686                    break;
687                case MSG_DO_SCHEDULE_CALLBACK:
688                    doScheduleCallback(msg.arg1);
689                    break;
690            }
691        }
692    }
693
694    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
695            implements Runnable {
696        private boolean mHavePendingVsync;
697        private long mTimestampNanos;
698        private int mFrame;
699
700        public FrameDisplayEventReceiver(Looper looper) {
701            super(looper);
702        }
703
704        @Override
705        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
706            // Ignore vsync from secondary display.
707            // This can be problematic because the call to scheduleVsync() is a one-shot.
708            // We need to ensure that we will still receive the vsync from the primary
709            // display which is the one we really care about.  Ideally we should schedule
710            // vsync for a particular display.
711            // At this time Surface Flinger won't send us vsyncs for secondary displays
712            // but that could change in the future so let's log a message to help us remember
713            // that we need to fix this.
714            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
715                Log.d(TAG, "Received vsync from secondary display, but we don't support "
716                        + "this case yet.  Choreographer needs a way to explicitly request "
717                        + "vsync for a specific display to ensure it doesn't lose track "
718                        + "of its scheduled vsync.");
719                scheduleVsync();
720                return;
721            }
722
723            // Post the vsync event to the Handler.
724            // The idea is to prevent incoming vsync events from completely starving
725            // the message queue.  If there are no messages in the queue with timestamps
726            // earlier than the frame time, then the vsync event will be processed immediately.
727            // Otherwise, messages that predate the vsync event will be handled first.
728            long now = System.nanoTime();
729            if (timestampNanos > now) {
730                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
731                        + " ms in the future!  Check that graphics HAL is generating vsync "
732                        + "timestamps using the correct timebase.");
733                timestampNanos = now;
734            }
735
736            if (mHavePendingVsync) {
737                Log.w(TAG, "Already have a pending vsync event.  There should only be "
738                        + "one at a time.");
739            } else {
740                mHavePendingVsync = true;
741            }
742
743            mTimestampNanos = timestampNanos;
744            mFrame = frame;
745            Message msg = Message.obtain(mHandler, this);
746            msg.setAsynchronous(true);
747            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
748        }
749
750        @Override
751        public void run() {
752            mHavePendingVsync = false;
753            doFrame(mTimestampNanos, mFrame);
754        }
755    }
756
757    private static final class CallbackRecord {
758        public CallbackRecord next;
759        public long dueTime;
760        public Object action; // Runnable or FrameCallback
761        public Object token;
762
763        public void run(long frameTimeNanos) {
764            if (token == FRAME_CALLBACK_TOKEN) {
765                ((FrameCallback)action).doFrame(frameTimeNanos);
766            } else {
767                ((Runnable)action).run();
768            }
769        }
770    }
771
772    private final class CallbackQueue {
773        private CallbackRecord mHead;
774
775        public boolean hasDueCallbacksLocked(long now) {
776            return mHead != null && mHead.dueTime <= now;
777        }
778
779        public CallbackRecord extractDueCallbacksLocked(long now) {
780            CallbackRecord callbacks = mHead;
781            if (callbacks == null || callbacks.dueTime > now) {
782                return null;
783            }
784
785            CallbackRecord last = callbacks;
786            CallbackRecord next = last.next;
787            while (next != null) {
788                if (next.dueTime > now) {
789                    last.next = null;
790                    break;
791                }
792                last = next;
793                next = next.next;
794            }
795            mHead = next;
796            return callbacks;
797        }
798
799        public void addCallbackLocked(long dueTime, Object action, Object token) {
800            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
801            CallbackRecord entry = mHead;
802            if (entry == null) {
803                mHead = callback;
804                return;
805            }
806            if (dueTime < entry.dueTime) {
807                callback.next = entry;
808                mHead = callback;
809                return;
810            }
811            while (entry.next != null) {
812                if (dueTime < entry.next.dueTime) {
813                    callback.next = entry.next;
814                    break;
815                }
816                entry = entry.next;
817            }
818            entry.next = callback;
819        }
820
821        public void removeCallbacksLocked(Object action, Object token) {
822            CallbackRecord predecessor = null;
823            for (CallbackRecord callback = mHead; callback != null;) {
824                final CallbackRecord next = callback.next;
825                if ((action == null || callback.action == action)
826                        && (token == null || callback.token == token)) {
827                    if (predecessor != null) {
828                        predecessor.next = next;
829                    } else {
830                        mHead = next;
831                    }
832                    recycleCallbackLocked(callback);
833                } else {
834                    predecessor = callback;
835                }
836                callback = next;
837            }
838        }
839    }
840}
841