diff options
15 files changed, 1207 insertions, 0 deletions
diff --git a/tests/TileBenchmark/Android.mk b/tests/TileBenchmark/Android.mk new file mode 100644 index 0000000..430f0f1 --- /dev/null +++ b/tests/TileBenchmark/Android.mk @@ -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)) diff --git a/tests/TileBenchmark/AndroidManifest.xml b/tests/TileBenchmark/AndroidManifest.xml new file mode 100644 index 0000000..663cc0d --- /dev/null +++ b/tests/TileBenchmark/AndroidManifest.xml @@ -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> diff --git a/tests/TileBenchmark/res/drawable-hdpi/icon.png b/tests/TileBenchmark/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 0000000..8074c4c --- /dev/null +++ b/tests/TileBenchmark/res/drawable-hdpi/icon.png diff --git a/tests/TileBenchmark/res/drawable-ldpi/icon.png b/tests/TileBenchmark/res/drawable-ldpi/icon.png Binary files differnew file mode 100644 index 0000000..1095584 --- /dev/null +++ b/tests/TileBenchmark/res/drawable-ldpi/icon.png diff --git a/tests/TileBenchmark/res/drawable-mdpi/icon.png b/tests/TileBenchmark/res/drawable-mdpi/icon.png Binary files differnew file mode 100644 index 0000000..a07c69f --- /dev/null +++ b/tests/TileBenchmark/res/drawable-mdpi/icon.png diff --git a/tests/TileBenchmark/res/layout/main.xml b/tests/TileBenchmark/res/layout/main.xml new file mode 100644 index 0000000..4a81da6 --- /dev/null +++ b/tests/TileBenchmark/res/layout/main.xml @@ -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> diff --git a/tests/TileBenchmark/res/layout/playback.xml b/tests/TileBenchmark/res/layout/playback.xml new file mode 100644 index 0000000..aa1c8a4 --- /dev/null +++ b/tests/TileBenchmark/res/layout/playback.xml @@ -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> diff --git a/tests/TileBenchmark/res/values/colors.xml b/tests/TileBenchmark/res/values/colors.xml new file mode 100644 index 0000000..3958083 --- /dev/null +++ b/tests/TileBenchmark/res/values/colors.xml @@ -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> diff --git a/tests/TileBenchmark/res/values/strings.xml b/tests/TileBenchmark/res/values/strings.xml new file mode 100644 index 0000000..f70ee2c --- /dev/null +++ b/tests/TileBenchmark/res/values/strings.xml @@ -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> diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java new file mode 100644 index 0000000..5130f5d --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java @@ -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; + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java new file mode 100644 index 0000000..db4a341 --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java @@ -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; + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java new file mode 100644 index 0000000..f104eac --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java @@ -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); + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java new file mode 100644 index 0000000..23b6275 --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java @@ -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); + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java new file mode 100644 index 0000000..6560624 --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java @@ -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; + } +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java new file mode 100644 index 0000000..7d4bb9f --- /dev/null +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java @@ -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; + } +} |