support specifying process name at runtime

If the annoated process name with GfxMonitor starts with '#', we
treat it as a method that returns a String to indicate process
name. Such method will be called between beforeTest and test
iteration starts, in case that test class needs to setup test
iteration before it can determine the process name.

Also added monitor class name in the assertion message when not
enough frames are received.

Change-Id: I462f7c10a600372e137eb3d314cbf6703aa14ff5
diff --git a/src/main/java/android/support/test/jank/GfxMonitor.java b/src/main/java/android/support/test/jank/GfxMonitor.java
index 71ede73..18f283a 100644
--- a/src/main/java/android/support/test/jank/GfxMonitor.java
+++ b/src/main/java/android/support/test/jank/GfxMonitor.java
@@ -25,7 +25,10 @@
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface GfxMonitor {
-    /** The name of the process to monitor */
+    /**
+     * The name of the process to monitor. Alternatively, if the name begins with '#', it specifies
+     * a method that takes no parameters and returns string as the process name to monitor.
+     * */
     String processName();
 
     public static final String KEY_AVG_NUM_JANKY = "gfx-avg-jank";
diff --git a/src/main/java/android/support/test/jank/JankTestBase.java b/src/main/java/android/support/test/jank/JankTestBase.java
index bb2605d..a2398c7 100644
--- a/src/main/java/android/support/test/jank/JankTestBase.java
+++ b/src/main/java/android/support/test/jank/JankTestBase.java
@@ -20,8 +20,8 @@
 import android.app.Instrumentation;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.jank.internal.JankMonitorFactory;
 import android.support.test.jank.internal.JankMonitor;
+import android.support.test.jank.internal.JankMonitorFactory;
 import android.support.test.runner.AndroidJUnitRunner;
 import android.test.InstrumentationTestCase;
 import android.test.InstrumentationTestRunner;
@@ -89,14 +89,14 @@
         Method afterLoop  = resolveMethod(annotation.afterLoop());
         Method afterTest  = resolveAfterTest(annotation.afterTest());
 
-        // Get the appropriate JankMonitors for the test type
-        JankMonitorFactory factory = new JankMonitorFactory(getInstrumentation().getUiAutomation());
-        List<JankMonitor> monitors = factory.getJankMonitors(testMethod);
-        assertTrue("No monitors configured for this test", monitors.size() > 0);
-
         // Test setup
         beforeTest.invoke(this, (Object[])null);
 
+        // Get the appropriate JankMonitors for the test type
+        JankMonitorFactory factory = new JankMonitorFactory(getInstrumentation().getUiAutomation());
+        List<JankMonitor> monitors = factory.getJankMonitors(testMethod, this);
+        assertTrue("No monitors configured for this test", monitors.size() > 0);
+
         // Execute the test several times according to the "iteration" parameter
         int iterations = Integer.valueOf(getArguments().getString("iterations",
                 Integer.toString(annotation.defaultIterationCount())));
@@ -117,8 +117,9 @@
                 int numFrames = monitor.stopIteration();
 
                 // Fail the test if we didn't get enough frames
-                assertTrue(String.format("Too few frames received. Expected: %d, Received: %d.",
-                        annotation.expectedFrames(), numFrames),
+                assertTrue(String.format(
+                        "Too few frames received. Monitor: %s, Expected: %d, Received: %d.",
+                        monitor.getClass().getSimpleName(), annotation.expectedFrames(), numFrames),
                         numFrames >= annotation.expectedFrames());
             }
 
diff --git a/src/main/java/android/support/test/jank/internal/JankMonitorFactory.java b/src/main/java/android/support/test/jank/internal/JankMonitorFactory.java
index 55f940d..d3b1cb7 100644
--- a/src/main/java/android/support/test/jank/internal/JankMonitorFactory.java
+++ b/src/main/java/android/support/test/jank/internal/JankMonitorFactory.java
@@ -18,12 +18,17 @@
 
 import android.app.UiAutomation;
 import android.os.Build;
+import android.support.test.jank.JankTestBase;
 import android.support.test.jank.WindowAnimationFrameStatsMonitor;
 import android.support.test.jank.WindowContentFrameStatsMonitor;
 import android.support.test.jank.GfxMonitor;
 import android.util.Log;
 
+import junit.framework.Assert;
+
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,7 +45,7 @@
         mUiAutomation = automation;
     }
 
-    public List<JankMonitor> getJankMonitors(Method testMethod) {
+    public List<JankMonitor> getJankMonitors(Method testMethod, JankTestBase testInstance) {
         List<JankMonitor> monitors = new ArrayList<JankMonitor>();
         if (testMethod.getAnnotation(GfxMonitor.class) != null) {
             // GfxMonitor only works on M+. NB: Hard coding value since SDK 22 isn't in prebuilts.
@@ -48,6 +53,35 @@
                 Log.w(TAG, "Skipping GfxMonitor. Not supported by current platform.");
             } else {
                 String process = testMethod.getAnnotation(GfxMonitor.class).processName();
+                // if process name starts with "#", treat it as a method that returns process name
+                if (process.startsWith("#")) {
+                    process = process.substring(1);
+                    Method method = null;
+                    try {
+                        method = testMethod.getDeclaringClass().getMethod(process, (Class[]) null);
+                    } catch (NoSuchMethodException e) {
+                        Assert.fail(String.format("Method \"%s\" not found", process));
+                    }
+
+                    if (!Modifier.isPublic(method.getModifiers())) {
+                        Assert.fail(String.format("Method \"%s\" should be public", process));
+                    }
+
+                    Object o = null;
+                    try {
+                        o = method.invoke(testInstance, (Object[])null);
+                    } catch (IllegalAccessException | IllegalArgumentException
+                            | InvocationTargetException e) {
+                        Assert.fail(String.format(
+                                "Exception %s(%s) while invoking \"%s\" for monitored process name",
+                                e.getClass().getName(), e.getMessage(), process));
+                    }
+                    if (o == null || !(o instanceof String)) {
+                        Assert.fail(String.format("Method \"%s\" should return String", process));
+                    }
+                    process = (String)o;
+                    Log.d(TAG, "Using process name from annotated method: " + process);
+                }
                 monitors.add(new GfxMonitorImpl(mUiAutomation, process));
             }
         }