Merge "Fix test break due to API not working as advertised." into lmp-sprout-dev
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
index 9fc52cb..189e987 100644
--- a/apps/CameraITS/tests/scene1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
@@ -20,25 +20,6 @@
import numpy
import os.path
-
-def check_crop_region(expected, reported, active, err_threshold):
- """Check if the reported region is within the tolerance.
-
- Args:
- expected: expected crop region
- reported: reported crop region
- active: active resolution
- err_threshold: error threshold for the active resolution
- """
-
- ex = (active["right"] - active["left"]) * err_threshold
- ey = (active["bottom"] - active["top"]) * err_threshold
-
- assert ((abs(expected["left"] - reported["left"]) <= ex) and
- (abs(expected["right"] - reported["right"]) <= ex) and
- (abs(expected["top"] - reported["top"]) <= ey) and
- (abs(expected["bottom"] - reported["bottom"]) <= ey))
-
def main():
"""Test that raw streams are not croppable.
"""
@@ -53,6 +34,7 @@
its.caps.raw16(props) and
its.caps.per_frame_control(props))
+ # Calculate the active sensor region for a full (non-cropped) image.
a = props['android.sensor.info.activeArraySize']
ax, ay = a["left"], a["top"]
aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
@@ -65,6 +47,19 @@
"bottom": ah
}
+ # Calculate a center crop region.
+ zoom = min(3.0, its.objects.get_max_digital_zoom(props))
+ assert(zoom >= 1)
+ cropw = aw / zoom
+ croph = ah / zoom
+
+ crop_region = {
+ "left": aw / 2 - cropw / 2,
+ "top": ah / 2 - croph / 2,
+ "right": aw / 2 + cropw / 2,
+ "bottom": ah / 2 + croph / 2
+ }
+
# Capture without a crop region.
# Use a manual request with a linear tonemap so that the YUV and RAW
# should look the same (once converted by the its.image module).
@@ -72,59 +67,57 @@
req = its.objects.manual_capture_request(s,e, True)
cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
- # Calculate a center crop region.
- zoom = min(3.0, its.objects.get_max_digital_zoom(props))
- assert(zoom >= 1)
- cropw = aw / zoom
- croph = ah / zoom
-
- req["android.scaler.cropRegion"] = {
- "left": aw / 2 - cropw / 2,
- "top": ah / 2 - croph / 2,
- "right": aw / 2 + cropw / 2,
- "bottom": ah / 2 + croph / 2
- }
-
- # when both YUV and RAW are requested, the crop region that's
- # applied to YUV should be reported.
- crop_region = req["android.scaler.cropRegion"]
- if crop_region == full_region:
- crop_region_err_thresh = 0.0
- else:
- crop_region_err_thresh = CROP_REGION_ERROR_THRESHOLD
-
+ # Capture with a crop region.
+ req["android.scaler.cropRegion"] = crop_region
cap2_raw, cap2_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+ # Check the metadata related to crop regions.
+ # When both YUV and RAW are requested, the crop region that's
+ # applied to YUV should be reported.
+ # Note that the crop region returned by the cropped captures doesn't
+ # need to perfectly match the one that was requested.
imgs = {}
- for s, cap, cr, err_delta in [("yuv_full", cap1_yuv, full_region, 0),
- ("raw_full", cap1_raw, full_region, 0),
- ("yuv_crop", cap2_yuv, crop_region, crop_region_err_thresh),
- ("raw_crop", cap2_raw, crop_region, crop_region_err_thresh)]:
+ for s, cap, cr_expected, err_delta in [
+ ("yuv_full",cap1_yuv,full_region,0),
+ ("raw_full",cap1_raw,full_region,0),
+ ("yuv_crop",cap2_yuv,crop_region,CROP_REGION_ERROR_THRESHOLD),
+ ("raw_crop",cap2_raw,crop_region,CROP_REGION_ERROR_THRESHOLD)]:
+
+ # Convert the capture to RGB and dump to a file.
img = its.image.convert_capture_to_rgb_image(cap, props=props)
its.image.write_image(img, "%s_%s.jpg" % (NAME, s))
- r = cap["metadata"]["android.scaler.cropRegion"]
- x, y = r["left"], r["top"]
- w, h = r["right"] - r["left"], r["bottom"] - r["top"]
imgs[s] = img
- print "Crop on %s: (%d,%d %dx%d)" % (s, x, y, w, h)
- check_crop_region(cr, r, a, err_delta)
+
+ # Get the crop region that is reported in the capture result.
+ cr_reported = cap["metadata"]["android.scaler.cropRegion"]
+ x, y = cr_reported["left"], cr_reported["top"]
+ w = cr_reported["right"] - cr_reported["left"]
+ h = cr_reported["bottom"] - cr_reported["top"]
+ print "Crop reported on %s: (%d,%d %dx%d)" % (s, x, y, w, h)
+
+ # Test that the reported crop region is the same as the expected
+ # one, for a non-cropped capture, and is close to the expected one,
+ # for a cropped capture.
+ ex = aw * err_delta
+ ey = ah * err_delta
+ assert ((abs(cr_expected["left"] - cr_reported["left"]) <= ex) and
+ (abs(cr_expected["right"] - cr_reported["right"]) <= ex) and
+ (abs(cr_expected["top"] - cr_reported["top"]) <= ey) and
+ (abs(cr_expected["bottom"] - cr_reported["bottom"]) <= ey))
# Also check the image content; 3 of the 4 shots should match.
# Note that all the shots are RGB below; the variable names correspond
# to what was captured.
- # Average the images down 4x4 -> 1 prior to comparison to smooth out
- # noise.
- # Shrink the YUV images an additional 2x2 -> 1 to account for the size
- # reduction that the raw images went through in the RGB conversion.
+
+ # Shrink the YUV images 2x2 -> 1 to account for the size reduction that
+ # the raw images went through in the RGB conversion.
imgs2 = {}
for s,img in imgs.iteritems():
h,w,ch = img.shape
- m = 4
if s in ["yuv_full", "yuv_crop"]:
- m = 8
- img = img.reshape(h/m,m,w/m,m,3).mean(3).mean(1).reshape(h/m,w/m,3)
+ img = img.reshape(h/2,2,w/2,2,3).mean(3).mean(1)
+ img = img.reshape(h/2,w/2,3)
imgs2[s] = img
- print s, img.shape
# Strip any border pixels from the raw shots (since the raw images may
# be larger than the YUV images). Assume a symmetric padded border.
@@ -139,7 +132,10 @@
for s,img in imgs2.iteritems():
its.image.write_image(img, "%s_comp_%s.jpg" % (NAME, s))
- # Compute image diffs.
+ # Compute diffs between images of the same type.
+ # The raw_crop and raw_full shots should be identical (since the crop
+ # doesn't apply to raw images), and the yuv_crop and yuv_full shots
+ # should be different.
diff_yuv = numpy.fabs((imgs2["yuv_full"] - imgs2["yuv_crop"])).mean()
diff_raw = numpy.fabs((imgs2["raw_full"] - imgs2["raw_crop"])).mean()
print "YUV diff (crop vs. non-crop):", diff_yuv
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index e370c81..87f962f 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,7 +25,10 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := cts-sensors-tests ctstestrunner android-ex-camera2
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
+ compatibility-common-util-devicesidelib_v2 \
+ cts-sensors-tests \
+ ctstestrunner \
LOCAL_PACKAGE_NAME := CtsVerifier
@@ -40,10 +43,13 @@
include $(BUILD_PACKAGE)
+notification-bot := $(call intermediates-dir-for,APPS,NotificationBot)/package.apk
+
# Builds and launches CTS Verifier on a device.
.PHONY: cts-verifier
-cts-verifier: CtsVerifier adb
+cts-verifier: CtsVerifier adb NotificationBot
adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
+ && adb install -r $(notification-bot) \
&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
#
@@ -79,10 +85,11 @@
$(verifier-zip) : $(HOST_OUT)/bin/cts-usb-accessory
endif
$(verifier-zip) : $(HOST_OUT)/CameraITS
-
+$(verifier-zip) : $(notification-bot)
$(verifier-zip) : $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk | $(ACP)
$(hide) mkdir -p $(verifier-dir)
$(hide) $(ACP) -fp $< $(verifier-dir)/CtsVerifier.apk
+ $(ACP) -fp $(notification-bot) $(verifier-dir)/NotificationBot.apk
ifeq ($(HOST_OS),linux)
$(hide) $(ACP) -fp $(HOST_OUT)/bin/cts-usb-accessory $(verifier-dir)/cts-usb-accessory
endif
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index ab119bd..5a08558 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier;
+import com.android.compatibility.common.util.ReportLog;
+
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
@@ -94,10 +96,18 @@
* @param passed Whether or not the test passed.
*/
void setTestResultAndFinish(boolean passed);
+
+ /** @return A {@link ReportLog} that is used to record test metric data. */
+ ReportLog getReportLog();
}
public static class Activity extends android.app.Activity implements PassFailActivity {
private WakeLock mWakeLock;
+ private final ReportLog reportLog;
+
+ public Activity() {
+ this.reportLog = new CtsVerifierReportLog();
+ }
@Override
protected void onResume() {
@@ -149,13 +159,22 @@
@Override
public void setTestResultAndFinish(boolean passed) {
- PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
- passed);
+ PassFailButtons.setTestResultAndFinishHelper(
+ this, getTestId(), getTestDetails(), passed, getReportLog());
}
+
+ @Override
+ public ReportLog getReportLog() { return reportLog; }
}
public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
+ private final ReportLog reportLog;
+
+ public ListActivity() {
+ this.reportLog = new CtsVerifierReportLog();
+ }
+
@Override
public void setPassFailButtonClickListeners() {
setPassFailClickListeners(this);
@@ -188,14 +207,23 @@
@Override
public void setTestResultAndFinish(boolean passed) {
- PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
- passed);
+ PassFailButtons.setTestResultAndFinishHelper(
+ this, getTestId(), getTestDetails(), passed, getReportLog());
}
+
+ @Override
+ public ReportLog getReportLog() { return reportLog; }
}
public static class TestListActivity extends AbstractTestListActivity
implements PassFailActivity {
+ private final ReportLog reportLog;
+
+ public TestListActivity() {
+ this.reportLog = new CtsVerifierReportLog();
+ }
+
@Override
public void setPassFailButtonClickListeners() {
setPassFailClickListeners(this);
@@ -228,9 +256,12 @@
@Override
public void setTestResultAndFinish(boolean passed) {
- PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
- passed);
+ PassFailButtons.setTestResultAndFinishHelper(
+ this, getTestId(), getTestDetails(), passed, getReportLog());
}
+
+ @Override
+ public ReportLog getReportLog() { return reportLog; }
}
private static <T extends android.app.Activity & PassFailActivity>
@@ -239,7 +270,7 @@
@Override
public void onClick(View target) {
setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
- target);
+ activity.getReportLog(), target);
}
};
@@ -366,7 +397,7 @@
/** Set the test result corresponding to the button clicked and finish the activity. */
private static void setTestResultAndFinish(android.app.Activity activity, String testId,
- String testDetails, View target) {
+ String testDetails, ReportLog reportLog, View target) {
boolean passed;
switch (target.getId()) {
case R.id.pass_button:
@@ -378,16 +409,16 @@
default:
throw new IllegalArgumentException("Unknown id: " + target.getId());
}
- setTestResultAndFinishHelper(activity, testId, testDetails, passed);
+ setTestResultAndFinishHelper(activity, testId, testDetails, passed, reportLog);
}
/** Set the test result and finish the activity. */
private static void setTestResultAndFinishHelper(android.app.Activity activity, String testId,
- String testDetails, boolean passed) {
+ String testDetails, boolean passed, ReportLog reportLog) {
if (passed) {
- TestResult.setPassedResult(activity, testId, testDetails);
+ TestResult.setPassedResult(activity, testId, testDetails, reportLog);
} else {
- TestResult.setFailedResult(activity, testId, testDetails);
+ TestResult.setFailedResult(activity, testId, testDetails, reportLog);
}
activity.finish();
@@ -396,4 +427,8 @@
private static ImageButton getPassButtonView(android.app.Activity activity) {
return (ImageButton) activity.findViewById(R.id.pass_button);
}
+
+ public static class CtsVerifierReportLog extends ReportLog {
+
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index afe3a73..2160902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier;
+import com.android.compatibility.common.util.ReportLog;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +32,9 @@
import android.widget.ListView;
import android.widget.TextView;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -66,6 +71,9 @@
/** Map from test name to test details. */
private final Map<String, String> mTestDetails = new HashMap<String, String>();
+ /** Map from test name to {@link ReportLog}. */
+ private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>();
+
private final LayoutInflater mLayoutInflater;
/** {@link ListView} row that is either a test category header or a test. */
@@ -168,7 +176,7 @@
public void setTestResult(TestResult testResult) {
new SetTestResultTask(testResult.getName(), testResult.getResult(),
- testResult.getDetails()).execute();
+ testResult.getDetails(), testResult.getReportLog()).execute();
}
class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
@@ -187,6 +195,8 @@
mTestResults.putAll(result.mResults);
mTestDetails.clear();
mTestDetails.putAll(result.mDetails);
+ mReportLogs.clear();
+ mReportLogs.putAll(result.mReportLogs);
notifyDataSetChanged();
}
}
@@ -195,12 +205,17 @@
List<TestListItem> mItems;
Map<String, Integer> mResults;
Map<String, String> mDetails;
+ Map<String, ReportLog> mReportLogs;
- RefreshResult(List<TestListItem> items, Map<String, Integer> results,
- Map<String, String> details) {
+ RefreshResult(
+ List<TestListItem> items,
+ Map<String, Integer> results,
+ Map<String, String> details,
+ Map<String, ReportLog> reportLogs) {
mItems = items;
mResults = results;
mDetails = details;
+ mReportLogs = reportLogs;
}
}
@@ -211,11 +226,13 @@
TestResultsProvider.COLUMN_TEST_NAME,
TestResultsProvider.COLUMN_TEST_RESULT,
TestResultsProvider.COLUMN_TEST_DETAILS,
+ TestResultsProvider.COLUMN_TEST_METRICS,
};
RefreshResult getRefreshResults(List<TestListItem> items) {
Map<String, Integer> results = new HashMap<String, Integer>();
Map<String, String> details = new HashMap<String, String>();
+ Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>();
ContentResolver resolver = mContext.getContentResolver();
Cursor cursor = null;
try {
@@ -226,8 +243,10 @@
String testName = cursor.getString(1);
int testResult = cursor.getInt(2);
String testDetails = cursor.getString(3);
+ ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4));
results.put(testName, testResult);
details.put(testName, testDetails);
+ reportLogs.put(testName, reportLog);
} while (cursor.moveToNext());
}
} finally {
@@ -235,7 +254,7 @@
cursor.close();
}
}
- return new RefreshResult(items, results, details);
+ return new RefreshResult(items, results, details, reportLogs);
}
class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
@@ -256,15 +275,22 @@
private final String mDetails;
- SetTestResultTask(String testName, int result, String details) {
+ private final ReportLog mReportLog;
+
+ SetTestResultTask(
+ String testName,
+ int result,
+ String details,
+ ReportLog reportLog) {
mTestName = testName;
mResult = result;
mDetails = details;
+ mReportLog = reportLog;
}
@Override
protected Void doInBackground(Void... params) {
- TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails);
+ TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails, mReportLog);
return null;
}
}
@@ -332,6 +358,13 @@
: null;
}
+ public ReportLog getReportLog(int position) {
+ TestListItem item = getItem(position);
+ return mReportLogs.containsKey(item.testName)
+ ? mReportLogs.get(item.testName)
+ : null;
+ }
+
public boolean allTestsPassed() {
for (TestListItem item : mRows) {
if (item.isTest() && (!mTestResults.containsKey(item.testName)
@@ -400,4 +433,29 @@
}
}
+
+ private static Object deserialize(byte[] bytes) {
+ if (bytes == null || bytes.length == 0) {
+ return null;
+ }
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+ ObjectInputStream objectInput = null;
+ try {
+ objectInput = new ObjectInputStream(byteStream);
+ return objectInput.readObject();
+ } catch (IOException e) {
+ return null;
+ } catch (ClassNotFoundException e) {
+ return null;
+ } finally {
+ try {
+ if (objectInput != null) {
+ objectInput.close();
+ }
+ byteStream.close();
+ } catch (IOException e) {
+ // Ignore close exception.
+ }
+ }
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 68513ac..d8a675c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier;
+import com.android.compatibility.common.util.ReportLog;
+
import android.app.Activity;
import android.content.Intent;
@@ -35,29 +37,44 @@
private static final String TEST_NAME = "name";
private static final String TEST_RESULT = "result";
private static final String TEST_DETAILS = "details";
+ private static final String TEST_METRICS = "metrics";
private final String mName;
private final int mResult;
private final String mDetails;
+ private final ReportLog mReportLog;
/** Sets the test activity's result to pass. */
public static void setPassedResult(Activity activity, String testId, String testDetails) {
+ setPassedResult(activity, testId, testDetails, null /*reportLog*/);
+ }
+
+ /** Sets the test activity's result to pass including a test report log result. */
+ public static void setPassedResult(Activity activity, String testId, String testDetails,
+ ReportLog reportLog) {
activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
- testDetails));
+ testDetails, reportLog));
}
/** Sets the test activity's result to failed. */
public static void setFailedResult(Activity activity, String testId, String testDetails) {
+ setFailedResult(activity, testId, testDetails, null /*reportLog*/);
+ }
+
+ /** Sets the test activity's result to failed including a test report log result. */
+ public static void setFailedResult(Activity activity, String testId, String testDetails,
+ ReportLog reportLog) {
activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
- testDetails));
+ testDetails, reportLog));
}
private static Intent createResult(Activity activity, int testResult, String testName,
- String testDetails) {
+ String testDetails, ReportLog reportLog) {
Intent data = new Intent(activity, activity.getClass());
data.putExtra(TEST_NAME, testName);
data.putExtra(TEST_RESULT, testResult);
data.putExtra(TEST_DETAILS, testDetails);
+ data.putExtra(TEST_METRICS, reportLog);
return data;
}
@@ -69,13 +86,16 @@
String name = data.getStringExtra(TEST_NAME);
int result = data.getIntExtra(TEST_RESULT, TEST_RESULT_NOT_EXECUTED);
String details = data.getStringExtra(TEST_DETAILS);
- return new TestResult(name, result, details);
+ ReportLog reportLog = (ReportLog) data.getSerializableExtra(TEST_METRICS);
+ return new TestResult(name, result, details, reportLog);
}
- private TestResult(String name, int result, String details) {
+ private TestResult(
+ String name, int result, String details, ReportLog reportLog) {
this.mName = name;
this.mResult = result;
this.mDetails = details;
+ this.mReportLog = reportLog;
}
/** Return the name of the test like "com.android.cts.verifier.foo.FooTest" */
@@ -92,4 +112,9 @@
public String getDetails() {
return mDetails;
}
+
+ /** @return the {@link ReportLog} or null if not set */
+ public ReportLog getReportLog() {
+ return mReportLog;
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
index e4cd24a..45e528f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
@@ -59,6 +59,7 @@
int resultIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_RESULT);
int infoSeenIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_INFO_SEEN);
int detailsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_DETAILS);
+ int metricsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_METRICS);
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
DataOutputStream dataOutput = new DataOutputStream(byteOutput);
@@ -69,11 +70,16 @@
int result = cursor.getInt(resultIndex);
int infoSeen = cursor.getInt(infoSeenIndex);
String details = cursor.getString(detailsIndex);
+ byte[] metricsData = cursor.getBlob(metricsIndex);
dataOutput.writeUTF(name);
dataOutput.writeInt(result);
dataOutput.writeInt(infoSeen);
dataOutput.writeUTF(details != null ? details : "");
+ dataOutput.writeInt(metricsData.length);
+ if (metricsData.length > 0) {
+ dataOutput.write(metricsData);
+ }
}
byte[] rawBytes = byteOutput.toByteArray();
@@ -106,12 +112,19 @@
int result = dataInput.readInt();
int infoSeen = dataInput.readInt();
String details = dataInput.readUTF();
+ int metricsDataSize = dataInput.readInt();
values[i] = new ContentValues();
values[i].put(TestResultsProvider.COLUMN_TEST_NAME, name);
values[i].put(TestResultsProvider.COLUMN_TEST_RESULT, result);
values[i].put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, infoSeen);
values[i].put(TestResultsProvider.COLUMN_TEST_DETAILS, details);
+
+ if (metricsDataSize > 0) {
+ byte[] metrics = new byte[metricsDataSize];
+ dataInput.readFully(metrics);
+ values[i].put(TestResultsProvider.COLUMN_TEST_METRICS, metrics);
+ }
}
ContentResolver resolver = mContext.getContentResolver();
@@ -127,7 +140,7 @@
private void failBackupTest() {
TestResultsProvider.setTestResult(mContext, BackupTestActivity.class.getName(),
- TestResult.TEST_RESULT_FAILED, null);
+ TestResult.TEST_RESULT_FAILED, null /*testDetails*/, null /*testMetrics*/);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
index df05519..a9f672e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier;
+import com.android.compatibility.common.util.ReportLog;
+
import android.app.backup.BackupManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -28,6 +30,10 @@
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
/** {@link ContentProvider} that provides read and write access to the test results. */
public class TestResultsProvider extends ContentProvider {
@@ -56,6 +62,9 @@
/** String containing the test's details. */
static final String COLUMN_TEST_DETAILS = "testdetails";
+ /** ReportLog containing the test result metrics. */
+ static final String COLUMN_TEST_METRICS = "testmetrics";
+
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int RESULTS_ALL = 1;
private static final int RESULTS_ID = 2;
@@ -96,7 +105,8 @@
+ COLUMN_TEST_NAME + " TEXT, "
+ COLUMN_TEST_RESULT + " INTEGER,"
+ COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
- + COLUMN_TEST_DETAILS + " TEXT);");
+ + COLUMN_TEST_DETAILS + " TEXT,"
+ + COLUMN_TEST_METRICS + " BLOB);");
}
@Override
@@ -202,11 +212,12 @@
}
static void setTestResult(Context context, String testName, int testResult,
- String testDetails) {
+ String testDetails, ReportLog reportLog) {
ContentValues values = new ContentValues(2);
values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
+ values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
ContentResolver resolver = context.getContentResolver();
int numUpdated = resolver.update(TestResultsProvider.RESULTS_CONTENT_URI, values,
@@ -218,4 +229,24 @@
}
}
+ private static byte[] serialize(Object o) {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutput = null;
+ try {
+ objectOutput = new ObjectOutputStream(byteStream);
+ objectOutput.writeObject(o);
+ return byteStream.toByteArray();
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try {
+ if (objectOutput != null) {
+ objectOutput.close();
+ }
+ byteStream.close();
+ } catch (IOException e) {
+ // Ignore close exception.
+ }
+ }
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index e40b428..dc2502c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier;
+import com.android.compatibility.common.util.MetricsXmlSerializer;
+import com.android.compatibility.common.util.ReportLog;
import com.android.cts.verifier.TestListAdapter.TestListItem;
import org.xmlpull.v1.XmlSerializer;
@@ -128,6 +130,12 @@
xml.endTag(null, TEST_DETAILS_TAG);
}
+ ReportLog reportLog = mAdapter.getReportLog(i);
+ if (reportLog != null) {
+ MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(xml);
+ metricsXmlSerializer.serialize(reportLog);
+ }
+
xml.endTag(null, TEST_TAG);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
index 510a03b..c202bb1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
@@ -35,6 +35,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
@@ -89,6 +90,7 @@
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
+ ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(1000);
}
};
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
index 25f90d9..41bc303 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
@@ -16,8 +16,12 @@
package com.android.cts.verifier.sample;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
import android.content.Intent;
import android.net.Uri;
@@ -61,6 +65,7 @@
public void onClick(View v) {
try {
createFileAndShare();
+ recordMetricsExample();
} catch (Exception e) {
e.printStackTrace();
}
@@ -68,6 +73,21 @@
});
}
+ private void recordMetricsExample() {
+ double[] metricValues = new double[] {1, 11, 21, 1211, 111221};
+
+ // Record metric results
+ getReportLog().setSummary(
+ "Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+ getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+
+ // Alternatively, activities can invoke TestResult directly to record metrics
+ ReportLog reportLog = new PassFailButtons.CtsVerifierReportLog();
+ reportLog.setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+ getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+ TestResult.setPassedResult(this, "manualSample", "manualDetails", reportLog);
+ }
+
/**
* Creates a temporary file containing the test string and then issues the intent to share it.
*
diff --git a/apps/NotificationBot/Android.mk b/apps/NotificationBot/Android.mk
new file mode 100644
index 0000000..9d9c9f9
--- /dev/null
+++ b/apps/NotificationBot/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2014 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_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+
+LOCAL_PACKAGE_NAME := NotificationBot
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
new file mode 100644
index 0000000..b63791f
--- /dev/null
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2010 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.robot"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/icon"
+ android:debuggable="true">
+
+ <!-- Required because a bare service won't show up in the app notifications list. -->
+ <activity android:name=".NotificationBotActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- services used by the CtsVerifier to test notifications. -->
+ <receiver android:name=".NotificationBot" android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.cts.robot.ACTION_POST" />
+ <action android:name="com.android.cts.robot.ACTION_CANCEL" />
+ </intent-filter>
+ </receiver>
+
+
+ </application>
+
+</manifest>
diff --git a/apps/NotificationBot/proguard.flags b/apps/NotificationBot/proguard.flags
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/NotificationBot/proguard.flags
diff --git a/apps/NotificationBot/res/drawable-hdpi/icon.png b/apps/NotificationBot/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..ecaabbe
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-ldpi/icon.png b/apps/NotificationBot/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..f2de61f
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-mdpi/icon.png b/apps/NotificationBot/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..4950761
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xhdpi/icon.png b/apps/NotificationBot/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..9b39cfb
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xxhdpi/icon.png b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..b944c10
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/layout/main.xml b/apps/NotificationBot/res/layout/main.xml
new file mode 100644
index 0000000..bf84fa9
--- /dev/null
+++ b/apps/NotificationBot/res/layout/main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <Space android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/call_to_action"
+ android:textAlignment="center"
+ />
+ <Space android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/NotificationBot/res/values/strings.xml b/apps/NotificationBot/res/values/strings.xml
new file mode 100644
index 0000000..866a9ec
--- /dev/null
+++ b/apps/NotificationBot/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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>
+ <string name="app_name">CTS Robot</string>
+ <string name="call_to_action">Nothing to do here,\nPlease run the CTSVerifier App instead.</string>
+</resources>
\ No newline at end of file
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
new file mode 100644
index 0000000..2aa5f41
--- /dev/null
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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.android.cts.robot;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class NotificationBot extends BroadcastReceiver {
+ private static final String TAG = "NotificationBot";
+ private static final String EXTRA_ID = "ID";
+ private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+ private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
+ private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "received intent: " + intent);
+ if (ACTION_POST.equals(intent.getAction())) {
+ Log.i(TAG, ACTION_POST);
+ if (!intent.hasExtra(EXTRA_NOTIFICATION) || !intent.hasExtra(EXTRA_ID)) {
+ Log.e(TAG, "received post action with missing content");
+ return;
+ }
+ int id = intent.getIntExtra(EXTRA_ID, -1);
+ Log.i(TAG, "id: " + id);
+ Notification n = (Notification) intent.getParcelableExtra(EXTRA_NOTIFICATION);
+ Log.i(TAG, "n: " + n);
+ NotificationManager noMa =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMa.notify(id, n);
+
+ } else if (ACTION_CANCEL.equals(intent.getAction())) {
+ Log.i(TAG, ACTION_CANCEL);
+ int id = intent.getIntExtra(EXTRA_ID, -1);
+ Log.i(TAG, "id: " + id);
+ if (id < 0) {
+ Log.e(TAG, "received cancel action with no ID");
+ return;
+ }
+ NotificationManager noMa =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMa.cancel(id);
+
+ } else {
+ Log.i(TAG, "received unexpected action: " + intent.getAction());
+ }
+ }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
similarity index 64%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
index a376373..1b9408e 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
@@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.cts.robot;
-package com.android.compatibility.common.util;
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.cts.robot.R;
-import junit.framework.TestCase;
-
-public class CommonUtilTest extends TestCase {
-
- // TODO(stuartscott): Add tests when there is something to test.
-
-}
+public class NotificationBotActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
\ No newline at end of file
diff --git a/common/util/Android.mk b/common/util/Android.mk
index b7842559..84ced65 100644
--- a/common/util/Android.mk
+++ b/common/util/Android.mk
@@ -42,6 +42,8 @@
LOCAL_MODULE := compatibility-common-util-hostsidelib_v2
+LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
+
include $(BUILD_HOST_JAVA_LIBRARY)
###############################################################################
@@ -52,7 +54,10 @@
LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-LOCAL_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ junit \
+ kxml2-2.3.0 \
+ compatibility-common-util-hostsidelib_v2
LOCAL_MODULE := compatibility-common-util-tests_v2
diff --git a/common/util/run_unit_tests.sh b/common/util/run_unit_tests.sh
new file mode 100755
index 0000000..04a6745
--- /dev/null
+++ b/common/util/run_unit_tests.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# Copyright (C) 2012 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.
+
+# helper script for running the cts common unit tests
+
+checkFile() {
+ if [ ! -f "$1" ]; then
+ echo "Unable to locate $1"
+ exit
+ fi;
+}
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+ HOST=`uname`
+ if [ "$HOST" == "Linux" ]; then
+ OS="linux-x86"
+ elif [ "$HOST" == "Darwin" ]; then
+ OS="darwin-x86"
+ else
+ echo "Unrecognized OS"
+ exit
+ fi;
+fi;
+
+JAR_DIR=${ANDROID_BUILD_TOP}/out/host/$OS/framework
+JARS="tradefed-prebuilt.jar compatibility-common-util-hostsidelib_v2.jar compatibility-common-util-tests_v2.jar"
+
+for JAR in $JARS; do
+ checkFile ${JAR_DIR}/${JAR}
+ JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+ -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.compatibility.common.util.UnitTests "$@"
+
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
new file mode 100644
index 0000000..0e2b004
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 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.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Serialize Metric data from {@link ReportLog} into compatibility report friendly XML
+ */
+public final class MetricsXmlSerializer {
+
+ private final XmlSerializer mXmlSerializer;
+
+ public MetricsXmlSerializer(XmlSerializer xmlSerializer) {
+ this.mXmlSerializer = xmlSerializer;
+ }
+
+ public void serialize(ReportLog reportLog) throws IOException {
+ if (reportLog == null) {
+ return;
+ }
+ ReportLog.Result summary = reportLog.getSummary();
+ List<ReportLog.Result> detailedMetrics = reportLog.getDetailedMetrics();
+ // <Summary message="Average" scoreType="lower_better" unit="ms">195.2</Summary>
+ if (summary != null) {
+ mXmlSerializer.startTag(null, "Summary");
+ mXmlSerializer.attribute(null, "message", summary.getMessage());
+ mXmlSerializer.attribute(null, "scoreType", summary.getType().getXmlString());
+ mXmlSerializer.attribute(null, "unit", summary.getUnit().getXmlString());
+ mXmlSerializer.text(Double.toString(summary.getValues()[0]));
+ mXmlSerializer.endTag(null, "Summary");
+ }
+
+ if (!detailedMetrics.isEmpty()) {
+ mXmlSerializer.startTag(null, "Details");
+ for (ReportLog.Result result : detailedMetrics) {
+ mXmlSerializer.startTag(null, "ValueArray");
+ mXmlSerializer.attribute(null, "source", result.getLocation());
+ mXmlSerializer.attribute(null, "message", result.getMessage());
+ mXmlSerializer.attribute(null, "scoreType", result.getType().getXmlString());
+ mXmlSerializer.attribute(null, "unit", result.getUnit().getXmlString());
+
+ for (double value : result.getValues()) {
+ mXmlSerializer.startTag(null, "Value");
+ mXmlSerializer.text(Double.toString(value));
+ mXmlSerializer.endTag(null, "Value");
+ }
+ mXmlSerializer.endTag(null, "ValueArray");
+ }
+ mXmlSerializer.endTag(null, "Details");
+ }
+ }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ReportLog.java b/common/util/src/com/android/compatibility/common/util/ReportLog.java
index 9e733e4..8cfc086 100644
--- a/common/util/src/com/android/compatibility/common/util/ReportLog.java
+++ b/common/util/src/com/android/compatibility/common/util/ReportLog.java
@@ -16,6 +16,9 @@
package com.android.compatibility.common.util;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -28,8 +31,8 @@
private Result mSummary;
private final List<Result> mDetails = new ArrayList<Result>();
- private class Result implements Serializable {
- private static final int BASE_DEPTH = 2;// 0:constructor, 1:addValues/setSummary, 2:caller
+ class Result implements Serializable {
+ private static final int CALLER_STACKTRACE_DEPTH = 5;
private String mLocation;
private String mMessage;
private double[] mValues;
@@ -53,15 +56,35 @@
private Result(String message, double[] values, ResultType type,
ResultUnit unit, int depth) {
final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
- final StackTraceElement e = trace[Math.min(BASE_DEPTH + depth, trace.length - 1)];
- mLocation = String.format("%s#%s:%d",
- e.getClassName(), e.getMethodName(), e.getLineNumber());
+ final StackTraceElement e =
+ trace[Math.min(CALLER_STACKTRACE_DEPTH + depth, trace.length - 1)];
+ mLocation = String.format(
+ "%s#%s:%d", e.getClassName(), e.getMethodName(), e.getLineNumber());
mMessage = message;
mValues = values;
mType = type;
mUnit = unit;
}
+ public String getLocation() {
+ return mLocation;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public double[] getValues() {
+ return mValues;
+ }
+
+ public ResultType getType() {
+ return mType;
+ }
+
+ public ResultUnit getUnit() {
+ return mUnit;
+ }
}
/**
@@ -108,4 +131,12 @@
ResultUnit unit, int depth) {
mSummary = new Result(message, new double[] {value}, type, unit, depth);
}
+
+ public Result getSummary() {
+ return mSummary;
+ }
+
+ public List<Result> getDetailedMetrics() {
+ return new ArrayList<Result>(mDetails);
+ }
}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
new file mode 100644
index 0000000..70da820
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 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.android.compatibility.common.util;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link MetricsXmlSerializer}
+ */
+public class MetricsXmlSerializerTest extends TestCase {
+
+ static class LocalReportLog extends ReportLog {}
+ private static final double[] VALUES = new double[] {1, 11, 21, 1211, 111221};
+ private static final String HEADER = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>";
+ private static final String EXPECTED_XML =
+ HEADER
+ + "<Summary message=\"Sample\" scoreType=\"higher_better\" unit=\"byte\">1.0</Summary>"
+ + "<Details>"
+ + "<ValueArray source=\"sun.reflect.NativeMethodAccessorImpl#invoke0:-2\""
+ + " message=\"Details\" scoreType=\"neutral\" unit=\"fps\">"
+ + "<Value>1.0</Value>"
+ + "<Value>11.0</Value>"
+ + "<Value>21.0</Value>"
+ + "<Value>1211.0</Value>"
+ + "<Value>111221.0</Value>"
+ + "</ValueArray>"
+ + "</Details>";
+
+ private LocalReportLog mLocalReportLog;
+ private MetricsXmlSerializer mMetricsXmlSerializer;
+ private ByteArrayOutputStream mByteArrayOutputStream;
+ private XmlSerializer xmlSerializer;
+
+ @Override
+ public void setUp() throws Exception {
+ mLocalReportLog = new LocalReportLog();
+ mByteArrayOutputStream = new ByteArrayOutputStream();
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance(null, null);
+ xmlSerializer = factory.newSerializer();
+ xmlSerializer.setOutput(mByteArrayOutputStream, "utf-8");
+
+ this.mMetricsXmlSerializer = new MetricsXmlSerializer(xmlSerializer);
+ }
+
+ public void testSerialize_null() throws IOException {
+ xmlSerializer.startDocument("utf-8", true);
+ mMetricsXmlSerializer.serialize(null);
+ xmlSerializer.endDocument();
+
+ assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+ }
+
+ public void testSerialize_noData() throws IOException {
+ xmlSerializer.startDocument("utf-8", true);
+ mMetricsXmlSerializer.serialize(mLocalReportLog);
+ xmlSerializer.endDocument();
+
+ assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+ }
+
+ public void testSerialize() throws IOException {
+ mLocalReportLog.setSummary("Sample", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+ mLocalReportLog.addValues("Details", VALUES, ResultType.NEUTRAL, ResultUnit.FPS);
+
+ xmlSerializer.startDocument("utf-8", true);
+ mMetricsXmlSerializer.serialize(mLocalReportLog);
+ xmlSerializer.endDocument();
+
+ assertEquals(EXPECTED_XML, mByteArrayOutputStream.toString("utf-8"));
+ }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
similarity index 70%
rename from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
rename to common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
index a376373..b9a17e1 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
@@ -11,15 +11,21 @@
* 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.
+ * limitations under the License
*/
package com.android.compatibility.common.util;
-import junit.framework.TestCase;
+import junit.framework.TestSuite;
-public class CommonUtilTest extends TestCase {
+/**
+ * A {@link TestSuite} for the common.util package.
+ */
+public class UnitTests extends TestSuite {
- // TODO(stuartscott): Add tests when there is something to test.
+ public UnitTests() {
+ super();
+ addTestSuite(MetricsXmlSerializerTest.class);
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
index a68286a..41018a9 100644
--- a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
+++ b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
@@ -21,15 +21,37 @@
import android.app.Activity;
import android.os.Bundle;
import android.widget.PopupWindow;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.Window;
+import android.view.WindowInsets;
/**
* Stub activity for testing {@link PopupWindow}
*/
public class MockPopupWindowCtsActivity extends Activity {
+ private boolean isFirstRun = true;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.popupwindow);
+ Window window = getWindow();
+ final View decor = window.getDecorView();
+ decor.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ if (isFirstRun) {
+ if (insets.isRound()) {
+ decor.setPadding(decor.getPaddingLeft(), decor.getPaddingTop(),
+ decor.getPaddingRight(),
+ decor.getPaddingBottom() + insets.getSystemWindowInsetBottom());
+ }
+ isFirstRun = false;
+ setContentView(R.layout.popupwindow);
+ }
+ return insets.consumeSystemWindowInsets();
+ }
+ });
}
}