Added TileBenchmark

Adds TileBenchmark app, which loads pages in webview and scrolls through them
automatically, measuring rendering and allocation information.

Change-Id: Ieaf5bbcec604c99aaf16822915e58bfc495011ec
This commit is contained in:
Chris Craik
2011-07-15 16:24:37 -07:00
parent 9cba686366
commit 5888ec2b30
15 changed files with 1207 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
# Copyright (C) 2011 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := TileBenchmark
include $(BUILD_PACKAGE)
##################################################
include $(CLEAR_VARS)
include $(BUILD_MULTI_PREBUILT)
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" package="com.test.tilebenchmark">
<uses-permission android:name="android.permission.INTERNET"/>
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:hardwareAccelerated="true">
<activity android:name=".ProfileActivity"
android:label="@string/profile_activity">
<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">
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="@+id/inspect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/inspect_log"
/>
<Spinner
android:id="@+id/velocity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:prompt="@string/desired_scroll_velocity"
/>
<EditText
android:id="@+id/url"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:inputType="textUri"
android:imeOptions="actionGo"
android:layout_weight="1"
/>
</LinearLayout>
<com.test.tilebenchmark.ProfiledWebView
android:id="@+id/web"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="@+id/backward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backward"
/>
<TextView
android:id="@+id/frame_display"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_weight="1"
/>
<Button
android:id="@+id/forward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/forward"
/>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="10"
/>
</LinearLayout>
<com.test.tilebenchmark.PlaybackView
android:id="@+id/playback"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<resources>
<!-- The color of tiles with valid textures -->
<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>
</resources>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<resources>
<!-- Button, steps back a single frame [CHAR LIMIT=15] -->
<string name="backward">Backward</string>
<!-- Button, steps forward a single frame [CHAR LIMIT=15] -->
<string name="forward">Forward</string>
<!-- The name of the application [CHAR LIMIT=20] -->
<string name="app_name">TileBenchmark</string>
<!-- name of the auto-scroller / tile logger activity [CHAR LIMIT=100] -->
<string name="profile_activity">Webview Profiler</string>
<!-- name of the tile log playback activity [CHAR LIMIT=100] -->
<string name="playback_activity">Webview Tile Playback</string>
<!-- Button, loads another tile log [CHAR LIMIT=30] -->
<string name="loadbutton">Load</string>
<!-- Button, opens the playback activity [CHAR LIMIT=20] -->
<string name="inspect_log">Inspect Log</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] -->
<string-array name="velocity_array">
<item>1</item>
<item>25</item>
<item>50</item>
<item>100</item>
<item>200</item>
<item>400</item>
</string-array>
<!-- 25th percentile - 25% of frames fall below this value [CHAR LIMIT=12]
-->
<string name="percentile_25">25%ile</string>
<!-- 50th percentile - 50% of frames fall below this value (aka median)
[CHAR LIMIT=12] -->
<string name="percentile_50">median</string>
<!-- 75th percentile - 75% of frames fall below this value [CHAR LIMIT=12]
-->
<string name="percentile_75">75%ile</string>
<!-- Frame rate [CHAR LIMIT=15] -->
<string name="frames_per_second">Frames/sec</string>
<!-- Portion of viewport covered by good tiles [CHAR LIMIT=15] -->
<string name="viewport_coverage">Coverage</string>
<!-- Format string for stat value overlay [CHAR LIMIT=15] -->
<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>
<!-- 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] -->
<string name="unready_tiles">Unready Tiles</string>
<!-- 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>
</resources>

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* Interface for playing back WebView tile rendering status. Draws viewport and
* states of tiles and statistics for off-line analysis.
*/
public class PlaybackActivity extends Activity {
private static final float SCROLL_SCALER = 0.125f;
PlaybackView mPlaybackView;
SeekBar mSeekBar;
Button mForward;
Button mBackward;
TextView mFrameDisplay;
private int mFrame = -1;
private int mFrameMax;
private class TouchFrameChangeListener extends SimpleOnGestureListener {
float mDist = 0;
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// aggregate scrolls so that small ones can add up
mDist += distanceY * SCROLL_SCALER;
int intComponent = (int) Math.floor(Math.abs(mDist));
if (intComponent >= 1) {
int scrollDist = (mDist > 0) ? intComponent : -intComponent;
setFrame(null, mFrame + scrollDist);
mDist -= scrollDist;
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
};
private class SeekFrameChangeListener implements OnSeekBarChangeListener {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
setFrame(seekBar, progress);
}
};
private class LoadFileTask extends AsyncTask<String, Void, TileData[][]> {
@Override
protected TileData[][] doInBackground(String... params) {
TileData[][] data = null;
try {
FileInputStream fis = openFileInput(params[0]);
ObjectInputStream in = new ObjectInputStream(fis);
data = (TileData[][]) in.readObject();
in.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return data;
}
@Override
protected void onPostExecute(TileData data[][]) {
if (data == null) {
data = genTestPattern();
}
mPlaybackView.setData(data);
mFrameMax = data.length - 1;
mSeekBar.setMax(mFrameMax);
setFrame(null, 0);
}
}
private void setFrame(View changer, int f) {
if (f < 0) {
f = 0;
} else if (f > mFrameMax) {
f = mFrameMax;
}
if (mFrame == f) {
return;
}
mFrame = f;
mForward.setEnabled(mFrame != mFrameMax);
mBackward.setEnabled(mFrame != 0);
if (changer != mSeekBar) {
mSeekBar.setProgress(mFrame);
}
mFrameDisplay.setText(Integer.toString(mFrame));
mPlaybackView.setFrame(mFrame);
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.playback);
mPlaybackView = (PlaybackView) findViewById(R.id.playback);
mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
mForward = (Button) findViewById(R.id.forward);
mBackward = (Button) findViewById(R.id.backward);
mFrameDisplay = (TextView) findViewById(R.id.frame_display);
mForward.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setFrame(v, mFrame + 1);
}
});
mBackward.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setFrame(v, mFrame - 1);
}
});
mSeekBar.setOnSeekBarChangeListener(new SeekFrameChangeListener());
mPlaybackView.setOnGestureListener(new TouchFrameChangeListener());
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;
}
}

View File

@@ -0,0 +1,264 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Arrays;
public class PlaybackGraphs {
private static final int BAR_WIDTH = PlaybackView.TILEX * 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) {
return 1.0f;
}
return 0.0f;
}
private interface MetricGen {
public double getValue(TileData[] frame);
public double getMax();
public int getLabelId();
};
private static MetricGen[] Metrics = new MetricGen[] {
new MetricGen() {
// framerate graph
@Override
public double getValue(TileData[] frame) {
int renderTimeUS = frame[0].level;
return 1.0e6f / renderTimeUS;
}
@Override
public double getMax() {
return IDEAL_FRAMES;
}
@Override
public int getLabelId() {
return R.string.frames_per_second;
}
}, new MetricGen() {
// 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++) {
TileData data = frame[tileID];
double coverage = viewportCoverage(l, b, r, t, data.x,
data.y);
total += coverage * (data.isReady ? 1 : 0);
totalCount += coverage;
}
if (totalCount == 0) {
return -1;
}
return total / totalCount;
}
@Override
public double getMax() {
return 1;
}
@Override
public int getLabelId() {
return R.string.viewport_coverage;
}
}
};
private interface StatGen {
public double getValue(double sortedValues[]);
public int getLabelId();
}
public static double getPercentile(double sortedValues[], double ratioAbove) {
double index = ratioAbove * (sortedValues.length - 1);
int intIndex = (int) Math.floor(index);
if (index == intIndex) {
return sortedValues[intIndex];
}
double alpha = index - intIndex;
return sortedValues[intIndex] * (1 - alpha)
+ sortedValues[intIndex + 1] * (alpha);
}
private static StatGen[] Stats = new StatGen[] {
new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.25);
}
@Override
public int getLabelId() {
return R.string.percentile_25;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.5);
}
@Override
public int getLabelId() {
return R.string.percentile_50;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.75);
}
@Override
public int getLabelId() {
return R.string.percentile_75;
}
},
};
public PlaybackGraphs() {
whiteLabels = new Paint();
whiteLabels.setColor(Color.WHITE);
whiteLabels.setTextSize(PlaybackView.TILEY / 3);
}
private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
private double[][] mStats = new double[Metrics.length][Stats.length];
public void setData(TileData[][] tileProfilingData) {
mShapes.clear();
double metricValues[] = new double[tileProfilingData.length];
if (tileProfilingData.length == 0) {
return;
}
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
// create graph out of rectangles, one per frame
int lastBar = 0;
for (int frameIndex = 0; frameIndex < tileProfilingData.length; frameIndex++) {
TileData frame[] = tileProfilingData[frameIndex];
int newBar = (frame[0].y + frame[1].y) / 2;
MetricGen s = Metrics[metricIndex];
double absoluteValue = s.getValue(frame);
double relativeValue = absoluteValue / s.getMax();
int rightPos = (int) (-BAR_WIDTH * metricIndex);
int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
ShapeDrawable graphBar = new ShapeDrawable();
graphBar.getPaint().setColor(Color.BLUE);
graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
mShapes.add(graphBar);
metricValues[frameIndex] = absoluteValue;
lastBar = newBar;
}
// store aggregate statistics per metric (median, and similar)
Arrays.sort(metricValues);
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
mStats[metricIndex][statIndex] = Stats[statIndex]
.getValue(metricValues);
}
}
}
public void drawVerticalShiftedShapes(Canvas canvas,
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);
for (ShapeDrawable shape : mShapes) {
shape.draw(canvas);
}
for (ShapeDrawable shape : shapes) {
shape.draw(canvas);
}
}
public void draw(Canvas canvas, ArrayList<ShapeDrawable> shapes,
String[] strings, Resources resources) {
canvas.scale(CANVAS_SCALE, CANVAS_SCALE);
canvas.translate(BAR_WIDTH * Metrics.length, 0);
canvas.save();
drawVerticalShiftedShapes(canvas, shapes);
canvas.restore();
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
String label = resources.getString(
Metrics[metricIndex].getLabelId());
int xPos = (metricIndex + 1) * -BAR_WIDTH;
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;
canvas.drawText(label, xPos, yPos, whiteLabels);
}
}
for (int stringIndex = 0; stringIndex < strings.length; stringIndex++) {
int yPos = LABELOFFSET + stringIndex * PlaybackView.TILEY / 2;
canvas.drawText(strings[stringIndex], 0, yPos, whiteLabels);
}
}
public Bundle getStatBundle(Resources resources) {
Bundle b = new Bundle();
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
String metricLabel = resources.getString(
Metrics[metricIndex].getLabelId());
String statLabel = resources.getString(
Stats[statIndex].getLabelId());
double value = mStats[metricIndex][statIndex];
b.putDouble(metricLabel + " " + statLabel, value);
}
}
return b;
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
public class PlaybackView extends View {
public static final int TILEX = 300;
public static final int TILEY = 300;
private Paint levelPaint = null, coordPaint = null, goldPaint = null;
private PlaybackGraphs mGraphs;
private ArrayList<ShapeDrawable> mTempShapes = new ArrayList<ShapeDrawable>();
private TileData mProfData[][] = null;
private GestureDetector mGestureDetector = null;
private String mRenderStrings[] = new String[3];
private class TileDrawable extends ShapeDrawable {
TileData tile;
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);
this.tile = t;
}
@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);
}
}
public PlaybackView(Context context) {
super(context);
init();
}
public PlaybackView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PlaybackView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setOnGestureListener(OnGestureListener gl) {
mGestureDetector = new GestureDetector(getContext(), gl);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}
private void init() {
levelPaint = new Paint();
levelPaint.setColor(Color.WHITE);
levelPaint.setTextSize(TILEY / 2);
coordPaint = new Paint();
coordPaint.setColor(Color.BLACK);
coordPaint.setTextSize(TILEY / 3);
goldPaint = new Paint();
goldPaint.setColor(0xffa0e010);
mGraphs = new PlaybackGraphs();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mTempShapes == null || mTempShapes.isEmpty()) {
return;
}
mGraphs.draw(canvas, mTempShapes, mRenderStrings, getResources());
}
public int setFrame(int frame) {
if (mProfData == null || mProfData.length == 0) {
return 0;
}
int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 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++;
} else {
unreadyTiles++;
}
if (t.x < 0 || t.y < 0) {
unplacedTiles++;
}
}
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);
// 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);
mTempShapes.add(viewShape);
this.invalidate();
return frame;
}
public void setData(TileData[][] tileProfilingData) {
mProfData = tileProfilingData;
mGraphs.setData(mProfData);
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import android.app.Activity;
import android.content.Intent;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* Interface for profiling the webview's scrolling, with simple controls on how
* to scroll, and what content to load.
*/
public class ProfileActivity extends Activity {
public interface ProfileCallback {
public void profileCallback(TileData data[][]);
}
public static final String TEMP_FILENAME = "profile.tiles";
private static final int LOAD_TEST_DELAY = 2000; // nr of millis after load,
// before test
Button mInspectButton;
Spinner mVelocitySpinner;
EditText mUrl;
ProfiledWebView mWeb;
ProfileCallback mCallback;
private class VelocitySelectedListener implements OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
String speedStr = parent.getItemAtPosition(position).toString();
int speedInt = Integer.parseInt(speedStr);
mWeb.setAutoScrollSpeed(speedInt);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
private class LoggingWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mUrl.setText(url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.requestFocus();
new CountDownTimer(LOAD_TEST_DELAY, LOAD_TEST_DELAY) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
mWeb.startScrollTest(mCallback);
}
}.start();
}
}
private class StoreFileTask extends
AsyncTask<Pair<String, TileData[][]>, Void, Void> {
@Override
protected Void doInBackground(Pair<String, TileData[][]>... params) {
try {
FileOutputStream fos = openFileOutput(params[0].first,
Context.MODE_PRIVATE);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(params[0].second);
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
mUrl.setBackgroundResource(R.color.finished_url);
}
}
/** 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);
mVelocitySpinner = (Spinner) findViewById(R.id.velocity);
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));
}
};
// Inspect button (opens PlaybackActivity)
mInspectButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ProfileActivity.this,
PlaybackActivity.class));
}
});
// Velocity spinner
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.velocity_array,
android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mVelocitySpinner.setAdapter(adapter);
mVelocitySpinner.setOnItemSelectedListener(
new VelocitySelectedListener());
mVelocitySpinner.setSelection(3);
// Custom profiling WebView
WebSettings settings = mWeb.getSettings();
settings.setJavaScriptEnabled(true);
settings.setSupportZoom(true);
settings.setEnableSmoothTransition(true);
settings.setBuiltInZoomControls(true);
settings.setLoadWithOverviewMode(true);
mWeb.setWebViewClient(new LoggingWebViewClient());
// URL text entry
mUrl.setOnEditorActionListener(new OnEditorActionListener() {
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;
}
});
}
public void setCallback(ProfileCallback callback) {
mCallback = callback;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWeb.canGoBack()) {
mWeb.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
public class ProfiledWebView extends WebView {
private int mSpeed;
private boolean isScrolling = false;
private ProfileCallback mCallback;
public ProfiledWebView(Context context) {
super(context);
}
public ProfiledWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ProfiledWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ProfiledWebView(Context context, AttributeSet attrs, int defStyle,
boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
}
@Override
protected void onDraw(android.graphics.Canvas canvas) {
if (isScrolling) {
if (canScrollVertically(1)) {
scrollBy(0, mSpeed);
} else {
stopScrollTest();
isScrolling = false;
}
}
super.onDraw(canvas);
}
/*
* Called once the page is loaded to start scrolling for evaluating tiles
*/
public void startScrollTest(ProfileCallback callback) {
isScrolling = true;
mCallback = callback;
super.tileProfilingStart();
invalidate();
}
/*
* Called once the page has stopped scrolling
*/
public void stopScrollTest() {
float testRatio = super.tileProfilingStop();
TileData data[][] = new TileData[super.tileProfilingNumFrames()][];
for (int frame = 0; frame < data.length; frame++) {
data[frame] = new TileData[
super.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);
data[frame][tile] = new TileData(x, y, isReady, level);
}
}
super.tileProfilingClear();
mCallback.profileCallback(data);
}
@Override
public void loadUrl(String url) {
if (!url.startsWith("http://")) {
url = "http://" + url;
}
super.loadUrl(url);
}
public void setAutoScrollSpeed(int speedInt) {
mSpeed = speedInt;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2011 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 com.test.tilebenchmark;
import java.io.Serializable;
public class TileData implements Serializable {
public int x, y;
public boolean isReady;
public int level;
public TileData(int x, int y, boolean isReady, int level) {
this.x = x;
this.y = y;
this.isReady = isReady;
this.level = level;
}
}