New log format, Manual log generation, plus logging of invalidates

bug:5062896

Added features to TileProfiler, updated jni interface to allow querying of
arbitrary log data via strings. Depends on the following webkit change:

https://android-git.corp.google.com/g/#change,122779

For new logging jni interface, and logging of invalidates.

Change-Id: I80ba6702b87e86ec76e5b0eafde45f4ef3a80ad3
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index b22c57b0..8cd93de 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -9119,20 +9119,12 @@
         return nativeTileProfilingNumTilesInFrame(frame);
     }
     /** @hide only used by profiling tests */
-    public int tileProfilingGetX(int frame, int tile) {
-        return nativeTileProfilingGetX(frame, tile);
+    public int tileProfilingGetInt(int frame, int tile, String key) {
+        return nativeTileProfilingGetInt(frame, tile, key);
     }
     /** @hide only used by profiling tests */
-    public int tileProfilingGetY(int frame, int tile) {
-        return nativeTileProfilingGetY(frame, tile);
-    }
-    /** @hide only used by profiling tests */
-    public boolean tileProfilingGetReady(int frame, int tile) {
-        return nativeTileProfilingGetReady(frame, tile);
-    }
-    /** @hide only used by profiling tests */
-    public int tileProfilingGetLevel(int frame, int tile) {
-        return nativeTileProfilingGetLevel(frame, tile);
+    public float tileProfilingGetFloat(int frame, int tile, String key) {
+        return nativeTileProfilingGetFloat(frame, tile, key);
     }
 
     private native int nativeCacheHitFramePointer();
@@ -9262,10 +9254,8 @@
     private native void     nativeTileProfilingClear();
     private native int      nativeTileProfilingNumFrames();
     private native int      nativeTileProfilingNumTilesInFrame(int frame);
-    private native int      nativeTileProfilingGetX(int frame, int tile);
-    private native int      nativeTileProfilingGetY(int frame, int tile);
-    private native boolean  nativeTileProfilingGetReady(int frame, int tile);
-    private native int      nativeTileProfilingGetLevel(int frame, int tile);
+    private native int      nativeTileProfilingGetInt(int frame, int tile, String key);
+    private native float    nativeTileProfilingGetFloat(int frame, int tile, String key);
     // Never call this version except by updateCachedTextfield(String) -
     // we always want to pass in our generation number.
     private native void     nativeUpdateCachedTextfield(String updatedText,
diff --git a/tests/TileBenchmark/AndroidManifest.xml b/tests/TileBenchmark/AndroidManifest.xml
index 663cc0d..ab61a9e 100644
--- a/tests/TileBenchmark/AndroidManifest.xml
+++ b/tests/TileBenchmark/AndroidManifest.xml
@@ -7,14 +7,16 @@
                  android:label="@string/app_name"
                  android:hardwareAccelerated="true">
         <activity android:name=".ProfileActivity"
-                  android:label="@string/profile_activity">
+                  android:label="@string/profile_activity"
+                  android:theme="@android:style/Theme.Holo.NoActionBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
         <activity android:name=".PlaybackActivity"
-                  android:label="@string/playback_activity">
+                  android:label="@string/playback_activity"
+                  android:theme="@android:style/Theme.Holo.NoActionBar">
         </activity>
     </application>
 </manifest>
diff --git a/tests/TileBenchmark/res/layout/main.xml b/tests/TileBenchmark/res/layout/main.xml
index 4a81da6..577c466 100644
--- a/tests/TileBenchmark/res/layout/main.xml
+++ b/tests/TileBenchmark/res/layout/main.xml
@@ -23,11 +23,11 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         >
-        <Button
-            android:id="@+id/inspect"
+        <Spinner
+            android:id="@+id/movement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/inspect_log"
+            android:prompt="@string/movement_method"
             />
         <Spinner
             android:id="@+id/velocity"
@@ -36,6 +36,13 @@
             android:gravity="center_horizontal"
             android:prompt="@string/desired_scroll_velocity"
             />
+        <ToggleButton
+            android:id="@+id/capture"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOn="@string/capture_stop"
+            android:textOff="@string/capture_start"
+            />
         <EditText
             android:id="@+id/url"
             android:layout_width="0dip"
@@ -44,6 +51,12 @@
             android:imeOptions="actionGo"
             android:layout_weight="1"
             />
+        <Button
+            android:id="@+id/inspect"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/inspect_log"
+            />
     </LinearLayout>
     <com.test.tilebenchmark.ProfiledWebView
         android:id="@+id/web"
diff --git a/tests/TileBenchmark/res/values/colors.xml b/tests/TileBenchmark/res/values/colors.xml
index 3958083..dbb8e72 100644
--- a/tests/TileBenchmark/res/values/colors.xml
+++ b/tests/TileBenchmark/res/values/colors.xml
@@ -18,8 +18,17 @@
     <color name="ready_tile">#ff4ac230</color>
     <!-- The color of tiles with stale / invalid textures -->
     <color name="unready_tile">#ff744400</color>
-    <!-- Background color for logged URLs -->
-    <color name="finished_url">#ff004000</color>
-    <!-- Background color for URLs with logging in progress -->
-    <color name="unfinished_url">#ff400000</color>
+    <!-- Viewport overlay in playback -->
+    <color name="view">#50000050</color>
+    <!-- Invalidated region overlay in playback - start color -->
+    <color name="inval_region_start">#80ff0000</color>
+    <!-- Invalidated region overlay in playback - stop color-->
+    <color name="inval_region_stop">#80ffffff</color>
+
+    <!-- Background color for not testing -->
+    <color name="background_not_testing">#ff000000</color>
+    <!-- Background color for during testing -->
+    <color name="background_start_testing">#ff400000</color>
+    <!-- Background color for testing complete -->
+    <color name="background_stop_testing">#ff004000</color>
 </resources>
diff --git a/tests/TileBenchmark/res/values/strings.xml b/tests/TileBenchmark/res/values/strings.xml
index f70ee2c..66972ac 100644
--- a/tests/TileBenchmark/res/values/strings.xml
+++ b/tests/TileBenchmark/res/values/strings.xml
@@ -28,6 +28,10 @@
     <string name="loadbutton">Load</string>
     <!-- Button, opens the playback activity [CHAR LIMIT=20] -->
     <string name="inspect_log">Inspect Log</string>
+    <!-- ToggleButton label when pressing starts capture [CHAR LIMIT=15] -->
+    <string name="capture_start">Start Capture</string>
+    <!-- ToggleButton label when pressing stops capture [CHAR LIMIT=15] -->
+    <string name="capture_stop">Stop Capture</string>
     <!-- The speed of auto-scrolling [CHAR LIMIT=30] -->
     <string name="desired_scroll_velocity">Choose Scroll Velocity</string>
     <!-- Pixels moved per frame [CHAR LIMIT=10] -->
@@ -39,6 +43,21 @@
         <item>200</item>
         <item>400</item>
     </string-array>
+    <!-- Drop down menu for selecting scrolling vs manual navigation for
+    capturing [CHAR LIMIT=15] -->
+    <string name="movement_method">Movement Method</string>
+    <!-- Drop down menu entry - automatically scroll to the end of the page
+    with scrollBy() [CHAR LIMIT=15] -->
+    <string name="movement_auto_scroll">Auto-scroll</string>
+    <!-- Drop down menu entry -  [CHAR LIMIT=15] -->
+    <string name="movement_auto_fling">Auto-fling</string>
+    <!-- Drop down menu entry - manually navigate the page(s), hit 'capture'
+    button [CHAR LIMIT=15] -->
+    <string name="movement_manual">Manual</string>
+
+    <!-- Error popup indicating log data couldn't be loaded [CHAR LIMIT=60] -->
+    <string name="error_no_data">Error: log data could not be loaded.</string>
+
     <!-- 25th percentile - 25% of frames fall below this value [CHAR LIMIT=12]
     -->
     <string name="percentile_25">25%ile</string>
@@ -56,7 +75,7 @@
     <string name="format_stat">%4.4f</string>
     <!-- Format string for displaying aggregate stats+values (nr of valid tiles,
     etc.) [CHAR LIMIT=20] -->
-    <string name="format_stat_name">%1$9s %2$3d</string>
+    <string name="format_stat_name">%1$-20s %2$3d</string>
     <!-- Text hovering over canvas, number of tiles ready [CHAR LIMIT=15] -->
     <string name="ready_tiles">Ready Tiles</string>
     <!-- Text hovering over canvas, number tiles not ready [CHAR LIMIT=15] -->
@@ -64,4 +83,7 @@
     <!-- Text hovering over canvas, number of tiles that haven't been
     allocated to a place on the page [CHAR LIMIT=15] -->
     <string name="unplaced_tiles">Unplaced Tiles</string>
+    <!-- Text hovering over canvas, number of invalidated regions this frame
+    [CHAR LIMIT=15] -->
+    <string name="number_invalidates">Invalidates</string>
 </resources>
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java
index 5130f5d..36694a7 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java
@@ -27,6 +27,7 @@
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -102,7 +103,10 @@
         @Override
         protected void onPostExecute(TileData data[][]) {
             if (data == null) {
-                data = genTestPattern();
+                Toast.makeText(getApplicationContext(),
+                        getResources().getString(R.string.error_no_data),
+                        Toast.LENGTH_LONG).show();
+                return;
             }
             mPlaybackView.setData(data);
 
@@ -166,23 +170,4 @@
 
         new LoadFileTask().execute(ProfileActivity.TEMP_FILENAME);
     }
-
-    private TileData[][] genTestPattern() {
-        final int XMAX = 5;
-        final int FRAMEMAX = 99;
-
-        TileData example[][] = new TileData[FRAMEMAX][];
-        for (int frame = 0; frame < FRAMEMAX; frame++) {
-            int numTiles = frame + 10;
-
-            example[frame] = new TileData[numTiles];
-            for (int t = 0; t < numTiles; t++) {
-                int x = t % XMAX;
-                int y = t / XMAX;
-                boolean isReady = y * 10 < frame;
-                example[frame][t] = new TileData(x, y, isReady, 0);
-            }
-        }
-        return example;
-    }
 }
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
index db4a341..35b1563 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
@@ -28,19 +28,17 @@
 import java.util.Arrays;
 
 public class PlaybackGraphs {
-    private static final int BAR_WIDTH = PlaybackView.TILEX * 3;
+    private static final int BAR_WIDTH = PlaybackView.TILE_SCALE * 3;
     private static final float CANVAS_SCALE = 0.2f;
     private static final double IDEAL_FRAMES = 60;
     private static final int LABELOFFSET = 100;
     private static Paint whiteLabels;
 
-    private static double viewportCoverage(int l, int b, int r, int t,
-            int tileIndexX,
-            int tileIndexY) {
-        if (tileIndexX * PlaybackView.TILEX < r
-                && (tileIndexX + 1) * PlaybackView.TILEX >= l
-                && tileIndexY * PlaybackView.TILEY < t
-                && (tileIndexY + 1) * PlaybackView.TILEY >= b) {
+    private static double viewportCoverage(TileData view, TileData tile) {
+        if (tile.left < view.right
+                && tile.right >= view.left
+                && tile.top < view.bottom
+                && tile.bottom >= view.top) {
             return 1.0f;
         }
         return 0.0f;
@@ -76,13 +74,10 @@
                 // coverage graph
                 @Override
                 public double getValue(TileData[] frame) {
-                    int l = frame[0].x, b = frame[0].y;
-                    int r = frame[1].x, t = frame[1].y;
                     double total = 0, totalCount = 0;
-                    for (int tileID = 2; tileID < frame.length; tileID++) {
+                    for (int tileID = 1; tileID < frame.length; tileID++) {
                         TileData data = frame[tileID];
-                        double coverage = viewportCoverage(l, b, r, t, data.x,
-                                data.y);
+                        double coverage = viewportCoverage(frame[0], data);
                         total += coverage * (data.isReady ? 1 : 0);
                         totalCount += coverage;
                     }
@@ -158,7 +153,7 @@
     public PlaybackGraphs() {
         whiteLabels = new Paint();
         whiteLabels.setColor(Color.WHITE);
-        whiteLabels.setTextSize(PlaybackView.TILEY / 3);
+        whiteLabels.setTextSize(PlaybackView.TILE_SCALE / 3);
     }
 
     private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
@@ -177,11 +172,13 @@
             int lastBar = 0;
             for (int frameIndex = 0; frameIndex < tileProfilingData.length; frameIndex++) {
                 TileData frame[] = tileProfilingData[frameIndex];
-                int newBar = (frame[0].y + frame[1].y) / 2;
+                int newBar = (frame[0].top + frame[0].bottom) / 2;
 
                 MetricGen s = Metrics[metricIndex];
                 double absoluteValue = s.getValue(frame);
                 double relativeValue = absoluteValue / s.getMax();
+                relativeValue = Math.min(1,relativeValue);
+                relativeValue = Math.max(0,relativeValue);
                 int rightPos = (int) (-BAR_WIDTH * metricIndex);
                 int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
 
@@ -207,7 +204,7 @@
             ArrayList<ShapeDrawable> shapes) {
         // Shapes drawn here are drawn relative to the viewRect
         Rect viewRect = shapes.get(shapes.size() - 1).getBounds();
-        canvas.translate(0, 5 * PlaybackView.TILEY - viewRect.top);
+        canvas.translate(0, 5 * PlaybackView.TILE_SCALE - viewRect.top);
 
         for (ShapeDrawable shape : mShapes) {
             shape.draw(canvas);
@@ -234,13 +231,15 @@
             int yPos = LABELOFFSET;
             canvas.drawText(label, xPos, yPos, whiteLabels);
             for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
-                label = resources.getString(R.string.format_stat, mStats[metricIndex][statIndex]);
-                yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILEY / 2;
+                label = resources.getString(R.string.format_stat,
+                        mStats[metricIndex][statIndex]);
+                yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILE_SCALE
+                        / 2;
                 canvas.drawText(label, xPos, yPos, whiteLabels);
             }
         }
         for (int stringIndex = 0; stringIndex < strings.length; stringIndex++) {
-            int yPos = LABELOFFSET + stringIndex * PlaybackView.TILEY / 2;
+            int yPos = LABELOFFSET + stringIndex * PlaybackView.TILE_SCALE / 2;
             canvas.drawText(strings[stringIndex], 0, yPos, whiteLabels);
         }
     }
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java
index f104eac..edc8643 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java
@@ -16,6 +16,9 @@
 
 package com.test.tilebenchmark;
 
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -30,8 +33,9 @@
 import java.util.ArrayList;
 
 public class PlaybackView extends View {
-    public static final int TILEX = 300;
-    public static final int TILEY = 300;
+    public static final int TILE_SCALE = 300;
+    private static final int INVAL_FLAG = -2;
+    private static final int INVAL_CYCLE = 250;
 
     private Paint levelPaint = null, coordPaint = null, goldPaint = null;
     private PlaybackGraphs mGraphs;
@@ -39,28 +43,46 @@
     private ArrayList<ShapeDrawable> mTempShapes = new ArrayList<ShapeDrawable>();
     private TileData mProfData[][] = null;
     private GestureDetector mGestureDetector = null;
-    private String mRenderStrings[] = new String[3];
+    private String mRenderStrings[] = new String[4];
 
     private class TileDrawable extends ShapeDrawable {
         TileData tile;
+        String label;
 
-        public TileDrawable(TileData t) {
-            int tileColorId = t.isReady ? R.color.ready_tile
-                    : R.color.unready_tile;
-            getPaint().setColor(getResources().getColor(tileColorId));
-
-            setBounds(t.x * TILEX, t.y * TILEY, (t.x + 1) * TILEX, (t.y + 1)
-                    * TILEY);
+        public TileDrawable(TileData t, int colorId) {
             this.tile = t;
+            getPaint().setColor(getResources().getColor(colorId));
+            if (colorId == R.color.ready_tile
+                    || colorId == R.color.unready_tile) {
+
+                label = (int) (t.left / TILE_SCALE) + ", "
+                        + (int) (t.top / TILE_SCALE);
+                // ignore scale value for tiles
+                setBounds(t.left, t.top,
+                        t.right, t.bottom);
+            } else {
+                setBounds((int) (t.left * t.scale),
+                        (int) (t.top * t.scale),
+                        (int) (t.right * t.scale),
+                        (int) (t.bottom * t.scale));
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public void setColor(int color) {
+            getPaint().setColor(color);
         }
 
         @Override
         public void draw(Canvas canvas) {
             super.draw(canvas);
-            canvas.drawText(Integer.toString(tile.level), getBounds().left,
-                    getBounds().bottom, levelPaint);
-            canvas.drawText(tile.x + "," + tile.y, getBounds().left,
-                    ((getBounds().bottom + getBounds().top) / 2), coordPaint);
+            if (label != null) {
+                canvas.drawText(Integer.toString(tile.level), getBounds().left,
+                        getBounds().bottom, levelPaint);
+                canvas.drawText(label, getBounds().left,
+                        ((getBounds().bottom + getBounds().top) / 2),
+                        coordPaint);
+            }
         }
     }
 
@@ -92,10 +114,10 @@
     private void init() {
         levelPaint = new Paint();
         levelPaint.setColor(Color.WHITE);
-        levelPaint.setTextSize(TILEY / 2);
+        levelPaint.setTextSize(TILE_SCALE / 2);
         coordPaint = new Paint();
         coordPaint.setColor(Color.BLACK);
-        coordPaint.setTextSize(TILEY / 3);
+        coordPaint.setTextSize(TILE_SCALE / 3);
         goldPaint = new Paint();
         goldPaint.setColor(0xffa0e010);
         mGraphs = new PlaybackGraphs();
@@ -110,6 +132,7 @@
         }
 
         mGraphs.draw(canvas, mTempShapes, mRenderStrings, getResources());
+        invalidate(); // may have animations, force redraw
     }
 
     public int setFrame(int frame) {
@@ -117,35 +140,66 @@
             return 0;
         }
 
-        int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0;
+        int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0, numInvals = 0;
         mTempShapes.clear();
 
-        // draw actual tiles
-        for (int tileID = 2; tileID < mProfData[frame].length; tileID++) {
-            TileData t = mProfData[frame][tileID];
-            mTempShapes.add(new TileDrawable(t));
-            if (t.isReady) {
-                readyTiles++;
+        // create tile shapes (as they're drawn on bottom)
+        for (TileData t : mProfData[frame]) {
+            if (t.level != INVAL_FLAG && t != mProfData[frame][0]) {
+                int colorId;
+                if (t.isReady) {
+                    readyTiles++;
+                    colorId = R.color.ready_tile;
+                } else {
+                    unreadyTiles++;
+                    colorId = R.color.unready_tile;
+                }
+                if (t.left < 0 || t.top < 0) {
+                    unplacedTiles++;
+                }
+                mTempShapes.add(new TileDrawable(t, colorId));
             } else {
-                unreadyTiles++;
-            }
-            if (t.x < 0 || t.y < 0) {
-                unplacedTiles++;
+                numInvals++;
             }
         }
+
+        // create invalidate shapes (drawn above tiles)
+        int invalId = 0;
+        for (TileData t : mProfData[frame]) {
+            if (t.level == INVAL_FLAG && t != mProfData[frame][0]) {
+                TileDrawable invalShape = new TileDrawable(t,
+                        R.color.inval_region_start);
+                ValueAnimator tileAnimator = ObjectAnimator.ofInt(invalShape,
+                        "color",
+                        getResources().getColor(R.color.inval_region_start),
+                        getResources().getColor(R.color.inval_region_stop));
+                tileAnimator.setDuration(numInvals * INVAL_CYCLE);
+                tileAnimator.setEvaluator(new ArgbEvaluator());
+                tileAnimator.setRepeatCount(ValueAnimator.INFINITE);
+                tileAnimator.setRepeatMode(ValueAnimator.RESTART);
+                float delay = (float) (invalId) * INVAL_CYCLE;
+                tileAnimator.setStartDelay((int) delay);
+                invalId++;
+                tileAnimator.start();
+
+                mTempShapes.add(invalShape);
+            }
+        }
+
         mRenderStrings[0] = getResources().getString(R.string.format_stat_name,
                 getResources().getString(R.string.ready_tiles), readyTiles);
         mRenderStrings[1] = getResources().getString(R.string.format_stat_name,
                 getResources().getString(R.string.unready_tiles), unreadyTiles);
         mRenderStrings[2] = getResources().getString(R.string.format_stat_name,
-                getResources().getString(R.string.unplaced_tiles), unplacedTiles);
+                getResources().getString(R.string.unplaced_tiles),
+                unplacedTiles);
+        mRenderStrings[3] = getResources().getString(R.string.format_stat_name,
+                getResources().getString(R.string.number_invalidates),
+                numInvals);
 
-        // draw view rect (using first two TileData objects)
-        ShapeDrawable viewShape = new ShapeDrawable();
-        viewShape.getPaint().setColor(0xff0000ff);
-        viewShape.setAlpha(64);
-        viewShape.setBounds(mProfData[frame][0].x, mProfData[frame][0].y,
-                mProfData[frame][1].x, mProfData[frame][1].y);
+        // draw view rect (using first TileData object, on top)
+        TileDrawable viewShape = new TileDrawable(mProfData[frame][0],
+                R.color.view);
         mTempShapes.add(viewShape);
         this.invalidate();
         return frame;
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
index 23b6275..1521807 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
@@ -38,6 +38,7 @@
 import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
+import android.widget.ToggleButton;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -58,11 +59,24 @@
                                                      // before test
 
     Button mInspectButton;
+    ToggleButton mCaptureButton;
     Spinner mVelocitySpinner;
+    Spinner mMovementSpinner;
     EditText mUrl;
     ProfiledWebView mWeb;
     ProfileCallback mCallback;
 
+    LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient();
+    AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient();
+
+    private enum TestingState {
+        NOT_TESTING,
+        PRE_TESTING,
+        START_TESTING,
+        STOP_TESTING,
+        SAVED_TESTING
+    };
+
     private class VelocitySelectedListener implements OnItemSelectedListener {
         @Override
         public void onItemSelected(AdapterView<?> parent, View view,
@@ -77,6 +91,31 @@
         }
     }
 
+    private class MovementSelectedListener implements OnItemSelectedListener {
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view,
+                int position, long id) {
+            String movementStr = parent.getItemAtPosition(position).toString();
+            if (movementStr == getResources().getString(
+                    R.string.movement_auto_scroll)
+                    || movementStr == getResources().getString(
+                            R.string.movement_auto_fling)) {
+                mWeb.setWebViewClient(mAutoLoggingWebViewClient);
+                mCaptureButton.setEnabled(false);
+                mVelocitySpinner.setEnabled(true);
+            } else if (movementStr == getResources().getString(
+                    R.string.movement_manual)) {
+                mWeb.setWebViewClient(mLoggingWebViewClient);
+                mCaptureButton.setEnabled(true);
+                mVelocitySpinner.setEnabled(false);
+            }
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+        }
+    }
+
     private class LoggingWebViewClient extends WebViewClient {
         @Override
         public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -88,6 +127,9 @@
             super.onPageStarted(view, url, favicon);
             mUrl.setText(url);
         }
+    }
+
+    private class AutoLoggingWebViewClient extends LoggingWebViewClient {
 
         @Override
         public void onPageFinished(WebView view, String url) {
@@ -100,10 +142,16 @@
 
                 @Override
                 public void onFinish() {
-                    mWeb.startScrollTest(mCallback);
+                    startViewProfiling(true);
                 }
             }.start();
         }
+
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            super.onPageStarted(view, url, favicon);
+            setTestingState(TestingState.PRE_TESTING);
+        }
     }
 
     private class StoreFileTask extends
@@ -125,24 +173,65 @@
 
         @Override
         protected void onPostExecute(Void v) {
-            mUrl.setBackgroundResource(R.color.finished_url);
+            setTestingState(TestingState.SAVED_TESTING);
         }
     }
 
+    public void setTestingState(TestingState state) {
+        switch (state) {
+            case NOT_TESTING:
+                mUrl.setBackgroundResource(R.color.background_not_testing);
+                mInspectButton.setEnabled(true);
+                mMovementSpinner.setEnabled(true);
+                break;
+            case PRE_TESTING:
+                mInspectButton.setEnabled(false);
+                mMovementSpinner.setEnabled(false);
+                break;
+            case START_TESTING:
+                mUrl.setBackgroundResource(R.color.background_start_testing);
+                mInspectButton.setEnabled(false);
+                mMovementSpinner.setEnabled(false);
+                break;
+            case STOP_TESTING:
+                mUrl.setBackgroundResource(R.color.background_stop_testing);
+                break;
+            case SAVED_TESTING:
+                mInspectButton.setEnabled(true);
+                mMovementSpinner.setEnabled(true);
+                break;
+        }
+    }
+
+    /** auto - automatically scroll. */
+    private void startViewProfiling(boolean auto) {
+        if (!auto) {
+            // manual, toggle capture button to indicate capture state to user
+            mCaptureButton.setChecked(true);
+        }
+        mWeb.startScrollTest(mCallback, auto);
+        setTestingState(TestingState.START_TESTING);
+    }
+
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         mInspectButton = (Button) findViewById(R.id.inspect);
+        mCaptureButton = (ToggleButton) findViewById(R.id.capture);
         mVelocitySpinner = (Spinner) findViewById(R.id.velocity);
+        mMovementSpinner = (Spinner) findViewById(R.id.movement);
         mUrl = (EditText) findViewById(R.id.url);
         mWeb = (ProfiledWebView) findViewById(R.id.web);
         mCallback = new ProfileCallback() {
             @SuppressWarnings("unchecked")
             @Override
             public void profileCallback(TileData[][] data) {
-                new StoreFileTask().execute(new Pair<String, TileData[][]>(TEMP_FILENAME, data));
+                new StoreFileTask().execute(new Pair<String, TileData[][]>(
+                        TEMP_FILENAME, data));
+                mCaptureButton.setChecked(false);
+                setTestingState(TestingState.STOP_TESTING);
             }
         };
 
@@ -166,6 +255,33 @@
                 new VelocitySelectedListener());
         mVelocitySpinner.setSelection(3);
 
+        // Movement spinner
+        String content[] = {
+                getResources().getString(R.string.movement_auto_scroll),
+                getResources().getString(R.string.movement_auto_fling),
+                getResources().getString(R.string.movement_manual)
+        };
+        adapter = new ArrayAdapter<CharSequence>(this,
+                android.R.layout.simple_spinner_item, content);
+        adapter.setDropDownViewResource(
+                android.R.layout.simple_spinner_dropdown_item);
+        mMovementSpinner.setAdapter(adapter);
+        mMovementSpinner.setOnItemSelectedListener(
+                new MovementSelectedListener());
+        mMovementSpinner.setSelection(0);
+
+        // Capture toggle button
+        mCaptureButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mCaptureButton.isChecked()) {
+                    startViewProfiling(false);
+                } else {
+                    mWeb.stopScrollTest();
+                }
+            }
+        });
+
         // Custom profiling WebView
         WebSettings settings = mWeb.getSettings();
         settings.setJavaScriptEnabled(true);
@@ -180,12 +296,13 @@
             public boolean onEditorAction(TextView v, int actionId,
                     KeyEvent event) {
                 String url = mUrl.getText().toString();
-                mUrl.setBackgroundResource(R.color.unfinished_url);
                 mWeb.loadUrl(url);
                 mWeb.requestFocus();
                 return true;
             }
         });
+
+        setTestingState(TestingState.NOT_TESTING);
     }
 
     public void setCallback(ProfileCallback callback) {
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
index 6560624..d3941be 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
@@ -59,12 +59,13 @@
     }
 
     /*
-     * Called once the page is loaded to start scrolling for evaluating tiles
+     * Called once the page is loaded to start scrolling for evaluating tiles.
+     * If autoScrolling isn't set, stop must be called manually.
      */
-    public void startScrollTest(ProfileCallback callback) {
-        isScrolling = true;
+    public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
+        isScrolling = autoScrolling;
         mCallback = callback;
-        super.tileProfilingStart();
+        tileProfilingStart();
         invalidate();
     }
 
@@ -72,19 +73,31 @@
      * Called once the page has stopped scrolling
      */
     public void stopScrollTest() {
-        float testRatio = super.tileProfilingStop();
+        super.tileProfilingStop();
+
+        if (mCallback == null) {
+            tileProfilingClear();
+            return;
+        }
 
         TileData data[][] = new TileData[super.tileProfilingNumFrames()][];
         for (int frame = 0; frame < data.length; frame++) {
             data[frame] = new TileData[
-                    super.tileProfilingNumTilesInFrame(frame)];
+                    tileProfilingNumTilesInFrame(frame)];
             for (int tile = 0; tile < data[frame].length; tile++) {
-                int x = super.tileProfilingGetX(frame, tile);
-                int y = super.tileProfilingGetY(frame, tile);
-                boolean isReady = super.tileProfilingGetReady(frame, tile);
-                int level = super.tileProfilingGetLevel(frame, tile);
+                int left = tileProfilingGetInt(frame, tile, "left");
+                int top = tileProfilingGetInt(frame, tile, "top");
+                int right = tileProfilingGetInt(frame, tile, "right");
+                int bottom = tileProfilingGetInt(frame, tile, "bottom");
 
-                data[frame][tile] = new TileData(x, y, isReady, level);
+                boolean isReady = super.tileProfilingGetInt(
+                        frame, tile, "isReady") == 1;
+                int level = tileProfilingGetInt(frame, tile, "level");
+
+                float scale = tileProfilingGetFloat(frame, tile, "scale");
+
+                data[frame][tile] = new TileData(left, top, right, bottom,
+                        isReady, level, scale);
             }
         }
         super.tileProfilingClear();
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java
index 7d4bb9f..3e729a6 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java
@@ -19,14 +19,24 @@
 import java.io.Serializable;
 
 public class TileData implements Serializable {
-    public int x, y;
+    int left, top, right, bottom;
     public boolean isReady;
     public int level;
+    public float scale;
 
-    public TileData(int x, int y, boolean isReady, int level) {
-        this.x = x;
-        this.y = y;
+    public TileData(int left, int top, int right, int bottom, boolean isReady,
+            int level, float scale) {
+        this.left = left;
+        this.right = right;
+        this.top = top;
+        this.bottom = bottom;
         this.isReady = isReady;
         this.level = level;
+        this.scale = scale;
+    }
+
+    public String toString() {
+        return "Tile (" + left + "," + top + ")->("
+                + right + "," + bottom + ")";
     }
 }