enable customized reporting of final jank metrics

Bug: 19125024
Change-Id: I14507c0476171490ddbc55836d314d907a2d8bfb
diff --git a/src/android/support/test/jank/JankMetrics.java b/src/android/support/test/jank/JankMetrics.java
new file mode 100644
index 0000000..b227804
--- /dev/null
+++ b/src/android/support/test/jank/JankMetrics.java
@@ -0,0 +1,32 @@
+/*
+ * 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.support.test.jank;
+
+/**
+ * {@link JankMetrics} contains aggregated {@link JankResult} after looped execution of a test case.
+ *
+ */
+public class JankMetrics {
+    /** average number of jank across iterations */
+    public double averageJank;
+    /** max jank number among all iterations */
+    public int maxJank;
+    /** average FPS across iterations */
+    public double averageFps;
+    /** average of longest frame duration reported from all frames rendered for each iteration */
+    public double averageMaxFrameDuration;
+}
diff --git a/src/android/support/test/jank/JankTest.java b/src/android/support/test/jank/JankTest.java
index 1256ab7..67726ac 100644
--- a/src/android/support/test/jank/JankTest.java
+++ b/src/android/support/test/jank/JankTest.java
@@ -32,15 +32,32 @@
     /** The minimum number of frames expected */
     int expectedFrames();
 
-    /** Alternate method to execute before the test method */
+    /**
+     * Alternate method to execute before the test method
+     * <p>
+     * Note: the annotated method must have same signature as {@link JankTestBase#beforeTest()}
+     */
     String beforeTest() default "beforeTest";
 
-    /** Alternate method to execute before each iteration */
+    /**
+     * Alternate method to execute before each iteration
+     * <p>
+     * Note: the annotated method must have same signature as {@link JankTestBase#beforeLoop()}
+     */
     String beforeLoop() default "beforeLoop";
 
-    /** Alternate method to execute after each iteration */
+    /**
+     * Alternate method to execute after each iteration
+     * <p>
+     * Note: the annotated method must have same signature as {@link JankTestBase#afterLoop()}
+     */
     String afterLoop() default "afterLoop";
 
-    /** Alternate method to execute after all iterations have completed */
+    /**
+     * Alternate method to execute after all iterations have completed.
+     * <p>
+     * <b>Important:</b> the annotated method must take a parameter of type {@link JankMetrics}.
+     * See {@link JankTestBase#afterTest(JankMetrics)}
+     * */
     String afterTest() default "afterTest";
 }
diff --git a/src/android/support/test/jank/JankTestBase.java b/src/android/support/test/jank/JankTestBase.java
index e23e975..821090b 100644
--- a/src/android/support/test/jank/JankTestBase.java
+++ b/src/android/support/test/jank/JankTestBase.java
@@ -67,9 +67,19 @@
         // Default implementation. Do nothing.
     }
 
-    /** Called once after all iterations have completed. */
-    public void afterTest() throws Exception {
-        // Default implementation. Do nothing.
+    /**
+     * Called once after all iterations have completed.
+     * <p>Note: default implementation reports the aggregated jank metrics via
+     * {@link Instrumentation#sendStatus(int, Bundle)}
+     * @param metrics the aggregated jank metrics after looped execution
+     * */
+    public void afterTest(JankMetrics metrics) throws Exception {
+        Bundle status = new Bundle();
+        status.putDouble(KEY_AVG_JANK, metrics.averageJank);
+        status.putInt(KEY_MAX_JANK, metrics.maxJank);
+        status.putDouble(KEY_AVG_FPS, metrics.averageFps);
+        status.putDouble(KEY_AVG_MAX_FRAME_DURATION, metrics.averageMaxFrameDuration);
+        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
     }
 
     /** Return the index of the currently executing iteration. */
@@ -77,7 +87,6 @@
         return mCurrentIteration;
     }
 
-
     @Override
     protected final void runTest() throws Throwable {
 
@@ -87,7 +96,7 @@
         Method beforeTest = resolveMethod(annotation.beforeTest());
         Method beforeLoop = resolveMethod(annotation.beforeLoop());
         Method afterLoop  = resolveMethod(annotation.afterLoop());
-        Method afterTest  = resolveMethod(annotation.afterTest());
+        Method afterTest  = resolveAfterTest(annotation.afterTest());
 
         // Get a JankUtil instance
         JankUtil jank = JankUtil.getInstance(getInstrumentation());
@@ -132,16 +141,14 @@
             afterLoop.invoke(this, (Object[])null);
         }
 
-        // Test tear down
-        afterTest.invoke(this, (Object[])null);
-
-        // Report results
-        Bundle status = new Bundle();
-        status.putDouble(KEY_AVG_JANK, (double)sumJankyFrames / iterations);
-        status.putInt(KEY_MAX_JANK, maxJankyFrames);
-        status.putDouble(KEY_AVG_FPS, sumFps / iterations);
-        status.putDouble(KEY_AVG_MAX_FRAME_DURATION, sumLongestFrame / iterations);
-        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+        // Report aggregated results
+        JankMetrics metrics = new JankMetrics();
+        metrics.averageJank = (double)sumJankyFrames / iterations;
+        metrics.maxJank = maxJankyFrames;
+        metrics.averageFps = sumFps / iterations;
+        metrics.averageMaxFrameDuration = sumLongestFrame / iterations;
+        // Test tear down and reporting
+        afterTest.invoke(this, metrics);
     }
 
 
@@ -163,6 +170,27 @@
         return method;
     }
 
+    /**
+     * Returns a {@link Method}} object representing the method annotated with
+     * {@link JankTest#afterTest()}.
+     */
+    private Method resolveAfterTest(String name) {
+        assertNotNull(name);
+
+        Method method = null;
+        try {
+            method = getClass().getMethod(name, JankMetrics.class);
+        } catch (NoSuchMethodException e) {
+            fail("method annotated with JankTest#afterTest has wrong signature");
+        }
+
+        if (!Modifier.isPublic(method.getModifiers())) {
+            fail(String.format("Method \"%s\" should be public", name));
+        }
+
+        return method;
+    }
+
     /** Returns a {@link Bundle} containing the command line parameters. */
     protected final Bundle getArguments() {
         if (arguments == null) {
diff --git a/src/android/support/test/jank/JankUtil.java b/src/android/support/test/jank/JankUtil.java
index 7f63174..d433d98 100644
--- a/src/android/support/test/jank/JankUtil.java
+++ b/src/android/support/test/jank/JankUtil.java
@@ -20,11 +20,8 @@
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.util.Log;
-import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.FrameStats;
-
-import java.util.HashMap;
-import java.util.Map;
+import android.view.accessibility.AccessibilityWindowInfo;
 
 /** The {@link JankUtil} class provides functionality for monitoring jank. */
 public class JankUtil {