1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.base;
6
7import android.animation.Animator;
8import android.animation.Animator.AnimatorListener;
9import android.animation.AnimatorListenerAdapter;
10import android.animation.TimeAnimator;
11import android.animation.TimeAnimator.TimeListener;
12import android.util.Log;
13
14import org.chromium.base.annotations.MainDex;
15
16/**
17 * Record Android animation frame rate and save it to UMA histogram. This is mainly for monitoring
18 * any jankiness of short Chrome Android animations. It is limited to few seconds of recording.
19 */
20@MainDex
21public class AnimationFrameTimeHistogram {
22    private static final String TAG = "AnimationFrameTimeHistogram";
23    private static final int MAX_FRAME_TIME_NUM = 600; // 10 sec on 60 fps.
24
25    private final Recorder mRecorder = new Recorder();
26    private final String mHistogramName;
27
28    /**
29     * @param histogramName The histogram name that the recorded frame times will be saved.
30     *                      This must be also defined in histograms.xml
31     * @return An AnimatorListener instance that records frame time histogram on start and end
32     *         automatically.
33     */
34    public static AnimatorListener getAnimatorRecorder(final String histogramName) {
35        return new AnimatorListenerAdapter() {
36            private final AnimationFrameTimeHistogram mAnimationFrameTimeHistogram =
37                    new AnimationFrameTimeHistogram(histogramName);
38
39            @Override
40            public void onAnimationStart(Animator animation) {
41                mAnimationFrameTimeHistogram.startRecording();
42            }
43
44            @Override
45            public void onAnimationEnd(Animator animation) {
46                mAnimationFrameTimeHistogram.endRecording();
47            }
48
49            @Override
50            public void onAnimationCancel(Animator animation) {
51                mAnimationFrameTimeHistogram.endRecording();
52            }
53        };
54    }
55
56    /**
57     * @param histogramName The histogram name that the recorded frame times will be saved.
58     *                      This must be also defined in histograms.xml
59     */
60    public AnimationFrameTimeHistogram(String histogramName) {
61        mHistogramName = histogramName;
62    }
63
64    /**
65     * Start recording frame times. The recording can fail if it exceeds a few seconds.
66     */
67    public void startRecording() {
68        mRecorder.startRecording();
69    }
70
71    /**
72     * End recording and save it to histogram. It won't save histogram if the recording wasn't
73     * successful.
74     */
75    public void endRecording() {
76        if (mRecorder.endRecording()) {
77            nativeSaveHistogram(mHistogramName,
78                    mRecorder.getFrameTimesMs(), mRecorder.getFrameTimesCount());
79        }
80        mRecorder.cleanUp();
81    }
82
83    /**
84     * Record Android animation frame rate and return the result.
85     */
86    private static class Recorder implements TimeListener {
87        // TODO(kkimlabs): If we can use in the future, migrate to Choreographer for minimal
88        //                 workload.
89        private final TimeAnimator mAnimator = new TimeAnimator();
90        private long[] mFrameTimesMs;
91        private int mFrameTimesCount;
92
93        private Recorder() {
94            mAnimator.setTimeListener(this);
95        }
96
97        private void startRecording() {
98            assert !mAnimator.isRunning();
99            mFrameTimesCount = 0;
100            mFrameTimesMs = new long[MAX_FRAME_TIME_NUM];
101            mAnimator.start();
102        }
103
104        /**
105         * @return Whether the recording was successful. If successful, the result is available via
106         *         getFrameTimesNs and getFrameTimesCount.
107         */
108        private boolean endRecording() {
109            boolean succeeded = mAnimator.isStarted();
110            mAnimator.end();
111            return succeeded;
112        }
113
114        private long[] getFrameTimesMs() {
115            return mFrameTimesMs;
116        }
117
118        private int getFrameTimesCount() {
119            return mFrameTimesCount;
120        }
121
122        /**
123         * Deallocates the temporary buffer to record frame times. Must be called after ending
124         * the recording and getting the result.
125         */
126        private void cleanUp() {
127            mFrameTimesMs = null;
128        }
129
130        @Override
131        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
132            if (mFrameTimesCount == mFrameTimesMs.length) {
133                mAnimator.end();
134                cleanUp();
135                Log.w(TAG, "Animation frame time recording reached the maximum number. It's either"
136                        + "the animation took too long or recording end is not called.");
137                return;
138            }
139
140            // deltaTime is 0 for the first frame.
141            if (deltaTime > 0) {
142                mFrameTimesMs[mFrameTimesCount++] = deltaTime;
143            }
144        }
145    }
146
147    private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count);
148}
149