Merge "Increasing dhrystone failure threshold to 10%." into nyc-dr1-dev
am: 5b417b68d9
Change-Id: I707a6af27481fb75c73ef838035aa97ddfe55859
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index bcf294a..c33092d 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -31,12 +31,23 @@
python -V 2>&1 | grep -q "Python 2.7" || \
echo ">> Require python 2.7" >&2
-for M in numpy PIL Image matplotlib pylab cv2 scipy.stats scipy.spatial
+for M in numpy PIL Image matplotlib pylab scipy.stats scipy.spatial
do
python -c "import $M" >/dev/null 2>&1 || \
echo ">> Require Python $M module" >&2
done
+CV2_VER=$(python -c "\
+try:
+ import cv2
+ print cv2.__version__
+except:
+ print \"N/A\"
+")
+
+echo $CV2_VER | grep -q "^2.4" || \
+ echo ">> Require python opencv 2.4. Got $CV2_VER" >&2
+
export PYTHONPATH="$PWD/pymodules:$PYTHONPATH"
for M in device objects image caps dng target error
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 351b03c..692a62d 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -67,9 +67,17 @@
PACKAGE = 'com.android.cts.verifier.camera.its'
INTENT_START = 'com.android.cts.verifier.camera.its.START'
ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
+ EXTRA_VERSION = 'camera.its.extra.VERSION'
+ CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
- EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
- EXTRA_SUMMARY = 'camera.its.extra.SUMMARY'
+ EXTRA_RESULTS = 'camera.its.extra.RESULTS'
+
+ RESULT_PASS = 'PASS'
+ RESULT_FAIL = 'FAIL'
+ RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
+ RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
+ RESULT_KEY = 'result'
+ SUMMARY_KEY = 'summary'
adb = "adb -d"
device_id = ""
@@ -781,34 +789,43 @@
return device_id
-def report_result(device_id, camera_id, success, summary_path=None):
+def report_result(device_id, camera_id, results):
"""Send a pass/fail result to the device, via an intent.
Args:
device_id: The ID string of the device to report the results to.
camera_id: The ID string of the camera for which to report pass/fail.
- success: Boolean, indicating if the result was pass or fail.
- summary_path: (Optional) path to ITS summary file on host PC
-
+ results: a dictionary contains all ITS scenes as key and result/summary
+ of current ITS run. See test_report_result unit test for
+ an example.
Returns:
Nothing.
"""
adb = "adb -s " + device_id
- device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt"
- if summary_path is not None:
- _run("%s push %s %s" % (
- adb, summary_path, device_summary_path))
- _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
- adb, ItsSession.ACTION_ITS_RESULT,
- ItsSession.EXTRA_CAMERA_ID, camera_id,
- ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
- ItsSession.EXTRA_SUMMARY, device_summary_path))
- else:
- _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
- adb, ItsSession.ACTION_ITS_RESULT,
- ItsSession.EXTRA_CAMERA_ID, camera_id,
- ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
- ItsSession.EXTRA_SUMMARY, "null"))
+ # Validate/process results argument
+ for scene in results:
+ result_key = ItsSession.RESULT_KEY
+ summary_key = ItsSession.SUMMARY_KEY
+ if result_key not in results[scene]:
+ raise its.error.Error('ITS result not found for ' + scene)
+ if results[scene][result_key] not in ItsSession.RESULT_VALUES:
+ raise its.error.Error('Unknown ITS result for %s: %s' % (
+ scene, results[result_key]))
+ if summary_key in results[scene]:
+ device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
+ camera_id, scene)
+ _run("%s push %s %s" % (
+ adb, results[scene][summary_key], device_summary_path))
+ results[scene][summary_key] = device_summary_path
+ json_results = json.dumps(results)
+ cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
+ adb, ItsSession.ACTION_ITS_RESULT,
+ ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
+ ItsSession.EXTRA_CAMERA_ID, camera_id,
+ ItsSession.EXTRA_RESULTS, json_results)
+ if len(cmd) > 4095:
+ print "ITS command string might be too long! len:", len(cmd)
+ _run(cmd)
def _run(cmd):
"""Replacement for os.system, with hiding of stdout+stderr messages.
@@ -821,8 +838,20 @@
"""Run a suite of unit tests on this module.
"""
- # TODO: Add some unit tests.
- None
+ """
+ # TODO: this test currently needs connected device to pass
+ # Need to remove that dependency before enabling the test
+ def test_report_result(self):
+ device_id = get_device_id()
+ camera_id = "1"
+ result_key = ItsSession.RESULT_KEY
+ results = {"scene0":{result_key:"PASS"},
+ "scene1":{result_key:"PASS"},
+ "scene2":{result_key:"PASS"},
+ "scene3":{result_key:"PASS"},
+ "sceneNotExist":{result_key:"FAIL"}}
+ report_result(device_id, camera_id, results)
+ """
if __name__ == '__main__':
unittest.main()
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 58fe4ec..9766ab9 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -185,7 +185,8 @@
ar = match_ar_size[0] / float(match_ar_size[1])
out_sizes = [s for s in out_sizes if
abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE]
- out_sizes.sort(reverse=True)
+ out_sizes.sort(reverse=True, key=lambda s: s[0]) # 1st pass, sort by width
+ out_sizes.sort(reverse=True, key=lambda s: s[0]*s[1]) # sort by area
return out_sizes
def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 52780eb..678c35c 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -20,12 +20,20 @@
import sys
import textwrap
import its.device
+from its.device import ItsSession
def main():
"""Run all the automated tests, saving intermediate files, and producing
a summary/report of the results.
Script should be run from the top-level CameraITS directory.
+
+ Command line Arguments:
+ camera: the camera(s) to be tested. Use comma to separate multiple
+ camera Ids. Ex: "camera=0,1" or "camera=1"
+ scenes: the test scene(s) to be executed. Use comma to separate multiple
+ scenes. Ex: "scenes=scene0,scene1" or "scenes=0,1,sensor_fusion"
+ (sceneX can be abbreviated by X where X is a integer)
"""
SKIP_RET_CODE = 101
@@ -49,10 +57,7 @@
"sensor_fusion":[]
}
- # Get all the scene0 and scene1 tests, which can be run using the same
- # physical setup.
- scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
- "sensor_fusion"]
+ all_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
scene_req = {
"scene0" : None,
@@ -75,12 +80,48 @@
scene_extra_args = {
"scene5" : ["doAF=False"]
}
- tests = []
- for d in scenes:
- tests += [(d,s[:-3],os.path.join("tests", d, s))
- for s in os.listdir(os.path.join("tests",d))
- if s[-3:] == ".py"]
- tests.sort()
+
+ camera_ids = []
+ scenes = []
+ for s in sys.argv[1:]:
+ if s[:7] == "camera=" and len(s) > 7:
+ camera_ids = s[7:].split(',')
+ elif s[:7] == "scenes=" and len(s) > 7:
+ scenes = s[7:].split(',')
+
+ # Run through all scenes if user does not supply one
+ if not scenes:
+ scenes = all_scenes
+ else:
+ # Validate user input scene names
+ valid_scenes = True
+ temp_scenes = []
+ for s in scenes:
+ if s in all_scenes:
+ temp_scenes.append(s)
+ else:
+ try:
+ # Try replace "X" to "sceneX"
+ scene_num = int(s)
+ scene_str = "scene" + s
+ if scene_str not in all_scenes:
+ valid_scenes = False
+ break
+ temp_scenes.append(scene_str)
+ except ValueError:
+ valid_scenes = False
+ break
+
+ if not valid_scenes:
+ print "Unknown scene specifiied:", s
+ assert(False)
+ scenes = temp_scenes
+
+ # Initialize test results
+ results = {}
+ result_key = ItsSession.RESULT_KEY
+ for s in all_scenes:
+ results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED}
# Make output directories to hold the generated files.
topdir = tempfile.mkdtemp()
@@ -90,11 +131,6 @@
device_id_arg = "device=" + device_id
print "Testing device " + device_id
- camera_ids = []
- for s in sys.argv[1:]:
- if s[:7] == "camera=" and len(s) > 7:
- camera_ids.append(s[7:])
-
# user doesn't specify camera id, run through all cameras
if not camera_ids:
camera_ids_path = os.path.join(topdir, "camera_ids.txt")
@@ -108,7 +144,7 @@
for line in f:
camera_ids.append(line.replace('\n', ''))
- print "Running ITS on the following cameras:", camera_ids
+ print "Running ITS on camera: %s, scene %s" % (camera_ids, scenes)
for camera_id in camera_ids:
# Loop capturing images until user confirm test scene is correct
@@ -119,17 +155,18 @@
for d in scenes:
os.mkdir(os.path.join(topdir, camera_id, d))
- print "Start running ITS on camera: ", camera_id
- # Run each test, capturing stdout and stderr.
- summary = "ITS test result summary for camera " + camera_id + "\n"
- numpass = 0
- numskip = 0
- numnotmandatedfail = 0
- numfail = 0
+ for scene in scenes:
+ tests = [(s[:-3],os.path.join("tests", scene, s))
+ for s in os.listdir(os.path.join("tests",scene))
+ if s[-3:] == ".py" and s[:4] == "test"]
+ tests.sort()
- prev_scene = ""
- for (scene,testname,testpath) in tests:
- if scene != prev_scene and scene_req[scene] != None:
+ summary = "Cam" + camera_id + " " + scene + "\n"
+ numpass = 0
+ numskip = 0
+ num_not_mandated_fail = 0
+ numfail = 0
+ if scene_req[scene] != None:
out_path = os.path.join(topdir, camera_id, scene+".jpg")
out_arg = "out=" + out_path
scene_arg = "scene=" + scene_req[scene]
@@ -140,64 +177,71 @@
extra_args
retcode = subprocess.call(cmd,cwd=topdir)
assert(retcode == 0)
- print "Start running tests for", scene
- prev_scene = scene
- cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
- sys.argv[1:] + [camera_id_arg]
- outdir = os.path.join(topdir,camera_id,scene)
- outpath = os.path.join(outdir,testname+"_stdout.txt")
- errpath = os.path.join(outdir,testname+"_stderr.txt")
- t0 = time.time()
- with open(outpath,"w") as fout, open(errpath,"w") as ferr:
- retcode = subprocess.call(cmd,stderr=ferr,stdout=fout,cwd=outdir)
- t1 = time.time()
+ print "Start running ITS on camera %s, %s" % (camera_id, scene)
- if retcode == 0:
- retstr = "PASS "
- numpass += 1
- elif retcode == SKIP_RET_CODE:
- retstr = "SKIP "
- numskip += 1
- elif retcode != 0 and testname in NOT_YET_MANDATED[scene]:
- retstr = "FAIL*"
- numnotmandatedfail += 1
+ # Run each test, capturing stdout and stderr.
+ for (testname,testpath) in tests:
+ cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
+ sys.argv[1:] + [camera_id_arg]
+ outdir = os.path.join(topdir,camera_id,scene)
+ outpath = os.path.join(outdir,testname+"_stdout.txt")
+ errpath = os.path.join(outdir,testname+"_stderr.txt")
+ t0 = time.time()
+ with open(outpath,"w") as fout, open(errpath,"w") as ferr:
+ retcode = subprocess.call(
+ cmd,stderr=ferr,stdout=fout,cwd=outdir)
+ t1 = time.time()
+
+ test_failed = False
+ if retcode == 0:
+ retstr = "PASS "
+ numpass += 1
+ elif retcode == SKIP_RET_CODE:
+ retstr = "SKIP "
+ numskip += 1
+ elif retcode != 0 and testname in NOT_YET_MANDATED[scene]:
+ retstr = "FAIL*"
+ num_not_mandated_fail += 1
+ else:
+ retstr = "FAIL "
+ numfail += 1
+ test_failed = True
+
+ msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
+ print msg
+ msg_short = "%s %s [%.1fs]" % (retstr, testname, t1-t0)
+ if test_failed:
+ summary += msg_short + "\n"
+
+ if numskip > 0:
+ skipstr = ", %d test%s skipped" % (
+ numskip, "s" if numskip > 1 else "")
else:
- retstr = "FAIL "
- numfail += 1
+ skipstr = ""
- msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
- print msg
- summary += msg + "\n"
- if retcode != 0 and retcode != SKIP_RET_CODE:
- # Dump the stderr if the test fails
- with open (errpath, "r") as error_file:
- errors = error_file.read()
- summary += errors + "\n"
+ test_result = "\n%d / %d tests passed (%.1f%%)%s" % (
+ numpass + num_not_mandated_fail, len(tests) - numskip,
+ 100.0 * float(numpass + num_not_mandated_fail) /
+ (len(tests) - numskip)
+ if len(tests) != numskip else 100.0,
+ skipstr)
+ print test_result
- if numskip > 0:
- skipstr = ", %d test%s skipped" % (numskip, "s" if numskip > 1 else "")
- else:
- skipstr = ""
+ if num_not_mandated_fail > 0:
+ msg = "(*) tests are not yet mandated"
+ print msg
- test_result = "\n%d / %d tests passed (%.1f%%)%s" % (
- numpass + numnotmandatedfail, len(tests) - numskip,
- 100.0 * float(numpass + numnotmandatedfail) / (len(tests) - numskip)
- if len(tests) != numskip else 100.0,
- skipstr)
- print test_result
- summary += test_result + "\n"
+ summary_path = os.path.join(topdir, camera_id, scene, "summary.txt")
+ with open(summary_path, "w") as f:
+ f.write(summary)
- if numnotmandatedfail > 0:
- msg = "(*) tests are not yet mandated"
- print msg
- summary += msg + "\n"
+ passed = numfail == 0
+ results[scene][result_key] = (ItsSession.RESULT_PASS if passed
+ else ItsSession.RESULT_FAIL)
+ results[scene][ItsSession.SUMMARY_KEY] = summary_path
- result = numfail == 0
print "Reporting ITS result to CtsVerifier"
- summary_path = os.path.join(topdir, camera_id, "summary.txt")
- with open(summary_path, "w") as f:
- f.write(summary)
- its.device.report_result(device_id, camera_id, result, summary_path)
+ its.device.report_result(device_id, camera_id, results)
print "ITS tests finished. Please go back to CtsVerifier and proceed"
diff --git a/apps/CtsVerifier/res/layout/its_main.xml b/apps/CtsVerifier/res/layout/its_main.xml
index 2f5eade..26f15bb 100644
--- a/apps/CtsVerifier/res/layout/its_main.xml
+++ b/apps/CtsVerifier/res/layout/its_main.xml
@@ -21,4 +21,14 @@
<include layout="@layout/pass_fail_buttons" />
+ <TextView
+ android:id="@+id/its_progress"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:padding="2dp"
+ android:scrollbars = "vertical"
+ android:text="@string/its_test_progress"
+ android:textSize="16sp" />
+
</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 4ef177e..2406b84 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -991,6 +991,10 @@
</string>
<string name="its_test_passed">All Camera ITS tests passed. Pass button enabled!</string>
<string name="its_test_failed">Some Camera ITS tests failed.</string>
+ <string name="its_version_mismatch">
+ CtsVerifier and ITS script version mismatch. Please update CtsVerifier and ITS script.
+ </string>
+ <string name="its_test_progress">ITS test progress will be shown here.</string>
<!-- Strings for the Camera Flashlight test activity -->
<string name="camera_flashlight_test">Camera Flashlight</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index dc81e19..f9f5823 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -41,6 +41,9 @@
import com.androidplot.xy.XYSeries;
import com.androidplot.xy.*;
+import com.android.compatibility.common.util.CddTest;
+
+@CddTest(requirement="7.8.3")
public class HifiUltrasoundSpeakerTestActivity extends PassFailButtons.Activity {
public enum Status {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 49525b7..1a3534d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -404,6 +404,7 @@
// (called on different threads) will need to send data back to the host script.
public Socket mOpenSocket = null;
+ private Thread mThread = null;
public SocketWriteRunnable(Socket openSocket) {
mOpenSocket = openSocket;
@@ -421,6 +422,7 @@
ByteBuffer b = mSocketWriteQueue.take();
synchronized(mSocketWriteDrainLock) {
if (mOpenSocket == null) {
+ Logt.e(TAG, "No open socket connection!");
continue;
}
if (b.hasArray()) {
@@ -442,14 +444,26 @@
}
} catch (IOException e) {
Logt.e(TAG, "Error writing to socket", e);
+ mOpenSocket = null;
break;
} catch (java.lang.InterruptedException e) {
Logt.e(TAG, "Error writing to socket (interrupted)", e);
+ mOpenSocket = null;
break;
}
}
Logt.i(TAG, "Socket writer thread terminated");
}
+
+ public synchronized void checkAndStartThread() {
+ if (mThread == null || mThread.getState() == Thread.State.TERMINATED) {
+ mThread = new Thread(this);
+ }
+ if (mThread.getState() == Thread.State.NEW) {
+ mThread.start();
+ }
+ }
+
}
class SocketRunnable implements Runnable {
@@ -475,7 +489,6 @@
// Create a new thread to handle writes to this socket.
mSocketWriteRunnable = new SocketWriteRunnable(null);
- (new Thread(mSocketWriteRunnable)).start();
while (!mThreadExitFlag) {
// Receive the socket-open request from the host.
@@ -489,6 +502,7 @@
mSocketWriteQueue.clear();
mInflightImageSizes.clear();
mSocketWriteRunnable.setOpenSocket(mOpenSocket);
+ mSocketWriteRunnable.checkAndStartThread();
Logt.i(TAG, "Socket connected");
} catch (IOException e) {
Logt.e(TAG, "Socket open error: ", e);
@@ -1161,12 +1175,13 @@
} else {
// No surface(s) specified at all.
// Default: a single output surface which is full-res YUV.
- Size sizes[] = ItsUtils.getYuvOutputSizes(mCameraCharacteristics);
+ Size maxYuvSize = ItsUtils.getMaxOutputSize(
+ mCameraCharacteristics, ImageFormat.YUV_420_888);
numSurfaces = backgroundRequest ? 2 : 1;
outputSizes = new Size[numSurfaces];
outputFormats = new int[numSurfaces];
- outputSizes[0] = sizes[0];
+ outputSizes[0] = maxYuvSize;
outputFormats[0] = ImageFormat.YUV_420_888;
if (backgroundRequest) {
outputSizes[1] = new Size(640, 480);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 0c39a9e..a8affcd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -25,15 +25,21 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.WindowManager;
+import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
-import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileNotFoundException;
@@ -44,6 +50,8 @@
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
+import org.json.JSONArray;
+import org.json.JSONObject;
/**
* Test for Camera features that require that the camera be aimed at a specific test scene.
@@ -53,48 +61,216 @@
public class ItsTestActivity extends PassFailButtons.Activity {
private static final String TAG = "ItsTestActivity";
private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
- private static final String EXTRA_SUCCESS = "camera.its.extra.SUCCESS";
- private static final String EXTRA_SUMMARY = "camera.its.extra.SUMMARY";
+ private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
+ private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
+ private static final String CURRENT_VERSION = "1.0";
private static final String ACTION_ITS_RESULT =
"com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
- class SuccessReceiver extends BroadcastReceiver {
+ private static final String RESULT_PASS = "PASS";
+ private static final String RESULT_FAIL = "FAIL";
+ private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
+ private static final Set<String> RESULT_VALUES = new HashSet<String>(
+ Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
+ private static final int MAX_SUMMARY_LEN = 200;
+
+ private final ResultReceiver mResultsReceiver = new ResultReceiver();
+
+ // Initialized in onCreate
+ ArrayList<String> mNonLegacyCameraIds = null;
+
+ // TODO: cache the following in saved bundle
+ private Set<ResultKey> mAllScenes = null;
+ // (camera, scene) -> (pass, fail)
+ private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
+ // map camera id to ITS summary report path
+ private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
+
+ final class ResultKey {
+ public final String cameraId;
+ public final String sceneId;
+
+ public ResultKey(String cameraId, String sceneId) {
+ this.cameraId = cameraId;
+ this.sceneId = sceneId;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == null) return false;
+ if (this == o) return true;
+ if (o instanceof ResultKey) {
+ final ResultKey other = (ResultKey) o;
+ return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = cameraId.hashCode();
+ h = ((h << 5) - h) ^ sceneId.hashCode();
+ return h;
+ }
+ }
+
+ private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
+ @Override
+ public int compare(ResultKey k1, ResultKey k2) {
+ if (k1.cameraId.equals(k2.cameraId))
+ return k1.sceneId.compareTo(k2.sceneId);
+ return k1.cameraId.compareTo(k2.cameraId);
+ }
+ };
+
+ class ResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received result for Camera ITS tests");
if (ACTION_ITS_RESULT.equals(intent.getAction())) {
+ String version = intent.getStringExtra(EXTRA_VERSION);
+ if (version == null || !version.equals(CURRENT_VERSION)) {
+ Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
+ ", got " + ((version == null) ? "null" : version));
+ ItsTestActivity.this.showToast(R.string.its_version_mismatch);
+ return;
+ }
+
String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
- String result = intent.getStringExtra(EXTRA_SUCCESS);
- String summaryPath = intent.getStringExtra(EXTRA_SUMMARY);
+ String results = intent.getStringExtra(EXTRA_RESULTS);
+ if (cameraId == null || results == null) {
+ Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
+ ", results = " + ((results == null) ? "null" : results));
+ return;
+ }
+
if (!mNonLegacyCameraIds.contains(cameraId)) {
Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
return;
}
- Log.i(TAG, "ITS summary path is: " + summaryPath);
- mSummaryMap.put(cameraId, summaryPath);
- // Create summary report
- if (mSummaryMap.keySet().containsAll(mNonLegacyCameraIds)) {
+ try {
+ /* Sample JSON results string
+ {
+ "scene0":{
+ "result":"PASS",
+ "summary":"/sdcard/cam0_scene0.txt"
+ },
+ "scene1":{
+ "result":"NOT_EXECUTED"
+ },
+ "scene2":{
+ "result":"FAIL",
+ "summary":"/sdcard/cam0_scene2.txt"
+ }
+ }
+ */
+ JSONObject jsonResults = new JSONObject(results);
+ Set<String> scenes = new HashSet<>();
+ Iterator<String> keys = jsonResults.keys();
+ while (keys.hasNext()) {
+ scenes.add(keys.next());
+ }
+ boolean newScenes = false;
+ if (mAllScenes == null) {
+ mAllScenes = new TreeSet<>(mComparator);
+ newScenes = true;
+ } else { // See if scene lists changed
+ for (String scene : scenes) {
+ if (!mAllScenes.contains(new ResultKey(cameraId, scene))) {
+ // Scene list changed. Cleanup previous test results
+ newScenes = true;
+ break;
+ }
+ }
+ for (ResultKey k : mAllScenes) {
+ if (!scenes.contains(k.sceneId)) {
+ newScenes = true;
+ break;
+ }
+ }
+ }
+ if (newScenes) {
+ mExecutedScenes.clear();
+ mAllScenes.clear();
+ for (String scene : scenes) {
+ for (String c : mNonLegacyCameraIds) {
+ mAllScenes.add(new ResultKey(c, scene));
+ }
+ }
+ }
+
+ // Update test execution results
+ for (String scene : scenes) {
+ JSONObject sceneResult = jsonResults.getJSONObject(scene);
+ String result = sceneResult.getString("result");
+ if (result == null) {
+ Log.e(TAG, "Result for " + scene + " is null");
+ return;
+ }
+ Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
+ if (!RESULT_VALUES.contains(result)) {
+ Log.e(TAG, "Unknown result for " + scene + ": " + result);
+ return;
+ }
+ ResultKey key = new ResultKey(cameraId, scene);
+ if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
+ boolean pass = result.equals(RESULT_PASS);
+ mExecutedScenes.put(key, pass);
+ String summary = sceneResult.optString("summary");
+ if (!summary.equals("")) {
+ mSummaryMap.put(key, summary);
+ }
+ } // do nothing for NOT_EXECUTED scenes
+ }
+ } catch (org.json.JSONException e) {
+ Log.e(TAG, "Error reading json result string:" + results , e);
+ return;
+ }
+
+ // Set summary if all scenes reported
+ if (mSummaryMap.keySet().containsAll(mAllScenes)) {
StringBuilder summary = new StringBuilder();
- for (String id : mNonLegacyCameraIds) {
- String path = mSummaryMap.get(id);
+ for (String path : mSummaryMap.values()) {
appendFileContentToSummary(summary, path);
}
+ if (summary.length() > MAX_SUMMARY_LEN) {
+ Log.w(TAG, "ITS summary report too long: len: " + summary.length());
+ }
ItsTestActivity.this.getReportLog().setSummary(
summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
}
- boolean pass = result.equals("True");
- if(pass) {
- Log.i(TAG, "Received Camera " + cameraId + " ITS SUCCESS from host.");
- mITSPassedCameraIds.add(cameraId);
- if (mNonLegacyCameraIds != null && mNonLegacyCameraIds.size() != 0 &&
- mITSPassedCameraIds.containsAll(mNonLegacyCameraIds)) {
- ItsTestActivity.this.showToast(R.string.its_test_passed);
- ItsTestActivity.this.getPassButton().setEnabled(true);
+
+ // Display current progress
+ StringBuilder progress = new StringBuilder();
+ for (ResultKey k : mAllScenes) {
+ String status = RESULT_NOT_EXECUTED;
+ if (mExecutedScenes.containsKey(k)) {
+ status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
}
+ progress.append(String.format("Cam %s, %s: %s\n",
+ k.cameraId, k.sceneId, status));
+ }
+ TextView progressView = (TextView) findViewById(R.id.its_progress);
+ progressView.setMovementMethod(new ScrollingMovementMethod());
+ progressView.setText(progress.toString());
+
+
+ // Enable pass button if all scenes pass
+ boolean allScenesPassed = true;
+ for (ResultKey k : mAllScenes) {
+ Boolean pass = mExecutedScenes.get(k);
+ if (pass == null || pass == false) {
+ allScenesPassed = false;
+ break;
+ }
+ }
+ if (allScenesPassed) {
+ // Enable pass button
+ ItsTestActivity.this.showToast(R.string.its_test_passed);
+ ItsTestActivity.this.getPassButton().setEnabled(true);
} else {
- Log.i(TAG, "Received Camera " + cameraId + " ITS FAILURE from host.");
- ItsTestActivity.this.showToast(R.string.its_test_failed);
+ ItsTestActivity.this.getPassButton().setEnabled(false);
}
}
}
@@ -127,12 +303,6 @@
}
}
- private final SuccessReceiver mSuccessReceiver = new SuccessReceiver();
- private final HashSet<String> mITSPassedCameraIds = new HashSet<>();
- // map camera id to ITS summary report path
- private final HashMap<String, String> mSummaryMap = new HashMap<>();
- ArrayList<String> mNonLegacyCameraIds = null;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -168,6 +338,7 @@
"Received error from camera service while checking device capabilities: "
+ e, Toast.LENGTH_SHORT).show();
}
+
getPassButton().setEnabled(false);
}
@@ -180,7 +351,7 @@
} else {
Log.d(TAG, "register ITS result receiver");
IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
- registerReceiver(mSuccessReceiver, filter);
+ registerReceiver(mResultsReceiver, filter);
}
}
@@ -188,7 +359,7 @@
protected void onPause() {
super.onPause();
Log.d(TAG, "unregister ITS result receiver");
- unregisterReceiver(mSuccessReceiver);
+ unregisterReceiver(mResultsReceiver);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index 8763223..b0eaf35 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -153,10 +153,13 @@
}
Size maxSize = sizes[0];
+ int maxArea = maxSize.getWidth() * maxSize.getHeight();
for (int i = 1; i < sizes.length; i++) {
- if (sizes[i].getWidth() * sizes[i].getHeight() >
- maxSize.getWidth() * maxSize.getHeight()) {
+ int area = sizes[i].getWidth() * sizes[i].getHeight();
+ if (area > maxArea ||
+ (area == maxArea && sizes[i].getWidth() > maxSize.getWidth())) {
maxSize = sizes[i];
+ maxArea = area;
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 19db37a..6c8cab4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -113,6 +113,13 @@
splitModules(shards);
}
}, "split", "m(?:odules)?", "(\\d+)");
+ trie.put(new Runnable() {
+ @Override
+ public void run() {
+ printLine(String.format("Android %s %s (%s)", SuiteInfo.FULLNAME,
+ SuiteInfo.VERSION, SuiteInfo.BUILD_NUMBER));
+ }
+ }, "version"); // override tradefed 'version' command to print test suite name and version
// find existing help for 'LIST_PATTERN' commands, and append these commands help
String listHelp = commandHelp.get(LIST_PATTERN);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index ae3f271..3e99186 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -83,12 +83,14 @@
private static final String MODULE_ARG_OPTION = "module-arg";
private static final String TEST_ARG_OPTION = "test-arg";
public static final String RETRY_OPTION = "retry";
+ public static final String RETRY_TYPE_OPTION = "retry-type";
private static final String ABI_OPTION = "abi";
private static final String SHARD_OPTION = "shards";
public static final String SKIP_DEVICE_INFO_OPTION = "skip-device-info";
public static final String SKIP_PRECONDITIONS_OPTION = "skip-preconditions";
public static final String PRIMARY_ABI_RUN = "primary-abi-only";
public static final String DEVICE_TOKEN_OPTION = "device-token";
+ public static final String LOGCAT_ON_FAILURE_SIZE_OPTION = "logcat-on-failure-size";
private static final String URL = "dynamic-config-url";
/* API Key for compatibility test project, used for dynamic configuration */
@@ -134,12 +136,22 @@
importance = Importance.ALWAYS)
private List<String> mTestArgs = new ArrayList<>();
+ public enum RetryType {
+ FAILED, NOT_EXECUTED;
+ }
+
@Option(name = RETRY_OPTION,
shortName = 'r',
- description = "retry a previous session.",
+ description = "retry a previous session's failed and not executed tests.",
importance = Importance.IF_UNSET)
private Integer mRetrySessionId = null;
+ @Option(name = RETRY_TYPE_OPTION,
+ description = "used with " + RETRY_OPTION + ", retry tests of a certain status. "
+ + "Possible values include \"failed\" and \"not_executed\".",
+ importance = Importance.IF_UNSET)
+ private RetryType mRetryType = null;
+
@Option(name = ABI_OPTION,
shortName = 'a',
description = "the abi to test.",
@@ -185,6 +197,11 @@
description = "Take a logcat snapshot on every test failure.")
private boolean mLogcatOnFailure = false;
+ @Option(name = LOGCAT_ON_FAILURE_SIZE_OPTION,
+ description = "The max number of logcat data in bytes to capture when "
+ + "--logcat-on-failure is on. Should be an amount that can comfortably fit in memory.")
+ private int mMaxLogcatBytes = 500 * 1024; // 500K
+
@Option(name = "screenshot-on-failure",
description = "Take a screenshot on every test failure.")
private boolean mScreenshotOnFailure = false;
@@ -315,7 +332,7 @@
List<IModuleDef> modules = mModuleRepo.getModules(getDevice().getSerialNumber());
listener = new FailureListener(listener, getDevice(), mBugReportOnFailure,
- mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure);
+ mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure, mMaxLogcatBytes);
int moduleCount = modules.size();
CLog.logAndDisplay(LogLevel.INFO, "Starting %d module%s on %s", moduleCount,
(moduleCount > 1) ? "s" : "", mDevice.getSerialNumber());
@@ -342,13 +359,23 @@
}
// Set values and run preconditions
+ boolean isPrepared = true; // whether the device has been successfully prepared
for (int i = 0; i < moduleCount; i++) {
IModuleDef module = modules.get(i);
module.setBuild(mBuildHelper.getBuildInfo());
module.setDevice(mDevice);
module.setPreparerWhitelist(mPreparerWhitelist);
- module.prepare(mSkipPreconditions);
+ isPrepared &= (module.prepare(mSkipPreconditions));
}
+ mModuleRepo.setPrepared(isPrepared);
+
+ if (!mModuleRepo.isPrepared()) {
+ CLog.logAndDisplay(LogLevel.ERROR,
+ "Incorrect preparation detected, exiting test run from %s",
+ mDevice.getSerialNumber());
+ return;
+ }
+
// Run the tests
for (int i = 0; i < moduleCount; i++) {
IModuleDef module = modules.get(i);
@@ -573,26 +600,18 @@
throw new RuntimeException(e);
}
}
- // Append each test that failed or was not executed to the filters
- for (IModuleResult module : result.getModules()) {
- if (module.isPassed()) {
- // Whole module passed, exclude entire module
- TestFilter filter = new TestFilter(module.getAbi(), module.getName(), null);
- mExcludeFilters.add(filter.toString());
- } else {
- for (ICaseResult testResultList : module.getResults()) {
- for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
- // Test passed, exclude it for retry
- TestFilter filter = new TestFilter(
- module.getAbi(), module.getName(), testResult.getFullName());
- mExcludeFilters.add(filter.toString());
- }
- }
- }
+
+ ITestPlan retryPlan = new TestPlan();
+ retryPlan.excludePassed(result); // always exclude passed tests on retry
+ if (RetryType.FAILED.equals(mRetryType)) {
+ retryPlan.includeFailed(result); // retry only failed tests
+ } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
+ retryPlan.excludeFailed(result); // retry only not executed tests
}
+ mIncludeFilters.addAll(retryPlan.getIncludeFilters());
+ mExcludeFilters.addAll(retryPlan.getExcludeFilters());
}
if (mModuleName != null) {
- mIncludeFilters.clear();
try {
List<String> modules = ModuleRepo.getModuleNamesMatching(
mBuildHelper.getTestsDir(), mModuleName);
@@ -605,19 +624,9 @@
mModuleName, ArrayUtil.join("\n", modules)));
} else {
String module = modules.get(0);
+ cleanFilters(mIncludeFilters, module);
+ cleanFilters(mExcludeFilters, module);
mIncludeFilters.add(new TestFilter(mAbiName, module, mTestName).toString());
- // We will run this module with previous exclusions,
- // unless they exclude the whole module.
- List<String> excludeFilters = new ArrayList<>();
- for (String excludeFilter : mExcludeFilters) {
- TestFilter filter = TestFilter.createFrom(excludeFilter);
- String name = filter.getName();
- // Add the filter if it applies to this module
- if (module.equals(name)) {
- excludeFilters.add(excludeFilter);
- }
- }
- mExcludeFilters = excludeFilters;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
@@ -633,6 +642,17 @@
}
}
+ /* Helper method designed to remove filters in a list not applicable to the given module */
+ private static void cleanFilters(List<String> filters, String module) {
+ List<String> cleanedFilters = new ArrayList<String>();
+ for (String filter : filters) {
+ if (module.equals(TestFilter.createFrom(filter).getName())) {
+ cleanedFilters.add(filter); // Module name matches, filter passes
+ }
+ }
+ filters = cleanedFilters;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
index 5d2cd38..cd0e54f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
@@ -27,23 +27,38 @@
public class FailureListener extends ResultForwarder {
- private static final int MAX_LOGCAT_BYTES = 500 * 1024; // 500K
+ private static final int DEFAULT_MAX_LOGCAT_BYTES = 500 * 1024; // 500K
+ /* Arbitrary upper limit for mMaxLogcatBytes, per b/30720850 */
+ public static final int LOGCAT_BYTE_LIMIT = 20 * 1024 * 1024; // 20 MB
private ITestDevice mDevice;
private boolean mBugReportOnFailure;
private boolean mLogcatOnFailure;
private boolean mScreenshotOnFailure;
private boolean mRebootOnFailure;
+ private int mMaxLogcatBytes;
public FailureListener(ITestInvocationListener listener, ITestDevice device,
boolean bugReportOnFailure, boolean logcatOnFailure, boolean screenshotOnFailure,
- boolean rebootOnFailure) {
+ boolean rebootOnFailure, int maxLogcatBytes) {
super(listener);
mDevice = device;
mBugReportOnFailure = bugReportOnFailure;
mLogcatOnFailure = logcatOnFailure;
mScreenshotOnFailure = screenshotOnFailure;
mRebootOnFailure = rebootOnFailure;
+ if (maxLogcatBytes < 0 ) {
+ CLog.w("FailureListener could not set %s to '%d', using default value %d",
+ CompatibilityTest.LOGCAT_ON_FAILURE_SIZE_OPTION, maxLogcatBytes,
+ DEFAULT_MAX_LOGCAT_BYTES);
+ mMaxLogcatBytes = DEFAULT_MAX_LOGCAT_BYTES;
+ } else if (maxLogcatBytes > LOGCAT_BYTE_LIMIT) {
+ CLog.w("Value %d for %s exceeds limit %d, using limit value", maxLogcatBytes,
+ CompatibilityTest.LOGCAT_ON_FAILURE_SIZE_OPTION, LOGCAT_BYTE_LIMIT);
+ mMaxLogcatBytes = LOGCAT_BYTE_LIMIT;
+ } else {
+ mMaxLogcatBytes = maxLogcatBytes;
+ }
}
/**
@@ -62,7 +77,7 @@
if (mLogcatOnFailure) {
// sleep 2s to ensure test failure stack trace makes it into logcat capture
RunUtil.getDefault().sleep(2 * 1000);
- InputStreamSource logSource = mDevice.getLogcat(MAX_LOGCAT_BYTES);
+ InputStreamSource logSource = mDevice.getLogcat(mMaxLogcatBytes);
super.testLog(String.format("%s-logcat", test.toString()), LogDataType.LOGCAT,
logSource);
logSource.cancel();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index 45ef438..14427ca 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -66,7 +66,8 @@
/**
* Runs the module's precondition checks and setup tasks.
+ * @return whether preparation succeeded.
*/
- void prepare(boolean skipPrep) throws DeviceNotAvailableException;
+ boolean prepare(boolean skipPrep) throws DeviceNotAvailableException;
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
index cf52f35..7cbf7a3 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
@@ -29,6 +29,16 @@
public interface IModuleRepo {
/**
+ * @return true after each shard has prepared successfully.
+ */
+ boolean isPrepared();
+
+ /**
+ * Indicates to the repo whether a shard is prepared to run.
+ */
+ void setPrepared(boolean isPrepared);
+
+ /**
* @return true if this repository has been initialized.
*/
boolean isInitialized();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
new file mode 100644
index 0000000..16b36ea
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.tradefed.testtype;
+
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Set;
+
+
+/**
+ * Interface for generating, parsing, and retrieving test plan data.
+ */
+public interface ITestPlan extends ITestFilterReceiver {
+
+ /**
+ * Parse the test plan data from a stream of XML, populate collections of filters internally.
+ * @param xmlInputStream the {@link InputStream} containing test plan XML
+ */
+ public void parse(InputStream xmlInputStream) throws ParseException;
+
+ /**
+ * Add include filters for {@link ITestResult}s that have passed.
+ * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+ */
+ public void includePassed(IInvocationResult result);
+
+ /**
+ * Add include filters for {@link ITestResult}s that have failed.
+ * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+ */
+ public void includeFailed(IInvocationResult result);
+
+ /**
+ * Add exclude filters for {@link ITestResult}s that have passed.
+ * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+ */
+ public void excludePassed(IInvocationResult result);
+
+ /**
+ * Add exclude filters for {@link ITestResult}s that have failed.
+ * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+ */
+ public void excludeFailed(IInvocationResult result);
+
+ /**
+ * Retrieve the set of include filters previously added or parsed from XML.
+ * @return a set of include filter strings
+ */
+ public Set<String> getIncludeFilters();
+
+ /**
+ * Retrieve the set of exclude filters previously added or parsed from XML.
+ * @return a set of exclude filter strings
+ */
+ public Set<String> getExcludeFilters();
+
+ /**
+ * Serialize the existing filters into a stream of XML, and write to an output stream.
+ * @param xmlOutputStream the {@link OutputStream} to receive test plan XML
+ */
+ public void serialize(OutputStream xmlOutputStream) throws IOException;
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index 0844a6e..2c3f96b 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -268,7 +268,7 @@
* {@inheritDoc}
*/
@Override
- public void prepare(boolean skipPrep) throws DeviceNotAvailableException {
+ public boolean prepare(boolean skipPrep) throws DeviceNotAvailableException {
for (ITargetPreparer preparer : mPreconditions) {
CLog.d("Preparer: %s", preparer.getClass().getSimpleName());
if (preparer instanceof IAbiReceiver) {
@@ -282,13 +282,16 @@
// This should only happen for flashing new build
CLog.e("Unexpected BuildError from precondition: %s",
preparer.getClass().getCanonicalName());
+ return false;
} catch (TargetSetupError e) {
// log precondition class then rethrow & let caller handle
CLog.e("TargetSetupError in precondition: %s",
preparer.getClass().getCanonicalName());
- throw new RuntimeException(e);
+ e.printStackTrace();
+ return false;
}
}
+ return true;
}
private void setOption(Object target, String option, String value) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index de509c8..c4ff1b0 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -47,6 +47,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
@@ -78,6 +79,10 @@
private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance();
private volatile boolean mInitialized = false;
+ // Whether the modules in this repo are ready to run on their assigned devices.
+ // True until explicitly set false in setPrepared().
+ private volatile boolean mPrepared = true;
+ private CountDownLatch mPreparedLatch;
// Holds all the small tests waiting to be run.
private List<IModuleDef> mSmallModules = new ArrayList<>();
@@ -197,6 +202,28 @@
* {@inheritDoc}
*/
@Override
+ public boolean isPrepared() {
+ try {
+ mPreparedLatch.await();
+ } catch (InterruptedException e) {
+ return false;
+ }
+ return mPrepared;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPrepared(boolean isPrepared) {
+ mPrepared &= isPrepared;
+ mPreparedLatch.countDown();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public boolean isInitialized() {
return mInitialized;
}
@@ -214,6 +241,7 @@
includeFilters, excludeFilters);
mInitialized = true;
mShards = shards;
+ mPreparedLatch = new CountDownLatch(shards);
for (String line : deviceTokens) {
String[] parts = line.split(":");
if (parts.length == 2) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
new file mode 100644
index 0000000..46dbe8d
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016 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.tradefed.testtype;
+
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+
+import com.android.compatibility.common.util.TestFilter;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.tradefed.util.xml.AbstractXmlParser;
+
+import org.kxml2.io.KXmlSerializer;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Container, parser, and generator of TestPlan info.
+ */
+public class TestPlan extends AbstractXmlParser implements ITestPlan {
+
+ private final Set<String> mIncludes;
+ private final Set<String> mExcludes;
+
+ private static final String ENCODING = "UTF-8";
+ private static final String NS = null; // namespace used for XML serializer
+ private static final String VERSION_ATTR = "version";
+ private static final String PLAN_VERSION = "2.0";
+
+
+ private static final String PLAN_TAG = "TestPlan";
+ private static final String ENTRY_TAG = "Entry";
+ private static final String EXCLUDE_ATTR = "exclude";
+ private static final String INCLUDE_ATTR = "include";
+ private static final String ABI_ATTR = "abi";
+ private static final String NAME_ATTR = "name";
+
+ public TestPlan() {
+ mIncludes = new HashSet<String>();
+ mExcludes = new HashSet<String>();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void includePassed(IInvocationResult result) {
+ for (IModuleResult module : result.getModules()) {
+ if (module.isPassed()) {
+ // Whole module passed, exclude
+ TestFilter filter =
+ new TestFilter(module.getAbi(), module.getName(), null /*test*/);
+ mIncludes.add(filter.toString());
+ } else {
+ for (ICaseResult testResultList : module.getResults()) {
+ for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+ // Test passed, exclude
+ TestFilter filter = new TestFilter(
+ module.getAbi(), module.getName(), testResult.getFullName());
+ mIncludes.add(filter.toString());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void includeFailed(IInvocationResult result) {
+ for (IModuleResult moduleResult : result.getModules()) {
+ for (ICaseResult caseResult : moduleResult.getResults()) {
+ for (ITestResult testResult : caseResult.getResults(TestStatus.FAIL)) {
+ // Test failed, include for retry
+ TestFilter filter = new TestFilter(moduleResult.getAbi(),
+ moduleResult.getName(), testResult.getFullName());
+ mIncludes.add(filter.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void excludePassed(IInvocationResult result) {
+ for (IModuleResult module : result.getModules()) {
+ if (module.isPassed()) {
+ // Whole module passed, exclude
+ TestFilter filter =
+ new TestFilter(module.getAbi(), module.getName(), null /*test*/);
+ mExcludes.add(filter.toString());
+ } else {
+ for (ICaseResult testResultList : module.getResults()) {
+ for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+ // Test passed, exclude
+ TestFilter filter = new TestFilter(
+ module.getAbi(), module.getName(), testResult.getFullName());
+ mExcludes.add(filter.toString());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void excludeFailed(IInvocationResult result) {
+ for (IModuleResult moduleResult : result.getModules()) {
+ for (ICaseResult caseResult : moduleResult.getResults()) {
+ for (ITestResult testResult : caseResult.getResults(TestStatus.FAIL)) {
+ // Test failed, include for retry
+ TestFilter filter = new TestFilter(moduleResult.getAbi(),
+ moduleResult.getName(), testResult.getFullName());
+ mExcludes.add(filter.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addIncludeFilter(String filter) {
+ mIncludes.add(filter);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addAllIncludeFilters(Set<String> filters) {
+ mIncludes.addAll(filters);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addExcludeFilter(String filter) {
+ mExcludes.add(filter);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addAllExcludeFilters(Set<String> filters) {
+ mExcludes.addAll(filters);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getIncludeFilters() {
+ return new HashSet<String>(mIncludes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getExcludeFilters() {
+ return new HashSet<String>(mExcludes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void serialize(OutputStream stream) throws IOException {
+ KXmlSerializer serializer = new KXmlSerializer();
+ serializer.setOutput(stream, ENCODING);
+ serializer.startDocument(ENCODING, false);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startTag(NS, PLAN_TAG);
+ serializer.attribute(NS, VERSION_ATTR, PLAN_VERSION);
+
+ ArrayList<String> sortedIncludes = new ArrayList<String>(mIncludes);
+ ArrayList<String> sortedExcludes = new ArrayList<String>(mExcludes);
+ Collections.sort(sortedIncludes);
+ Collections.sort(sortedExcludes);
+ for (String include : sortedIncludes) {
+ serializer.startTag(NS, ENTRY_TAG);
+ serializer.attribute(NS, INCLUDE_ATTR, include);
+ serializer.endTag(NS, ENTRY_TAG);
+ }
+ for (String exclude : sortedExcludes) {
+ serializer.startTag(NS, ENTRY_TAG);
+ serializer.attribute(NS, EXCLUDE_ATTR, exclude);
+ serializer.endTag(NS, ENTRY_TAG);
+ }
+
+ serializer.endTag(NS, PLAN_TAG);
+ serializer.endDocument();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected DefaultHandler createXmlHandler() {
+ return new EntryHandler();
+ }
+
+ /**
+ * SAX callback object. Handles parsing data from the xml tags.
+ */
+ private class EntryHandler extends DefaultHandler {
+
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes)
+ throws SAXException {
+ if (ENTRY_TAG.equals(localName)) {
+ String includeString = attributes.getValue(INCLUDE_ATTR);
+ String excludeString = attributes.getValue(EXCLUDE_ATTR);
+ if (includeString != null && excludeString != null) {
+ throw new IllegalArgumentException(
+ "Cannot specify include and exclude filter in the same element");
+ }
+ String abiString = attributes.getValue(ABI_ATTR);
+ String nameString = attributes.getValue(NAME_ATTR);
+
+ if (excludeString == null) {
+ parseFilter(abiString, nameString, includeString, mIncludes);
+ } else {
+ parseFilter(abiString, nameString, excludeString, mExcludes);
+ }
+ }
+ }
+
+ private void parseFilter(String abi, String name, String filter, Set<String> filterSet) {
+ if (name == null) {
+ // ignore name and abi attributes, 'filter' should contain all necessary parts
+ filterSet.add(filter);
+ } else {
+ // 'filter' is name of test. Build TestFilter and convert back to string
+ filterSet.add(new TestFilter(abi, name, filter).toString());
+ }
+ }
+ }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index dfe67c1..cbb2020 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -24,6 +24,7 @@
import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
+import com.android.compatibility.common.tradefed.testtype.TestPlanTest;
import com.android.compatibility.common.tradefed.util.OptionHelperTest;
import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
@@ -51,6 +52,7 @@
addTestSuite(ModuleRepoTest.class);
addTestSuite(PropertyCheckTest.class);
addTestSuite(SettingsPreparerTest.class);
+ addTestSuite(TestPlanTest.class);
}
public static Test suite() {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index 24c7e3d..3b8e4cb 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -262,6 +262,26 @@
assertArrayEquals(EXPECTED_MODULE_IDS, mRepo.getModuleIds());
}
+ public void testIsPrepared() {
+ mRepo.initialize(3, mTestsDir, ABIS, DEVICE_TOKENS, TEST_ARGS, MODULE_ARGS, INCLUDES,
+ EXCLUDES, mBuild);
+ assertTrue("Should be initialized", mRepo.isInitialized());
+ mRepo.setPrepared(true);
+ mRepo.setPrepared(true);
+ mRepo.setPrepared(true); // each shard should call setPrepared() once
+ assertTrue(mRepo.isPrepared());
+ }
+
+ public void testIsNotPrepared() {
+ mRepo.initialize(3, mTestsDir, ABIS, DEVICE_TOKENS, TEST_ARGS, MODULE_ARGS, INCLUDES,
+ EXCLUDES, mBuild);
+ assertTrue("Should be initialized", mRepo.isInitialized());
+ mRepo.setPrepared(true);
+ mRepo.setPrepared(false); // mRepo should return false for setPrepared() after third call
+ mRepo.setPrepared(true);
+ assertFalse(mRepo.isPrepared());
+ }
+
private void assertArrayEquals(Object[] expected, Object[] actual) {
assertEquals(Arrays.asList(expected), Arrays.asList(actual));
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java
new file mode 100644
index 0000000..dd09635
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 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.tradefed.testtype;
+
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TestPlanTest extends TestCase {
+
+ private static final String ABI = "armeabi-v7a";
+ private static final String MODULE_A = "ModuleA";
+ private static final String MODULE_B = "ModuleB";
+ private static final String TEST_1 = "android.test.Foo#test1";
+ private static final String TEST_2 = "android.test.Foo#test2";
+ private static final String TEST_3 = "android.test.Foo#test3";
+
+ private static final String XML_BASE =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<TestPlan version=\"2.0\">\n" +
+ "%s\n" +
+ "</TestPlan>";
+ private static final String XML_ENTRY = " <Entry %s/>\n";
+ private static final String XML_ATTR = "%s=\"%s\"";
+
+ public void testSerialization() throws Exception {
+ ITestPlan plan = new TestPlan();
+ plan.addIncludeFilter(new TestFilter(ABI, MODULE_A, TEST_1).toString());
+ Set<String> includeFilterSet = new HashSet<String>();
+ includeFilterSet.add(new TestFilter(ABI, MODULE_A, TEST_2).toString());
+ includeFilterSet.add(new TestFilter(ABI, MODULE_A, TEST_3).toString());
+ plan.addAllIncludeFilters(includeFilterSet); // add multiple include filters simultaneously
+ plan.addIncludeFilter(new TestFilter(null, MODULE_B, null).toString());
+ plan.addExcludeFilter(new TestFilter(null, MODULE_B, TEST_1).toString());
+ Set<String> excludeFilterSet = new HashSet<String>();
+ excludeFilterSet.add(new TestFilter(null, MODULE_B, TEST_2).toString());
+ excludeFilterSet.add(new TestFilter(null, MODULE_B, TEST_3).toString());
+ plan.addAllExcludeFilters(excludeFilterSet);
+
+ // Serialize to file
+ File planFile = FileUtil.createTempFile("test-plan-serialization", ".txt");
+ OutputStream planOutputStream = new FileOutputStream(planFile);
+ plan.serialize(planOutputStream);
+ planOutputStream.close();
+
+ // Parse plan and assert correctness
+ checkPlan(planFile);
+
+ }
+
+ public void testParsing() throws Exception {
+ File planFile = FileUtil.createTempFile("test-plan-parsing", ".txt");
+ FileWriter writer = new FileWriter(planFile);
+ Set<String> entries = new HashSet<String>();
+ entries.add(generateEntryXml(ABI, MODULE_A, TEST_1, true)); // include format 1
+ entries.add(generateEntryXml(ABI, MODULE_A, TEST_2, true));
+ entries.add(generateEntryXml(null, null,
+ new TestFilter(ABI, MODULE_A, TEST_3).toString(), true)); // include format 2
+ entries.add(generateEntryXml(null, MODULE_B, null, true));
+ entries.add(generateEntryXml(null, null,
+ new TestFilter(null, MODULE_B, TEST_1).toString(), false));
+ entries.add(generateEntryXml(null, null,
+ new TestFilter(null, MODULE_B, TEST_2).toString(), false));
+ entries.add(generateEntryXml(null, null,
+ new TestFilter(null, MODULE_B, TEST_3).toString(), false));
+ String xml = String.format(XML_BASE, String.join("\n", entries));
+ writer.write(xml);
+ writer.flush();
+ checkPlan(planFile);
+ }
+
+ private void checkPlan(File planFile) throws Exception {
+ InputStream planInputStream = new FileInputStream(planFile);
+ ITestPlan plan = new TestPlan();
+ plan.parse(planInputStream);
+ Set<String> planIncludes = plan.getIncludeFilters();
+ Set<String> planExcludes = plan.getExcludeFilters();
+ assertEquals("Expected 4 includes", 4, planIncludes.size());
+ assertTrue("Missing expected test include", planIncludes.contains(
+ new TestFilter(ABI, MODULE_A, TEST_1).toString()));
+ assertTrue("Missing expected test include", planIncludes.contains(
+ new TestFilter(ABI, MODULE_A, TEST_2).toString()));
+ assertTrue("Missing expected test include", planIncludes.contains(
+ new TestFilter(ABI, MODULE_A, TEST_3).toString()));
+ assertTrue("Missing expected module include", planIncludes.contains(
+ new TestFilter(null, MODULE_B, null).toString()));
+
+ assertEquals("Expected 3 excludes", 3, planExcludes.size());
+ assertTrue("Missing expected exclude", planExcludes.contains(
+ new TestFilter(null, MODULE_B, TEST_1).toString()));
+ assertTrue("Missing expected exclude", planExcludes.contains(
+ new TestFilter(null, MODULE_B, TEST_2).toString()));
+ assertTrue("Missing expected exclude", planExcludes.contains(
+ new TestFilter(null, MODULE_B, TEST_3).toString()));
+ }
+
+ // Helper for generating Entry XML tags
+ private String generateEntryXml(String abi, String name, String filter, boolean include) {
+ String filterType = (include) ? "include" : "exclude";
+ Set<String> attributes = new HashSet<String>();
+ if (filter != null) {
+ attributes.add(String.format(XML_ATTR, filterType, filter));
+ }
+ if (name != null) {
+ attributes.add(String.format(XML_ATTR, "name", name));
+ }
+ if (abi != null) {
+ attributes.add(String.format(XML_ATTR, "abi", abi));
+ }
+ return String.format(XML_ENTRY, String.join(" ", attributes));
+ }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/CddTest.java b/common/util/src/com/android/compatibility/common/util/CddTest.java
new file mode 100644
index 0000000..34ee663
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/CddTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks the type of test with purpose of asserting CDD requirements.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface CddTest {
+ String requirement();
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
index 7bd42b5..009d81b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
@@ -51,10 +51,7 @@
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
- getDevice().uninstallPackage(CLIENT_PKG);
-
- assertNull(getDevice().installPackage(
- MigrationHelper.getTestFile(mCtsBuild, CLIENT_APK), false));
+ reinstallClientPackage();
}
@Override
@@ -68,4 +65,11 @@
throws DeviceNotAvailableException {
Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
}
+
+ protected void reinstallClientPackage() throws Exception {
+ getDevice().uninstallPackage(CLIENT_PKG);
+
+ assertNull(getDevice().installPackage(
+ MigrationHelper.getTestFile(mCtsBuild, CLIENT_APK), false));
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
index ffc7597..889b20b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
@@ -42,6 +42,14 @@
runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testNotAskedAgain");
}
+ public void testDeniesOnceForAllClearedWhenPackageRemoved() throws Exception {
+ runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
+ "testRemovePackageStep1UserDenies");
+ reinstallClientPackage();
+ runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
+ "testRemovePackageStep2UserAcceptsDoNotClear");
+ }
+
public void testDeniesOnceButAllowsAskingAgain() throws Exception {
runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
"testDeniesOnceButAllowsAskingAgain");
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index e52af73..a26ec2d 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -150,7 +150,15 @@
return result;
}
+ /**
+ * Clears the DocumentsUI package data, unless test name ends on {@code DoNotClear}.
+ */
protected void clearDocumentsUi() throws Exception {
+ final String testName = getName();
+ if (testName.endsWith("DoNotClear")) {
+ Log.d(TAG, "Not clearing DocumentsUI due to test name: " + testName);
+ return;
+ }
executeShellCommand("pm clear com.android.documentsui");
}
}
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
index a4d35fb..5747adf 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
@@ -117,9 +117,9 @@
if (!supportedHardware()) return;
for (StorageVolume volume : getVolumes()) {
- userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
+ userAcceptsTest(volume, DIRECTORY_PICTURES);
if (!volume.isPrimary()) {
- userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
+ userAcceptsTest(volume, DIRECTORY_ROOT);
}
}
}
@@ -133,7 +133,7 @@
if (!output.isEmpty()) {
fail("Command '" + command + "' failed: '" + output + "'");
}
- userAcceptsOpenExternalDirectoryTest(getPrimaryVolume(), DIRECTORY_PICTURES);
+ userAcceptsTest(getPrimaryVolume(), DIRECTORY_PICTURES);
}
public void testNotAskedAgain() throws Exception {
@@ -141,7 +141,7 @@
for (StorageVolume volume : getVolumes()) {
final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
- final Uri grantedUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
+ final Uri grantedUri = userAcceptsTest(volume, DIRECTORY_PICTURES);
// Calls it again - since the permission has been granted, it should return right
// away, without popping up the permissions dialog.
@@ -151,7 +151,7 @@
assertEquals(grantedUri, newData.getData());
// Make sure other directories still require user permission.
- final Uri grantedUri2 = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ALARMS);
+ final Uri grantedUri2 = userAcceptsTest(volume, DIRECTORY_ALARMS);
assertNotEqual(grantedUri, grantedUri2);
}
}
@@ -162,7 +162,7 @@
for (StorageVolume volume : getVolumes()) {
if (volume.isPrimary()) continue;
final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
- final Uri grantedRootUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
+ final Uri grantedRootUri = userAcceptsTest(volume, DIRECTORY_ROOT);
// Calls it again - since the permission has been granted, it should return right
// away, without popping up the permissions dialog.
@@ -204,7 +204,7 @@
assertActivityFailed();
// Third time is a charm...
- userAcceptsOpenExternalDirectoryTest(volume, dir);
+ userAcceptsTest(volume, dir);
}
}
}
@@ -216,33 +216,44 @@
for (StorageVolume volume : getVolumes()) {
for (String dir : dirs) {
if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
- // Rejects the first attempt...
- UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
- dialog.assertDoNotAskAgainVisibility(false);
- dialog.noButton.click();
- assertActivityFailed();
-
- // ...and the second, checking the box
- dialog = openExternalDirectoryValidPath(volume, dir);
- UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
- assertTrue("checkbox should not be checkable", checkbox.isCheckable());
- assertFalse("checkbox should not be checked", checkbox.isChecked());
- checkbox.click();
- assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
- assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
-
- dialog.noButton.click();
- assertActivityFailed();
-
- // Third strike out...
- sendOpenExternalDirectoryIntent(volume, dir);
- assertActivityFailed();
+ deniesOnceForAllTest(volume, dir);
}
}
}
- private Uri userAcceptsOpenExternalDirectoryTest(StorageVolume volume, String directoryName)
- throws Exception {
+ private void deniesOnceForAllTest(StorageVolume volume, String dir) throws Exception {
+ // Rejects the first attempt...
+ UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
+ dialog.assertDoNotAskAgainVisibility(false);
+ dialog.noButton.click();
+ assertActivityFailed();
+
+ // ...and the second, checking the box
+ dialog = openExternalDirectoryValidPath(volume, dir);
+ UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
+ assertTrue("checkbox should not be checkable", checkbox.isCheckable());
+ assertFalse("checkbox should not be checked", checkbox.isChecked());
+ checkbox.click();
+ assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
+ assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
+
+ dialog.noButton.click();
+ assertActivityFailed();
+
+ // Third strike out...
+ sendOpenExternalDirectoryIntent(volume, dir);
+ assertActivityFailed();
+ }
+
+ public void testRemovePackageStep1UserDenies() throws Exception {
+ deniesOnceForAllTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
+ }
+
+ public void testRemovePackageStep2UserAcceptsDoNotClear() throws Exception {
+ userAcceptsTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
+ }
+
+ private Uri userAcceptsTest(StorageVolume volume, String directoryName) throws Exception {
// Asserts dialog contain the proper message.
final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, directoryName);
final String message = dialog.messageText.getText();
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
index 2d1197b..26d89c0 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
@@ -68,20 +68,22 @@
}
private void assertHasPassword() {
- dpm.setPasswordMinimumLength(mAdminComponent, 1);
+ final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
+ dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
try {
assertTrue("No password set", dpm.isActivePasswordSufficient());
} finally {
- dpm.setPasswordMinimumLength(mAdminComponent, 0);
+ dpm.setPasswordQuality(mAdminComponent, currentQuality);
}
}
private void assertNoPassword() {
- dpm.setPasswordMinimumLength(mAdminComponent, 1);
+ final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
+ dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
try {
assertFalse("Password is set", dpm.isActivePasswordSufficient());
} finally {
- dpm.setPasswordMinimumLength(mAdminComponent, 0);
+ dpm.setPasswordQuality(mAdminComponent, currentQuality);
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
index a00b4eb..d7c3bcf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
@@ -56,8 +56,9 @@
dpm.setPasswordMinimumLength(mAdminComponent, 10);
caseDescription = "minimum password length = 10";
assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertTrue(dpm.isActivePasswordSufficient()); // length not checked for this quality
+ // TODO(ascull): fix resetPassword() logic so these succeed
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
assertPasswordFails("abcd1234", caseDescription);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index c1c91da..50bcc60 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -85,16 +85,24 @@
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
- // Make sure foreground app doesn't lose access upon enabling it.
+ // Make sure foreground app doesn't lose access upon Battery Saver.
setBatterySaverMode(false);
launchActivity();
assertForegroundNetworkAccess();
setBatterySaverMode(true);
assertForegroundNetworkAccess();
+
+ // Although it should not have access while the screen is off.
+ turnScreenOff();
+ assertBackgroundNetworkAccess(false);
+ turnScreenOn();
+ assertForegroundNetworkAccess();
+
+ // Goes back to background state.
finishActivity();
assertBackgroundNetworkAccess(false);
- // Same for foreground service.
+ // Make sure foreground service doesn't lose access upon enabling Battery Saver.
setBatterySaverMode(false);
startForegroundService();
assertForegroundNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 8be1621..9245a6f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -244,7 +244,7 @@
if (isBackground(state.state)) {
return;
}
- Log.d(TAG, "App not on background state on attempt #" + i
+ Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 3e6bd33..881b3b4 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -98,16 +98,24 @@
assertsForegroundAlwaysHasNetworkAccess();
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- // Make sure foreground app doesn't lose access upon enabling it.
+ // Make sure foreground app doesn't lose access upon enabling Data Saver.
setRestrictBackground(false);
launchActivity();
assertForegroundNetworkAccess();
setRestrictBackground(true);
assertForegroundNetworkAccess();
+
+ // Although it should not have access while the screen is off.
+ turnScreenOff();
+ assertBackgroundNetworkAccess(false);
+ turnScreenOn();
+ assertForegroundNetworkAccess();
+
+ // Goes back to background state.
finishActivity();
assertBackgroundNetworkAccess(false);
- // Same for foreground service.
+ // Make sure foreground service doesn't lose access upon enabling Data Saver.
setRestrictBackground(false);
startForegroundService();
assertForegroundNetworkAccess();
diff --git a/hostsidetests/retaildemo/Android.mk b/hostsidetests/retaildemo/Android.mk
new file mode 100644
index 0000000..0aa5ee1
--- /dev/null
+++ b/hostsidetests/retaildemo/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 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 := CtsRetailDemoHostTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.retaildemo
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/retaildemo/AndroidTest.xml b/hostsidetests/retaildemo/AndroidTest.xml
new file mode 100644
index 0000000..c174489
--- /dev/null
+++ b/hostsidetests/retaildemo/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for the CTS retaildemo host tests">
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="test-file-name" value="CtsRetailDemoApp.apk" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsRetailDemoHostTestCases.jar" />
+ </test>
+
+</configuration>
\ No newline at end of file
diff --git a/hostsidetests/retaildemo/app/Android.mk b/hostsidetests/retaildemo/app/Android.mk
new file mode 100644
index 0000000..e91e1da
--- /dev/null
+++ b/hostsidetests/retaildemo/app/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 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)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsRetailDemoApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/retaildemo/app/AndroidManifest.xml b/hostsidetests/retaildemo/app/AndroidManifest.xml
new file mode 100644
index 0000000..b36ee1b
--- /dev/null
+++ b/hostsidetests/retaildemo/app/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2016 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.retaildemo">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.retaildemo"
+ android:label="RetailDemo device side tests" />
+</manifest>
diff --git a/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java b/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java
new file mode 100644
index 0000000..bb20b1a
--- /dev/null
+++ b/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.retaildemo;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class DemoUserTest {
+ private UserManager mUm;
+
+ @Before
+ public void setUp() {
+ mUm = InstrumentationRegistry.getContext().getSystemService(UserManager.class);
+ }
+
+ @Test
+ public void testIsDemoUser_success() {
+ assertTrue(mUm.isDemoUser());
+ }
+
+ @Test
+ public void testIsDemoUser_failure() {
+ assertFalse(mUm.isDemoUser());
+ }
+}
diff --git a/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java b/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java
new file mode 100644
index 0000000..5033b6d
--- /dev/null
+++ b/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 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 android.host.retaildemo;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Map;
+
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+ private static final String RETAIL_DEMO_TEST_PKG = "com.android.cts.retaildemo";
+ private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+ private IBuildInfo mBuildInfo;
+ private CompatibilityBuildHelper mBuildHelper;
+
+ private ArrayList<Integer> mTestUsers;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mBuildInfo = buildInfo;
+ mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertNotNull(mBuildInfo); // ensure build has been set before test is run.
+ mTestUsers = new ArrayList<>();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (int userId : mTestUsers) {
+ getDevice().removeUser(userId);
+ }
+ super.tearDown();
+ }
+
+ protected int createDemoUser() throws DeviceNotAvailableException, IllegalStateException {
+ final String command = "pm create-user --ephemeral --demo "
+ + "TestUser_" + System.currentTimeMillis();
+ CLog.d("Starting command: " + command);
+ final String output = getDevice().executeShellCommand(command);
+ CLog.d("Output for command " + command + ": " + output);
+
+ if (output.startsWith("Success")) {
+ try {
+ int userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+ mTestUsers.add(userId);
+ return userId;
+ } catch (NumberFormatException e) {
+ CLog.e("Failed to parse result: %s", output);
+ }
+ } else {
+ CLog.e("Failed to create demo user: %s", output);
+ }
+ throw new IllegalStateException();
+ }
+
+ protected void installAppAsUser(String appFileName, int userId)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName + " for user " + userId);
+ File apkFile = new File(mBuildHelper.getTestsDir(), appFileName);
+ final String result = getDevice().installPackageForUser(
+ apkFile, true, true, userId, "-t");
+ assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+ result);
+ }
+
+ protected boolean runDeviceTestsAsUser(String testClassName, String testMethodName, int userId)
+ throws Exception {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = RETAIL_DEMO_TEST_PKG + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ RETAIL_DEMO_TEST_PKG, RUNNER, getDevice().getIDevice());
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
+
+ TestRunResult runResult = listener.getCurrentRunResults();
+ printTestResult(runResult);
+ return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
+ }
+
+ private void printTestResult(TestRunResult runResult) {
+ for (Map.Entry<TestIdentifier, TestResult> testEntry :
+ runResult.getTestResults().entrySet()) {
+ TestResult testResult = testEntry.getValue();
+ CLog.d("Test " + testEntry.getKey() + ": " + testResult.getStatus());
+ if (testResult.getStatus() != TestStatus.PASSED) {
+ CLog.d(testResult.getStackTrace());
+ }
+ }
+ }
+}
diff --git a/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java b/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java
new file mode 100644
index 0000000..8252022
--- /dev/null
+++ b/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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 android.host.retaildemo;
+
+import static junit.framework.Assert.assertTrue;
+
+public class DemoModeTest extends BaseTestCase {
+ private static final String RETAIL_DEMO_TEST_APK = "CtsRetailDemoApp.apk";
+
+ public void testIsDemoUser_inPrimaryUser() throws Exception {
+ assertTrue(runDeviceTestsAsUser(
+ ".DemoUserTest", "testIsDemoUser_failure", getDevice().getPrimaryUserId()));
+ }
+
+ public void testIsDemoUser_inDemoUser() throws Exception {
+ final int demoUserId = createDemoUser();
+ getDevice().startUser(demoUserId);
+ installAppAsUser(RETAIL_DEMO_TEST_APK, demoUserId);
+ assertTrue(runDeviceTestsAsUser(
+ ".DemoUserTest", "testIsDemoUser_success", demoUserId));
+ }
+}
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index b74eba7..a8c35d2 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -27,6 +27,8 @@
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
+import com.android.compatibility.common.util.CddTest;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -123,6 +125,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testGlobalEnforcing() throws Exception {
CollectingOutputReceiver out = new CollectingOutputReceiver();
mDevice.executeShellCommand("cat /sys/fs/selinux/enforce", out);
@@ -134,6 +137,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
@RestrictedBuildTest
public void testAllDomainsEnforcing() throws Exception {
@@ -188,6 +192,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testMLSAttributes() throws Exception {
assertNotInAttribute("mlstrustedsubject", "untrusted_app");
assertNotInAttribute("mlstrustedobject", "app_data_file");
@@ -198,6 +203,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testValidSeappContexts() throws Exception {
/* obtain seapp_contexts file from running device */
@@ -257,6 +263,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testAospSeappContexts() throws Exception {
/* obtain seapp_contexts file from running device */
@@ -276,6 +283,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testAospFileContexts() throws Exception {
/* retrieve the checkfc executable from jar */
@@ -311,6 +319,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testAospPropertyContexts() throws Exception {
/* obtain property_contexts file from running device */
@@ -330,6 +339,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testAospServiceContexts() throws Exception {
/* obtain service_contexts file from running device */
@@ -348,6 +358,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testValidFileContexts() throws Exception {
/* retrieve the checkfc executable from jar */
@@ -383,6 +394,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testValidPropertyContexts() throws Exception {
/* retrieve the checkfc executable from jar */
@@ -418,6 +430,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testValidServiceContexts() throws Exception {
/* retrieve the checkfc executable from jar */
@@ -453,6 +466,7 @@
*
* @throws Exception
*/
+ @CddTest(requirement="9.7")
public void testNoBooleans() throws Exception {
/* run sepolicy-analyze booleans check on policy file */
@@ -629,67 +643,80 @@
}
/* Init is always there */
+ @CddTest(requirement="9.7")
public void testInitDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:init:s0", "/init");
}
/* Ueventd is always there */
+ @CddTest(requirement="9.7")
public void testUeventdDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:ueventd:s0", "/sbin/ueventd");
}
/* Devices always have healthd */
+ @CddTest(requirement="9.7")
public void testHealthdDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:healthd:s0", "/sbin/healthd");
}
/* Servicemanager is always there */
+ @CddTest(requirement="9.7")
public void testServicemanagerDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:servicemanager:s0", "/system/bin/servicemanager");
}
/* Vold is always there */
+ @CddTest(requirement="9.7")
public void testVoldDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:vold:s0", "/system/bin/vold");
}
/* netd is always there */
+ @CddTest(requirement="9.7")
public void testNetdDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:netd:s0", "/system/bin/netd");
}
/* Debuggerd is always there */
+ @CddTest(requirement="9.7")
public void testDebuggerdDomain() throws DeviceNotAvailableException {
assertDomainN("u:r:debuggerd:s0", "/system/bin/debuggerd", "/system/bin/debuggerd64",
"debuggerd:signaller", "debuggerd64:signaller");
}
/* Surface flinger is always there */
+ @CddTest(requirement="9.7")
public void testSurfaceflingerDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:surfaceflinger:s0", "/system/bin/surfaceflinger");
}
/* Zygote is always running */
+ @CddTest(requirement="9.7")
public void testZygoteDomain() throws DeviceNotAvailableException {
assertDomainN("u:r:zygote:s0", "zygote", "zygote64");
}
/* Checks drmserver for devices that require it */
+ @CddTest(requirement="9.7")
public void testDrmServerDomain() throws DeviceNotAvailableException {
assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
}
/* Installd is always running */
+ @CddTest(requirement="9.7")
public void testInstalldDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:installd:s0", "/system/bin/installd");
}
/* keystore is always running */
+ @CddTest(requirement="9.7")
public void testKeystoreDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:keystore:s0", "/system/bin/keystore");
}
/* System server better be running :-P */
+ @CddTest(requirement="9.7")
public void testSystemServerDomain() throws DeviceNotAvailableException {
assertDomainOne("u:r:system_server:s0", "system_server");
}
@@ -698,26 +725,31 @@
* Some OEMs do not use sdcardd so transient. Other OEMs have multiple sdcards
* so they run the daemon multiple times.
*/
+ @CddTest(requirement="9.7")
public void testSdcarddDomain() throws DeviceNotAvailableException {
assertDomainHasExecutable("u:r:sdcardd:s0", "/system/bin/sdcard");
}
/* Watchdogd may or may not be there */
+ @CddTest(requirement="9.7")
public void testWatchdogdDomain() throws DeviceNotAvailableException {
assertDomainZeroOrOne("u:r:watchdogd:s0", "/sbin/watchdogd");
}
/* logd may or may not be there */
+ @CddTest(requirement="9.7")
public void testLogdDomain() throws DeviceNotAvailableException {
assertDomainZeroOrOne("u:r:logd:s0", "/system/bin/logd");
}
/* lmkd may or may not be there */
+ @CddTest(requirement="9.7")
public void testLmkdDomain() throws DeviceNotAvailableException {
assertDomainZeroOrOne("u:r:lmkd:s0", "/system/bin/lmkd");
}
/* Wifi may be off so cardinality of 0 or 1 is ok */
+ @CddTest(requirement="9.7")
public void testWpaDomain() throws DeviceNotAvailableException {
assertDomainZeroOrOne("u:r:wpa:s0", "/system/bin/wpa_supplicant");
}
@@ -726,6 +758,7 @@
* Nothing should be running in this domain, cardinality test is all thats
* needed
*/
+ @CddTest(requirement="9.7")
public void testInitShellDomain() throws DeviceNotAvailableException {
assertDomainEmpty("u:r:init_shell:s0");
}
@@ -734,6 +767,7 @@
* Nothing should be running in this domain, cardinality test is all thats
* needed
*/
+ @CddTest(requirement="9.7")
public void testRecoveryDomain() throws DeviceNotAvailableException {
assertDomainEmpty("u:r:recovery:s0");
}
@@ -742,6 +776,7 @@
* Nothing should be running in this domain, cardinality test is all thats
* needed
*/
+ @CddTest(requirement="9.7")
@RestrictedBuildTest
public void testSuDomain() throws DeviceNotAvailableException {
assertDomainEmpty("u:r:su:s0");
@@ -750,6 +785,7 @@
/*
* All kthreads should be in kernel context.
*/
+ @CddTest(requirement="9.7")
public void testKernelDomain() throws DeviceNotAvailableException {
String domain = "u:r:kernel:s0";
List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain);
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 10a6792..b5460a6 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -329,6 +329,21 @@
<activity android:name="android.app.stubs.KeyboardShortcutsActivity" />
+ <service
+ android:name="android.app.stubs.LiveWallpaper"
+ android:icon="@drawable/robot"
+ android:label="@string/wallpaper_title"
+ android:permission="android.permission.BIND_WALLPAPER">
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService">
+ </action>
+ </intent-filter>
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/wallpaper">
+ </meta-data>
+ </service>
+
</application>
</manifest>
diff --git a/tests/app/app/res/values/strings.xml b/tests/app/app/res/values/strings.xml
index 5e9e6d7..839e398 100644
--- a/tests/app/app/res/values/strings.xml
+++ b/tests/app/app/res/values/strings.xml
@@ -177,4 +177,10 @@
I think so, so how about double this string, like copy and paste! </string>
<string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
<string name="hello">Hello World</string>
+
+ <string name="wallpaper_description">Description</string>
+ <string name="wallpaper_collection">Collection</string>
+ <string name="wallpaper_title">Title</string>
+ <string name="wallpaper_context">Context</string>
+ <string name="wallpaper_context_uri">http://android.com</string>
</resources>
diff --git a/tests/app/app/res/xml/wallpaper.xml b/tests/app/app/res/xml/wallpaper.xml
new file mode 100644
index 0000000..f70b20f
--- /dev/null
+++ b/tests/app/app/res/xml/wallpaper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<wallpaper
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:thumbnail="@drawable/icon_red"
+ android:author="@string/wallpaper_collection"
+ android:description="@string/wallpaper_description"
+ android:showMetadataInPreview="true"
+ android:contextDescription="@string/wallpaper_context"
+ android:contextUri="@string/wallpaper_context_uri"
+/>
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/LiveWallpaper.java b/tests/app/app/src/android/app/stubs/LiveWallpaper.java
new file mode 100644
index 0000000..5b8a82e
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LiveWallpaper.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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 android.app.stubs;
+
+import android.service.wallpaper.WallpaperService;
+
+public class LiveWallpaper extends WallpaperService {
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index 2b84be6..fefa546 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -27,6 +27,8 @@
import android.view.Display;
import android.view.WindowManager;
+import com.android.compatibility.common.util.CddTest;
+
import java.util.HashMap;
import java.util.Map;
@@ -148,6 +150,7 @@
}
}
+ @CddTest(requirement="3.7")
public void testGetMemoryClass() throws Exception {
int memoryClass = getMemoryClass();
int screenDensity = getScreenDensity();
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 50eb96b..1bec983 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -34,6 +34,8 @@
import android.util.Log;
import android.webkit.cts.CtsTestServer;
+import com.android.compatibility.common.util.CddTest;
+
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
@@ -181,6 +183,7 @@
}
}
+ @CddTest(requirement="7.6.1")
public void testMinimumDownload() throws Exception {
final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
try {
diff --git a/tests/app/src/android/app/cts/WallpaperInfoTest.java b/tests/app/src/android/app/cts/WallpaperInfoTest.java
new file mode 100644
index 0000000..1b30902
--- /dev/null
+++ b/tests/app/src/android/app/cts/WallpaperInfoTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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 android.app.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.WallpaperInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.wallpaper.WallpaperService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WallpaperInfoTest {
+
+ @Test
+ public void test() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ intent.setPackage("android.app.stubs");
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ assertEquals(1, result.size());
+ ResolveInfo info = result.get(0);
+ WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info);
+ assertEquals("Title", wallpaperInfo.loadLabel(pm));
+ assertEquals("Description", wallpaperInfo.loadDescription(pm));
+ assertEquals("Collection", wallpaperInfo.loadAuthor(pm));
+ assertEquals("Context", wallpaperInfo.loadContextDescription(pm));
+ assertEquals("http://android.com", wallpaperInfo.loadContextUri(pm).toString());
+ assertEquals(true, wallpaperInfo.getShowMetadataInPreview());
+ assertNotNull(wallpaperInfo.loadIcon(pm));
+ assertNotNull(wallpaperInfo.loadThumbnail(pm));
+ }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index ebda3dd..ae5c2ea 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -2622,7 +2622,7 @@
}
}
- mSession.stopRepeating();
+ stopPreview();
}
/**
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 8d141c4..a95f4f3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -222,9 +222,11 @@
* Does _not_ wait for the device to go idle
*/
protected void stopPreview() throws Exception {
- if (VERBOSE) Log.v(TAG, "Stopping preview");
// Stop repeat, wait for captures to complete, and disconnect from surfaces
- mSession.close();
+ if (mSession != null) {
+ if (VERBOSE) Log.v(TAG, "Stopping preview");
+ mSession.close();
+ }
}
/**
@@ -232,11 +234,13 @@
* resulting in an idle device.
*/
protected void stopPreviewAndDrain() throws Exception {
- if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
// Stop repeat, wait for captures to complete, and disconnect from surfaces
- mSession.close();
- mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_CLOSED,
- /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+ if (mSession != null) {
+ if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+ mSession.close();
+ mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_CLOSED,
+ /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
}
/**
diff --git a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
index f30c7a3..a2df743 100644
--- a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
@@ -18,6 +18,7 @@
import android.cts.util.CtsAndroidTestCase;
+import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.DeviceReportLog;
public class RandomRWTest extends CtsAndroidTestCase {
@@ -32,6 +33,7 @@
super.tearDown();
}
+ @CddTest(requirement="8.2")
public void testRandomRead() throws Exception {
final int READ_BUFFER_SIZE = 4 * 1024;
final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), READ_BUFFER_SIZE);
@@ -46,6 +48,7 @@
}
// It is taking too long in some device, and thus cannot run multiple times
+ @CddTest(requirement="8.2")
public void testRandomUpdate() throws Exception {
final int WRITE_BUFFER_SIZE = 4 * 1024;
final long fileSize = 256 * 1024 * 1024;
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
index c19d03c..2db2f24 100644
--- a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -25,6 +25,8 @@
import com.android.compatibility.common.util.ResultUnit;
import com.android.compatibility.common.util.Stat;
+import com.android.compatibility.common.util.CddTest;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -44,6 +46,7 @@
super.tearDown();
}
+ @CddTest(requirement="8.2")
public void testSingleSequentialWrite() throws Exception {
final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
if (fileSize == 0) { // not enough space, give up
@@ -85,6 +88,7 @@
NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
}
+ @CddTest(requirement="8.2")
public void testSingleSequentialRead() throws Exception {
final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
if (fileSize == 0) { // not enough space, give up
diff --git a/tests/tests/graphics/Android.mk b/tests/tests/graphics/Android.mk
index 7e26e07..d4d0d33 100644
--- a/tests/tests/graphics/Android.mk
+++ b/tests/tests/graphics/Android.mk
@@ -32,8 +32,6 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts
-LOCAL_SDK_VERSION := current
-
include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml b/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml
new file mode 100644
index 0000000..1e9fbff
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2016 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.
+ -->
+
+<com.android.internal.graphics.drawable.AnimationScaleListDrawable
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/vector_icon_create" />
+ <item android:drawable="@drawable/animation_vector_drawable_grouping_1" />
+</com.android.internal.graphics.drawable.AnimationScaleListDrawable>
diff --git a/tests/tests/graphics/res/drawable/layerdrawable_theme.xml b/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
index 2a678ff..fac42b2 100644
--- a/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
+++ b/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
@@ -25,5 +25,6 @@
android:dither="?attr/themeBoolean"
android:src="?attr/themeNinePatch" />
</item>
+ <item android:drawable="?attr/themeDrawable" />
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java
new file mode 100644
index 0000000..3445641
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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 android.graphics.drawable.cts;
+
+import android.animation.ValueAnimator;
+import android.graphics.cts.R;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * This test is used to verify that the CustomAnimationScaleListDrawable's current drawable depends
+ * on animation duration scale. When the scale is 0, it is a static drawable, otherwise, it is an
+ * animatable drawable.
+ */
+public class CustomAnimationScaleListDrawableTest extends AndroidTestCase {
+ @MediumTest
+ public void testNonZeroDurationScale() {
+ float originalScale = ValueAnimator.getDurationScale();
+ ValueAnimator.setDurationScale(2.0f);
+ Drawable dr = getContext().getDrawable(R.drawable.custom_animation_scale_list_drawable);
+ assertTrue(dr instanceof DrawableContainer);
+
+ assertTrue(dr.getCurrent() instanceof Animatable);
+ ValueAnimator.setDurationScale(originalScale);
+ }
+
+ @MediumTest
+ public void testZeroDurationScale() {
+ float originalScale = ValueAnimator.getDurationScale();
+ ValueAnimator.setDurationScale(0f);
+ Drawable dr = getContext().getDrawable(R.drawable.custom_animation_scale_list_drawable);
+ assertTrue(dr instanceof DrawableContainer);
+ assertFalse(dr.getCurrent() instanceof Animatable);
+ ValueAnimator.setDurationScale(originalScale);
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
index 52bef55..7c6fe7c 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
@@ -22,6 +22,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Shader.TileMode;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.GradientDrawable;
@@ -34,7 +35,6 @@
import android.graphics.cts.R;
-@TargetApi(21)
public class ThemedDrawableTest extends AndroidTestCase {
@Override
@@ -150,9 +150,14 @@
assertEquals(true, d.isAutoMirrored());
BitmapDrawable bitmapDrawable = (BitmapDrawable) d.getDrawable(0);
+ assertEquals(d, bitmapDrawable.getCallback());
internalTestBitmapDrawable(bitmapDrawable);
NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) d.getDrawable(1);
+ assertEquals(d, ninePatchDrawable.getCallback());
internalTestNinePatchDrawable(ninePatchDrawable);
+
+ Drawable themeDrawable = (Drawable) d.getDrawable(2);
+ assertEquals(d, themeDrawable.getCallback());
}
}
diff --git a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
index 33e82043..1468382 100644
--- a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
+++ b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
@@ -63,6 +63,8 @@
EglConfigCtsActivity activity = launchActivity("android.graphics.cts",
EglConfigCtsActivity.class, extras);
activity.waitToFinishDrawing();
+ // TODO(b/30948621): Remove the sleep below once b/30948621 is fixed.
+ Thread.sleep(500);
activity.finish();
mInstrumentation.waitForIdleSync();
}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
index 0f4775e..32c54c9 100644
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
@@ -135,7 +135,13 @@
List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
Log.i(TAG, "Number of GPS measurement events received = " + events.size());
- // Ensure that after getting a few (at least 2) measurements, that we still don't have
+ if (events.isEmpty()) {
+ SoftAssert.failOrWarning(isMeasurementTestStrict(), "No measurement events received",
+ false);
+ return; // All of the following checks rely on there being measurements
+ }
+
+ // Ensure that after getting a few (at least 2) measurement events, that we still don't have
// location (i.e. that we got measurements before location.) Fail, if strict, warn, if not.
SoftAssert.failOrWarning(isMeasurementTestStrict(),
"Location was received before " + events.size() +
@@ -149,9 +155,8 @@
return; // allow a (passing) return, if not strict, otherwise continue
}
- // If device is not indoors, verify
- // 1) that we receive GPS measurements before being able to calculate the position solution
- // 2) that mandatory fields of GnssMeasurement are in expected ranges.
+ // If device has received measurements also verify
+ // that mandatory fields of GnssMeasurement are in expected ranges.
GnssMeasurementsEvent firstEvent = events.get(0);
Collection<GnssMeasurement> gpsMeasurements = firstEvent.getMeasurements();
int satelliteCount = gpsMeasurements.size();
diff --git a/tests/tests/location/src/android/location/cts/SoftAssert.java b/tests/tests/location/src/android/location/cts/SoftAssert.java
index 3913149..4349f07 100644
--- a/tests/tests/location/src/android/location/cts/SoftAssert.java
+++ b/tests/tests/location/src/android/location/cts/SoftAssert.java
@@ -156,7 +156,9 @@
if (testIsStrict) {
Assert.assertTrue(message, condition);
} else {
- failAsWarning("", message);
+ if (!condition) {
+ failAsWarning("", message);
+ }
}
}
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
index 0b2d875..217d8eb 100644
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
@@ -28,8 +28,11 @@
import junit.framework.Assert;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.Set;
/**
* Helper class for GnssMeasurement Tests.
@@ -52,6 +55,21 @@
" listener has failed, this indicates a platform bug. Please report the issue with" +
" a full bugreport.";
+ // The valid Gnss navigation message type as listed in
+ // android/hardware/libhardware/include/hardware/gps.h
+ public static final Set<Integer> GNSS_NAVIGATION_MESSAGE_TYPE =
+ new HashSet<Integer>(Arrays.asList(
+ 0x0101,
+ 0x0102,
+ 0x0103,
+ 0x0104,
+ 0x0301,
+ 0x0501,
+ 0x0502,
+ 0x0601,
+ 0x0602
+ ));
+
/**
* Check if test can be run on the current device.
*
@@ -63,15 +81,12 @@
String testTag,
int minHardwareYear,
boolean isCtsVerifier) {
- // TODO(sumitk): Enable this check once api 24 for N is avaiable.
- /*
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Log.i(TAG, "This test is designed to work on N or newer. " +
"Test is being skipped because the platform version is being run in " +
Build.VERSION.SDK_INT);
return false;
}
- */
// If device does not have a GPS, skip the test.
PackageManager pm = testLocationManager.getContext().getPackageManager();
@@ -670,9 +685,9 @@
SoftAssert softAssert = new SoftAssert(TAG);
for (GnssNavigationMessage message : events) {
int type = message.getType();
- softAssert.assertTrue("Gnss Navigation Message Type:expected [0x0101 - 0x0104]," +
- " actual = " + type,
- type >= 0x0101 && type <= 0x0104);
+ softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
+ getGnssNavMessageTypes() + "] actual = " + type,
+ GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
// if message type == TYPE_L1CA, verify PRN & Data Size.
int messageType = message.getType();
@@ -691,4 +706,14 @@
}
softAssert.assertAll();
}
+
+ private static String getGnssNavMessageTypes() {
+ StringBuilder typesStr = new StringBuilder();
+ for (int type : GNSS_NAVIGATION_MESSAGE_TYPE) {
+ typesStr.append(String.format("0x%04X", type));
+ typesStr.append(", ");
+ }
+
+ return typesStr.length() > 2 ? typesStr.substring(0, typesStr.length() - 2) : "";
+ }
}
diff --git a/tests/tests/os/src/android/os/cts/AsyncTaskTest.java b/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
index efd1eed..5bf1f59 100644
--- a/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
+++ b/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
@@ -21,6 +21,7 @@
import android.os.AsyncTask;
import android.test.InstrumentationTestCase;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class AsyncTaskTest extends InstrumentationTestCase {
@@ -30,7 +31,8 @@
private static final long DURATION = 2000;
private static final String[] PARAM = { "Test" };
- private static MyAsyncTask mAsyncTask;
+ private static AsyncTask mAsyncTask;
+ private static MyAsyncTask mMyAsyncTask;
public void testAsyncTask() throws Throwable {
doTestAsyncTask(0);
@@ -43,51 +45,51 @@
private void doTestAsyncTask(final long timeout) throws Throwable {
startAsyncTask();
if (timeout > 0) {
- assertEquals(RESULT, mAsyncTask.get(DURATION, TimeUnit.MILLISECONDS).longValue());
+ assertEquals(RESULT, mMyAsyncTask.get(DURATION, TimeUnit.MILLISECONDS).longValue());
} else {
- assertEquals(RESULT, mAsyncTask.get().longValue());
+ assertEquals(RESULT, mMyAsyncTask.get().longValue());
}
// wait for the task to finish completely (including onPostResult()).
new PollingCheck(DURATION) {
protected boolean check() {
- return mAsyncTask.getStatus() == AsyncTask.Status.FINISHED;
+ return mMyAsyncTask.getStatus() == AsyncTask.Status.FINISHED;
}
}.run();
- assertTrue(mAsyncTask.isOnPreExecuteCalled);
- assert(mAsyncTask.hasRun);
- assertEquals(PARAM.length, mAsyncTask.parameters.length);
+ assertTrue(mMyAsyncTask.isOnPreExecuteCalled);
+ assert(mMyAsyncTask.hasRun);
+ assertEquals(PARAM.length, mMyAsyncTask.parameters.length);
for (int i = 0; i < PARAM.length; i++) {
- assertEquals(PARAM[i], mAsyncTask.parameters[i]);
+ assertEquals(PARAM[i], mMyAsyncTask.parameters[i]);
}
// even though the background task has run, the onPostExecute() may not have been
// executed yet and the progress update may not have been processed. Wait until the task
// has completed, which guarantees that onPostExecute has been called.
- assertEquals(RESULT, mAsyncTask.postResult.longValue());
- assertEquals(AsyncTask.Status.FINISHED, mAsyncTask.getStatus());
+ assertEquals(RESULT, mMyAsyncTask.postResult.longValue());
+ assertEquals(AsyncTask.Status.FINISHED, mMyAsyncTask.getStatus());
- if (mAsyncTask.exception != null) {
- throw mAsyncTask.exception;
+ if (mMyAsyncTask.exception != null) {
+ throw mMyAsyncTask.exception;
}
// wait for progress update to be processed (happens asynchronously)
new PollingCheck(DURATION) {
protected boolean check() {
- return mAsyncTask.updateValue != null;
+ return mMyAsyncTask.updateValue != null;
}
}.run();
- assertEquals(UPDATE_VALUE.length, mAsyncTask.updateValue.length);
+ assertEquals(UPDATE_VALUE.length, mMyAsyncTask.updateValue.length);
for (int i = 0; i < UPDATE_VALUE.length; i++) {
- assertEquals(UPDATE_VALUE[i], mAsyncTask.updateValue[i]);
+ assertEquals(UPDATE_VALUE[i], mMyAsyncTask.updateValue[i]);
}
runTestOnUiThread(new Runnable() {
public void run() {
try {
// task should not be allowed to execute twice
- mAsyncTask.execute(PARAM);
+ mMyAsyncTask.execute(PARAM);
fail("Failed to throw exception!");
} catch (IllegalStateException e) {
// expected
@@ -99,44 +101,81 @@
public void testCancelWithInterrupt() throws Throwable {
startAsyncTask();
Thread.sleep(COMPUTE_TIME / 2);
- assertTrue(mAsyncTask.cancel(true));
+ assertTrue(mMyAsyncTask.cancel(true));
// already cancelled
- assertFalse(mAsyncTask.cancel(true));
+ assertFalse(mMyAsyncTask.cancel(true));
Thread.sleep(DURATION);
- assertTrue(mAsyncTask.isCancelled());
- assertTrue(mAsyncTask.isOnCancelledCalled);
- assertNotNull(mAsyncTask.exception);
- assertTrue(mAsyncTask.exception instanceof InterruptedException);
+ assertTrue(mMyAsyncTask.isCancelled());
+ assertTrue(mMyAsyncTask.isOnCancelledCalled);
+ assertNotNull(mMyAsyncTask.exception);
+ assertTrue(mMyAsyncTask.exception instanceof InterruptedException);
}
public void testCancel() throws Throwable {
startAsyncTask();
Thread.sleep(COMPUTE_TIME / 2);
- assertTrue(mAsyncTask.cancel(false));
+ assertTrue(mMyAsyncTask.cancel(false));
// already cancelled
- assertFalse(mAsyncTask.cancel(false));
+ assertFalse(mMyAsyncTask.cancel(false));
Thread.sleep(DURATION);
- assertTrue(mAsyncTask.isCancelled());
- assertTrue(mAsyncTask.isOnCancelledCalled);
- assertNull(mAsyncTask.exception);
+ assertTrue(mMyAsyncTask.isCancelled());
+ assertTrue(mMyAsyncTask.isOnCancelledCalled);
+ assertNull(mMyAsyncTask.exception);
}
public void testCancelTooLate() throws Throwable {
startAsyncTask();
Thread.sleep(DURATION);
- assertFalse(mAsyncTask.cancel(false));
- assertTrue(mAsyncTask.isCancelled());
- assertFalse(mAsyncTask.isOnCancelledCalled);
- assertNull(mAsyncTask.exception);
+ assertFalse(mMyAsyncTask.cancel(false));
+ assertTrue(mMyAsyncTask.isCancelled());
+ assertFalse(mMyAsyncTask.isOnCancelledCalled);
+ assertNull(mMyAsyncTask.exception);
+ }
+
+ public void testCancellationWithException() throws Throwable {
+ final CountDownLatch readyToCancel = new CountDownLatch(1);
+ final CountDownLatch readyToThrow = new CountDownLatch(1);
+ final CountDownLatch calledOnCancelled = new CountDownLatch(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAsyncTask = new AsyncTask() {
+ @Override
+ protected Object doInBackground(Object... params) {
+ readyToCancel.countDown();
+ try {
+ readyToThrow.await();
+ } catch (InterruptedException e) {}
+ // This exception is expected to be caught and ignored
+ throw new RuntimeException();
+ }
+
+ @Override
+ protected void onCancelled(Object o) {
+ calledOnCancelled.countDown();
+ }
+ };
+ }
+ });
+
+ mAsyncTask.execute();
+ if (!readyToCancel.await(5, TimeUnit.SECONDS)) {
+ fail("Test failure: doInBackground did not run in time.");
+ }
+ mAsyncTask.cancel(false);
+ readyToThrow.countDown();
+ if (!calledOnCancelled.await(5, TimeUnit.SECONDS)) {
+ fail("onCancelled not called!");
+ }
}
private void startAsyncTask() throws Throwable {
runTestOnUiThread(new Runnable() {
public void run() {
- mAsyncTask = new MyAsyncTask();
- assertEquals(AsyncTask.Status.PENDING, mAsyncTask.getStatus());
- assertEquals(mAsyncTask, mAsyncTask.execute(PARAM));
- assertEquals(AsyncTask.Status.RUNNING, mAsyncTask.getStatus());
+ mMyAsyncTask = new MyAsyncTask();
+ assertEquals(AsyncTask.Status.PENDING, mMyAsyncTask.getStatus());
+ assertEquals(mMyAsyncTask, mMyAsyncTask.execute(PARAM));
+ assertEquals(AsyncTask.Status.RUNNING, mMyAsyncTask.getStatus());
}
});
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index ff84655..d167249 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -445,6 +445,10 @@
* {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
*/
public void testConnectionRemoveExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
testConnectionPutExtras();
mConnection.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
@@ -458,6 +462,10 @@
* {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
*/
public void testConnectionRemoveExtras2() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
testConnectionPutExtras();
mConnection.removeExtras(TEST_EXTRA_KEY);
diff --git a/tests/tests/toast/Android.mk b/tests/tests/toast/Android.mk
new file mode 100644
index 0000000..d9f15eb
--- /dev/null
+++ b/tests/tests/toast/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 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 := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsToastTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toast/AndroidManifest.xml b/tests/tests/toast/AndroidManifest.xml
new file mode 100644
index 0000000..1fa71c4
--- /dev/null
+++ b/tests/tests/toast/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="android.widget.toast.cts">
+
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="26" />
+
+ <application>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.widget.toast.cts"
+ android:label="CTS tests for toast windows">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
new file mode 100644
index 0000000..d0a5eed
--- /dev/null
+++ b/tests/tests/toast/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for Toast test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsToastTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.widget.toast.cts" />
+ </test>
+</configuration>
diff --git a/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
new file mode 100644
index 0000000..fd75309
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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 android.widget.toast.cts;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+import org.junit.Before;
+
+/**
+ * Base class for toast tests.
+ */
+public abstract class BaseToastTest {
+ protected static final long TOAST_TIMEOUT_MILLIS = 5000; // 5 sec
+ protected static final long EVENT_TIMEOUT_MILLIS = 5000; // 5 sec
+
+ protected Context mContext;
+ protected Instrumentation mInstrumentation;
+ protected UiAutomation mUiAutomation;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mUiAutomation = mInstrumentation.getUiAutomation();
+ waitForToastTimeout();
+ }
+
+ protected void waitForToastTimeout() {
+ SystemClock.sleep(TOAST_TIMEOUT_MILLIS);
+ }
+
+ protected void showToastsViaToastApis(int count) throws Exception {
+ Exception[] exceptions = new Exception[1];
+ mInstrumentation.runOnMainSync(
+ () -> {
+ try {
+ for (int i = 0; i < count; i++) {
+ Toast.makeText(mContext, getClass().getName(),
+ Toast.LENGTH_LONG).show();
+ }
+ } catch (Exception e) {
+ exceptions[0] = e;
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
+ }
+
+ protected void showToastsViaAddingWindow(int count, boolean focusable) throws Exception {
+ Exception[] exceptions = new Exception[1];
+ mInstrumentation.runOnMainSync(() -> {
+ try {
+ for (int i = 0; i < count; i++) {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+ params.type = WindowManager.LayoutParams.TYPE_TOAST;
+ params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+ if (!focusable) {
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+
+ TextView textView = new TextView(mContext);
+ textView.setText(BaseToastTest.class.getName());
+
+ WindowManager windowManager = mContext
+ .getSystemService(WindowManager.class);
+ windowManager.addView(textView, params);
+ }
+ } catch (Exception e) {
+ exceptions[0] = e;
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
+ }
+}
diff --git a/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
new file mode 100644
index 0000000..4ac88b3
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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 android.widget.toast.cts;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting API 25+
+ * like this app the only way to add toast windows is via the dedicated
+ * toast APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LegacyToastTest extends BaseToastTest {
+ @Test
+ public void testAddSingleToastViaTestApisWhenUidFocused() throws Exception {
+ // Normal toast windows cannot be obtained vie the accessibility APIs because
+ // they are not touchable. In this case not crashing is good enough.
+ showToastsViaToastApis(1);
+ }
+
+ @Test
+ public void testAddTwoToastViaTestApisWhenUidFocused() throws Exception {
+ // Normal toast windows cannot be obtained vie the accessibility APIs because
+ // they are not touchable. In this case not crashing is good enough.
+ showToastsViaToastApis(2);
+
+ // Wait for the first one to expire
+ waitForToastTimeout();
+ }
+
+ @Test
+ public void testAddSingleToastViaAddingWindowApisWhenUidFocused() throws Exception {
+ try {
+ showToastsViaAddingWindow(1, false);
+ fail("Shouldn't be able to add toast windows directly");
+ } catch (WindowManager.BadTokenException e) {
+ /* expected */
+ }
+ }
+}
diff --git a/tests/tests/toastlegacy/Android.mk b/tests/tests/toastlegacy/Android.mk
new file mode 100644
index 0000000..ac5825d
--- /dev/null
+++ b/tests/tests/toastlegacy/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2016 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 := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../toast/src/android/widget/toast/cts/BaseToastTest.java
+
+LOCAL_PACKAGE_NAME := CtsToastLegacyTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toastlegacy/AndroidManifest.xml b/tests/tests/toastlegacy/AndroidManifest.xml
new file mode 100644
index 0000000..8529b5c
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="android.widget.toast.legacy.cts">
+
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+ <application>
+ <activity android:name="android.widget.toast.cts.legacy.ToastActivity">
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.widget.toast.legacy.cts"
+ android:label="CTS tests for legacy toast windows">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toastlegacy/AndroidTest.xml b/tests/tests/toastlegacy/AndroidTest.xml
new file mode 100644
index 0000000..a208077
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for Toast legacy test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsToastLegacyTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.widget.toast.legacy.cts" />
+ </test>
+</configuration>
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
new file mode 100644
index 0000000..745385e
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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 android.widget.toast.cts.legacy;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Activity that shows toasts on demand.
+ */
+public class ToastActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ View view = new View(this);
+ view.setBackgroundColor(Color.BLUE);
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ setContentView(view);
+ }
+}
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
new file mode 100644
index 0000000..207e6ea
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 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 android.widget.toast.cts.legacy;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.toast.cts.BaseToastTest;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting SDK
+ * 25 and below a toast window can be added via the window APIs
+ * but it will be removed after a timeout if the UID that added
+ * the window is not focused. Also only a single toast window
+ * is allowed at a time.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ToastTest extends BaseToastTest {
+ @Rule
+ public final ActivityTestRule<ToastActivity> mActivityRule =
+ new ActivityTestRule<>(ToastActivity.class);
+
+ @Test
+ public void testAddSingleNotFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+ // Show a toast on top of the focused activity
+ showToastsViaAddingWindow(1, false);
+
+ // Wait for the toast to timeout
+ waitForToastTimeout();
+
+ // Finish the activity so the UID loses focus
+ finishActivity(false);
+
+ // Wait for the toast to timeout
+ waitForToastTimeout();
+
+ // Show another toast
+ showToastsViaAddingWindow(1, false);
+ }
+
+ @Test
+ public void testAddSingleFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+ // Show a toast on top of our activity
+ showToastsViaAddingWindow(1, true);
+
+ // Wait for the toast to timeout
+ waitForToastTimeout();
+
+ // Show a toast on top of our activity
+ showToastsViaAddingWindow(1, true);
+ }
+
+ @Test
+ public void testAddSingleToastViaAddingWindowApisWhenUidNotFocused() throws Exception {
+ // Finish the activity so the UID loses focus
+ finishActivity(false);
+
+ // Show a toast
+ showToastsViaAddingWindow(1, true);
+
+ // Wait for the toast to timeout
+ waitForToastTimeout();
+
+ // Show a toast on top of our activity
+ showToastsViaAddingWindow(1, true);
+ }
+
+ @Test
+ public void testAddTwoToastsViaToastApisWhenUidFocused() throws Exception {
+ // Finish the activity so the UID loses focus
+ finishActivity(false);
+
+ // Normal toast windows cannot be obtained vie the accessibility APIs because
+ // they are not touchable. In this case not crashing is good enough.
+ showToastsViaToastApis(2);
+
+ // Wait for the first one to expire
+ waitForToastTimeout();
+ }
+
+ @Test
+ public void testAddTwoToastsViaToastApisWhenUidNotFocused() throws Exception {
+ // Normal toast windows cannot be obtained vie the accessibility APIs because
+ // they are not touchable. In this case not crashing is good enough.
+ showToastsViaToastApis(2);
+
+ // Wait for the first one to expire
+ waitForToastTimeout();
+ }
+
+ @Test
+ public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedQuickly() throws Exception {
+ try {
+ showToastsViaAddingWindow(2, false);
+ Assert.fail("Only one custom toast window at a time should be allowed");
+ } catch (WindowManager.BadTokenException e) {
+ /* expected */
+ } catch (Exception ex) {
+ Assert.fail("Unexpected exception when adding second toast window" + ex);
+ }
+ }
+
+ @Test
+ public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedSlowly() throws Exception {
+ // Add one window
+ showToastsViaAddingWindow(1, true);
+
+ // Wait for the toast to timeout
+ waitForToastTimeout();
+
+ // Add another window
+ showToastsViaAddingWindow(1, true);
+ }
+
+ private void finishActivity(boolean waitForEvent) throws Exception {
+ if (waitForEvent) {
+ mUiAutomation.executeAndWaitForEvent(
+ () -> mActivityRule.getActivity().finish(),
+ (event) -> event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED
+ , EVENT_TIMEOUT_MILLIS);
+ } else {
+ mActivityRule.getActivity().finish();
+ }
+ }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index ba1f3d2..7f7bd8c 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.view.cts">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
android:maxRecents="1"
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTests.java b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
index 3fa3634..f8bf79a 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTests.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
@@ -23,9 +23,12 @@
import android.graphics.SurfaceTexture;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
+import android.os.Debug;
+import android.os.Debug.MemoryInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.util.Log;
@@ -171,6 +174,61 @@
}
@Test
+ @LargeTest
+ public void testNotLeaking() {
+ try {
+ CountDownLatch swapFence = new CountDownLatch(2);
+ GLSurfaceViewCtsActivity.setGlVersion(2);
+ GLSurfaceViewCtsActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ GLSurfaceViewCtsActivity.setFixedSize(100, 100);
+ GLSurfaceViewCtsActivity.setRenderer(new QuadColorGLRenderer(
+ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, swapFence));
+
+ GLSurfaceViewCtsActivity activity =
+ mGLSurfaceViewActivityRule.launchActivity(null);
+
+ while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
+ activity.getView().requestRender();
+ }
+
+ // Test a fullsize copy
+ Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+
+ MemoryInfo meminfoStart = new MemoryInfo();
+ MemoryInfo meminfoEnd = new MemoryInfo();
+
+ for (int i = 0; i < 1000; i++) {
+ if (i == 2) {
+ // Not really the "start" but by having done a couple
+ // we've fully initialized any state that may be required,
+ // so memory usage should be stable now
+ Debug.getMemoryInfo(meminfoStart);
+ }
+ if (i % 10 == 5) {
+ Debug.getMemoryInfo(meminfoEnd);
+ if (meminfoEnd.getTotalPss() - meminfoStart.getTotalPss() > 1000 /* kb */) {
+ assertEquals("Memory leaked, iteration=" + i,
+ meminfoStart.getTotalPss(), meminfoEnd.getTotalPss(),
+ 1000 /* kb */);
+ }
+ }
+ SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+ int result = copyHelper.request(activity.getView(), bitmap);
+ assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
+ // Make sure nothing messed with the bitmap
+ assertEquals(100, bitmap.getWidth());
+ assertEquals(100, bitmap.getHeight());
+ assertEquals(Config.ARGB_8888, bitmap.getConfig());
+ assertBitmapQuadColor(bitmap,
+ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+ }
+
+ } catch (InterruptedException e) {
+ fail("Interrupted, error=" + e.getMessage());
+ }
+ }
+
+ @Test
public void testVideoProducer() throws InterruptedException {
PixelCopyVideoSourceActivity activity =
mVideoSourceActivityRule.launchActivity(null);
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
index da453dd..6444713 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -19,10 +19,12 @@
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.MediaPlayer;
+import android.os.Environment;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@@ -32,6 +34,7 @@
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -44,11 +47,19 @@
import android.view.cts.surfacevalidator.ViewFactory;
import android.widget.FrameLayout;
+import libcore.io.IoUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
@@ -56,15 +67,21 @@
@SuppressLint("RtlHardcoded")
public class SurfaceViewSyncTests {
private static final String TAG = "SurfaceViewSyncTests";
- private static final int PERMISSION_DIALOG_WAIT_MS = 500;
+ private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
+ /**
+ * Want to be especially sure we don't leave up the permission dialog, so try and dismiss both
+ * before and after test.
+ */
@Before
+ @After
public void setUp() throws UiObjectNotFoundException {
// The permission dialog will be auto-opened by the activity - find it and accept
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiSelector acceptButtonSelector = new UiSelector().resourceId("android:id/button1");
UiObject acceptButton = uiDevice.findObject(acceptButtonSelector);
if (acceptButton.waitForExists(PERMISSION_DIALOG_WAIT_MS)) {
+ Log.d(TAG, "found permission dialog, dismissing...");
assertTrue(acceptButton.click());
}
}
@@ -80,6 +97,9 @@
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
+ @Rule
+ public TestName mName = new TestName();
+
static ValueAnimator makeInfinite(ValueAnimator a) {
a.setRepeatMode(ObjectAnimator.REVERSE);
a.setRepeatCount(ObjectAnimator.INFINITE);
@@ -165,13 +185,76 @@
};
///////////////////////////////////////////////////////////////////////////
+ // Bad frame capture
+ ///////////////////////////////////////////////////////////////////////////
+
+ private void saveFailureCaptures(SparseArray<Bitmap> failFrames) {
+ if (failFrames.size() == 0) return;
+
+ String directoryName = Environment.getExternalStorageDirectory()
+ + "/" + getClass().getSimpleName()
+ + "/" + mName.getMethodName();
+ File testDirectory = new File(directoryName);
+ if (testDirectory.exists()) {
+ String[] children = testDirectory.list();
+ if (children == null) {
+ return;
+ }
+ for (String file : children) {
+ new File(testDirectory, file).delete();
+ }
+ } else {
+ testDirectory.mkdirs();
+ }
+
+ for (int i = 0; i < failFrames.size(); i++) {
+ int frameNr = failFrames.keyAt(i);
+ Bitmap bitmap = failFrames.valueAt(i);
+
+ String bitmapName = "frame_" + frameNr + ".png";
+ Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
+
+ File file = new File(directoryName, bitmapName);
+ FileOutputStream fileStream = null;
+ try {
+ fileStream = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
+ fileStream.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IoUtils.closeQuietly(fileStream);
+ }
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////////
+ public void verifyTest(AnimationTestCase testCase) {
+ CapturedActivity.TestResult result = getActivity().runTest(testCase);
+ saveFailureCaptures(result.failures);
+
+ float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
+ assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
+ failRatio < 0.95f);
+ assertTrue("Error: " + result.failFrames
+ + " incorrect frames observed - incorrect positioning",
+ result.failFrames == 0);
+ float framesPerSecond = 1.0f * result.passFrames
+ / TimeUnit.MILLISECONDS.toSeconds(CapturedActivity.CAPTURE_DURATION_MS);
+ assertTrue("Error, only " + result.passFrames
+ + " frames observed, virtual display only capturing at "
+ + framesPerSecond + " frames per second",
+ result.passFrames > 100);
+ }
+
/** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
@Test
public void testSmallRect() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
context -> new View(context) {
// draw a single pixel
final Paint sBlackPaint = new Paint();
@@ -191,8 +274,6 @@
new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
(blackishPixelCount, width, height) -> blackishPixelCount == 100));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
/**
@@ -201,52 +282,44 @@
*/
@Test
public void testEmptySurfaceView() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sEmptySurfaceViewFactory,
new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
sTranslateAnimationFactory,
(blackishPixelCount, width, height) ->
blackishPixelCount > 9000 && blackishPixelCount < 11000));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testSurfaceViewSmallScale() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sGreenSurfaceViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
sSmallScaleAnimationFactory,
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testSurfaceViewBigScale() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sGreenSurfaceViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
sBigScaleAnimationFactory,
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testVideoSurfaceViewTranslate() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
sTranslateAnimationFactory,
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testVideoSurfaceViewRotated() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
@@ -254,13 +327,11 @@
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testVideoSurfaceViewEdgeCoverage() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
view -> {
@@ -274,13 +345,11 @@
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
},
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
@Test
public void testVideoSurfaceViewCornerCoverage() {
- CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ verifyTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
view -> {
@@ -294,7 +363,5 @@
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
},
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
- assertTrue(result.passFrames > 100);
- assertTrue(result.failFrames == 0);
}
}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 6a23e02..07cb88c 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -19,7 +19,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaPlayer;
@@ -30,6 +33,7 @@
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import android.widget.FrameLayout;
@@ -40,10 +44,11 @@
public static class TestResult {
public int passFrames;
public int failFrames;
+ public final SparseArray<Bitmap> failures = new SparseArray<>();
}
private static final String TAG = "CapturedActivity";
- private static final long TIME_OUT_MS = 10000;
+ private static final long TIME_OUT_MS = 20000;
private static final int PERMISSION_CODE = 1;
private MediaProjectionManager mProjectionManager;
private MediaProjection mMediaProjection;
@@ -52,9 +57,11 @@
private SurfacePixelValidator mSurfacePixelValidator;
private final Object mLock = new Object();
- private static final long START_CAPTURE_DELAY_MS = 1000;
- private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + 4000;
- private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 500;
+ public static final long CAPTURE_DURATION_MS = 10000;
+
+ private static final long START_CAPTURE_DELAY_MS = 1500;
+ private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + CAPTURE_DURATION_MS;
+ private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 1000;
private MediaPlayer mMediaPlayer;
@@ -64,6 +71,12 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ if (mOnWatch) {
+ // Don't try and set up test/capture infrastructure - they're not supported
+ return;
+ }
+
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
@@ -74,8 +87,6 @@
mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
mMediaPlayer.setLooping(true);
-
- mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
/**
@@ -97,6 +108,10 @@
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mOnWatch) return;
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
if (requestCode != PERMISSION_CODE) {
throw new IllegalStateException("Unknown request code: " + requestCode);
}
@@ -143,6 +158,7 @@
display.getRealSize(size);
display.getMetrics(metrics);
+
mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
size, animationTestCase.getChecker());
Log.d("MediaProjection", "Size is " + size.toString());
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
index 55bc251..f58b9cb 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -15,22 +15,18 @@
*/
#pragma version(1)
#pragma rs java_package_name(android.view.cts.surfacevalidator)
+#pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
-int WIDTH;
uchar THRESHOLD;
-rs_allocation image;
-
-void countBlackishPixels(const int32_t *v_in, int *v_out){
- int y = v_in[0];
- v_out[0] = 0;
-
- for(int i = 0 ; i < WIDTH; i++){
- uchar4 pixel = rsGetElementAt_uchar4(image, i, y);
- if (pixel.r < THRESHOLD
- && pixel.g < THRESHOLD
- && pixel.b < THRESHOLD) {
- v_out[0]++;
- }
+static void countBlackishPixelsAccum(int *accum, uchar4 pixel){
+ if (pixel.r < THRESHOLD
+ && pixel.g < THRESHOLD
+ && pixel.b < THRESHOLD) {
+ *accum += 1;
}
}
+
+static void countBlackishPixelsCombiner(int *accum, const int *other){
+ *accum += *other;
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
index c9bff1d..5a30b77 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -16,6 +16,7 @@
package android.view.cts.surfacevalidator;
import android.content.Context;
+import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Handler;
import android.os.HandlerThread;
@@ -25,11 +26,13 @@
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Surface;
import android.view.cts.surfacevalidator.PixelChecker;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayList;
public class SurfacePixelValidator {
private static final String TAG = "SurfacePixelValidator";
@@ -43,6 +46,8 @@
// If no channel is greater than this value, pixel will be considered 'blackish'.
private static final short PIXEL_CHANNEL_THRESHOLD = 4;
+ private static final int MAX_CAPTURED_FAILURES = 5;
+
private final int mWidth;
private final int mHeight;
@@ -54,14 +59,13 @@
private final RenderScript mRS;
private final Allocation mInPixelsAllocation;
- private final Allocation mInRowsAllocation;
- private final Allocation mOutRowsAllocation;
private final ScriptC_PixelCounter mScript;
private final Object mResultLock = new Object();
private int mResultSuccessFrames;
private int mResultFailureFrames;
+ private SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES);
private Runnable mConsumeRunnable = new Runnable() {
int numSkipped = 0;
@@ -69,15 +73,10 @@
public void run() {
Trace.beginSection("consume buffer");
mInPixelsAllocation.ioReceive();
- mScript.set_image(mInPixelsAllocation);
Trace.endSection();
- Trace.beginSection("compare");
- mScript.forEach_countBlackishPixels(mInRowsAllocation, mOutRowsAllocation);
- Trace.endSection();
-
- Trace.beginSection("sum");
- int blackishPixelCount = sum1DIntAllocation(mOutRowsAllocation, mHeight);
+ Trace.beginSection("compare and sum");
+ int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get();
Trace.endSection();
boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
@@ -85,7 +84,6 @@
if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
numSkipped++;
} else {
-
if (success) {
mResultSuccessFrames++;
} else {
@@ -93,6 +91,15 @@
int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
+ ") occurred on frame " + totalFramesSeen);
+
+ if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) {
+ Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size());
+ // error, worth looking at...
+ Bitmap capture = Bitmap.createBitmap(mWidth, mHeight,
+ Bitmap.Config.ARGB_8888);
+ mInPixelsAllocation.copyTo(capture);
+ mFirstFailures.put(totalFramesSeen, capture);
+ }
}
}
}
@@ -113,9 +120,6 @@
mScript = new ScriptC_PixelCounter(mRS);
mInPixelsAllocation = createBufferQueueAllocation();
- mInRowsAllocation = createInputRowIndexAllocation();
- mOutRowsAllocation = createOutputRowAllocation();
- mScript.set_WIDTH(mWidth);
mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
mInPixelsAllocation.setOnBufferAvailableListener(
@@ -126,41 +130,10 @@
return mInPixelsAllocation.getSurface();
}
- static private int sum1DIntAllocation(Allocation array, int length) {
- //Get the values returned from the function
- int[] returnValue = new int[length];
- array.copyTo(returnValue);
- int sum = 0;
- //If any row had any different pixels, then it fails
- for (int i = 0; i < length; i++) {
- sum += returnValue[i];
- }
- return sum;
- }
-
- /**
- * Creates an allocation where the values in it are the indices of each row
- */
- private Allocation createInputRowIndexAllocation() {
- //Create an array with the index of each row
- int[] inputIndices = new int[mHeight];
- for (int i = 0; i < mHeight; i++) {
- inputIndices[i] = i;
- }
- //Create the allocation from that given array
- Allocation inputAllocation = Allocation.createSized(mRS, Element.I32(mRS),
- inputIndices.length, Allocation.USAGE_SCRIPT);
- inputAllocation.copyFrom(inputIndices);
- return inputAllocation;
- }
-
- private Allocation createOutputRowAllocation() {
- return Allocation.createSized(mRS, Element.I32(mRS), mHeight, Allocation.USAGE_SCRIPT);
- }
-
private Allocation createBufferQueueAllocation() {
return Allocation.createAllocations(mRS, Type.createXY(mRS,
- Element.U8_4(mRS), mWidth, mHeight),
+ Element.RGBA_8888(mRS)
+ /*Element.U32(mRS)*/, mWidth, mHeight),
Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
1)[0];
}
@@ -177,6 +150,10 @@
// Caller should only call this
testResult.failFrames = mResultFailureFrames;
testResult.passFrames = mResultSuccessFrames;
+
+ for (int i = 0; i < mFirstFailures.size(); i++) {
+ testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i));
+ }
}
mWorkerThread.quitSafely();
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
index a77d648..e09e0d6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -16,6 +16,7 @@
package android.webkit.cts;
+import android.cts.util.NullWebViewUtils;
import android.cts.util.PollingCheck;
import android.test.ActivityInstrumentationTestCase2;
@@ -136,6 +137,10 @@
// Test correct invocation of shouldInterceptRequest for Service Workers.
public void testServiceWorkerClientInterceptCallback() throws Exception {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
final InterceptServiceWorkerClient mInterceptServiceWorkerClient =
new InterceptServiceWorkerClient();
ServiceWorkerController swController = ServiceWorkerController.getInstance();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
index a4ebaca..3b67d84 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.cts.util.NullWebViewUtils;
import android.cts.util.PollingCheck;
-import android.os.StrictMode;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.Log;
@@ -89,21 +88,4 @@
assertTrue(m.matches());
assertEquals("42", m.group(1)); // value got incremented
}
-
- @UiThreadTest
- public void testStrictModeNotViolatedOnStartup() throws Throwable {
- StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- StrictMode.ThreadPolicy testPolicy = new StrictMode.ThreadPolicy.Builder()
- .detectDiskReads()
- .penaltyLog()
- .penaltyDeath()
- .build();
- StrictMode.setThreadPolicy(testPolicy);
- try {
- mActivity.createAndAttachWebView();
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- }
- }
-
}
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
index d1fe4ed..8c038dc 100644
--- a/tools/cts-api-coverage/src/Android.mk
+++ b/tools/cts-api-coverage/src/Android.mk
@@ -23,6 +23,10 @@
LOCAL_JAVA_RESOURCE_DIRS := res
LOCAL_JAR_MANIFEST := MANIFEST.mf
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-host-util \
+ dexlib2
+
LOCAL_MODULE := cts-api-coverage
LOCAL_MODULE_TAGS := optional
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/src/MANIFEST.mf
index b6aa831..63d6674 100644
--- a/tools/cts-api-coverage/src/MANIFEST.mf
+++ b/tools/cts-api-coverage/src/MANIFEST.mf
@@ -1,2 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.android.cts.apicoverage.CtsApiCoverage
+Class-Path: tradefed-prebuilt.jar
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java
new file mode 100644
index 0000000..3d1a07d
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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.apicoverage;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Set;
+
+/** Representation of the entire CDD. */
+class CddCoverage {
+
+ private final Map<String, CddRequirement> requirements = new HashMap<>();
+
+ public void addCddRequirement(CddRequirement cddRequirement) {
+ requirements.put(cddRequirement.getRequirementId(), cddRequirement);
+ }
+
+ public Collection<CddRequirement> getCddRequirements() {
+ return Collections.unmodifiableCollection(requirements.values());
+ }
+
+ public void addCoverage(String cddRequirementId, TestMethod testMethod) {
+ if (!requirements.containsKey(cddRequirementId)) {
+ requirements.put(cddRequirementId, new CddRequirement(cddRequirementId));
+ }
+
+ requirements.get(cddRequirementId).addTestMethod(testMethod);
+ }
+
+ static class CddRequirement {
+ private final String mRequirementId;
+ private final List<TestMethod> mtestMethods;
+
+ CddRequirement(String requirementId) {
+ this.mRequirementId = requirementId;
+ this.mtestMethods = new ArrayList<>();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ } else if (!(other instanceof CddRequirement)) {
+ return false;
+ } else {
+ return mRequirementId.equals(((CddRequirement)other).mRequirementId);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mRequirementId.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Requirement %s %s", mRequirementId, mtestMethods);
+ }
+
+ public String getRequirementId() { return mRequirementId; }
+
+ public void addTestMethod(TestMethod testMethod) {
+ mtestMethods.add(testMethod);
+ }
+
+ public Collection<TestMethod> getTestMethods() {
+ return Collections.unmodifiableCollection(mtestMethods);
+ }
+ }
+
+ static class TestMethod {
+ private final String mTestModule;
+ private final String mTestClass;
+ private final String mTestMethod;
+
+ TestMethod(String testModule, String testClass, String testMethod) {
+ this.mTestModule = testModule;
+ this.mTestClass = testClass;
+ this.mTestMethod = testMethod;
+ }
+
+ public String getTestModule() { return mTestModule; }
+
+ public String getTestClass() { return mTestClass; }
+
+ public String getTestMethod() { return mTestMethod; }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s#%s", mTestModule, mTestClass, mTestMethod);
+ }
+ }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index 6b69a57..2b57c76 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -16,6 +16,19 @@
package com.android.cts.apicoverage;
+import com.android.compatibility.common.util.CddTest;
+
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
+import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.value.StringEncodedValue;
+
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@@ -29,7 +42,9 @@
import java.io.OutputStream;
import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Set;
import javax.xml.transform.TransformerException;
@@ -52,6 +67,10 @@
private static final int FORMAT_HTML = 2;
+ private static final String CDD_REQUIREMENT_ANNOTATION = "Lcom/android/compatibility/common/util/CddTest;";
+
+ private static final String CDD_REQUIREMENT_ELEMENT_NAME = "requirement";
+
private static void printUsage() {
System.out.println("Usage: cts-api-coverage [OPTION]... [APK]...");
System.out.println();
@@ -70,6 +89,7 @@
System.out.println(" -a PATH path to the API XML file");
System.out.println(" -p PACKAGENAMEPREFIX report coverage only for package that start with");
System.out.println(" -t TITLE report title");
+ System.out.println(" -a API the Android API Level");
System.out.println();
System.exit(1);
}
@@ -82,6 +102,7 @@
String apiXmlPath = "";
PackageFilter packageFilter = new PackageFilter();
String reportTitle = "CTS API Coverage";
+ int apiLevel = Integer.MAX_VALUE;
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
@@ -106,6 +127,8 @@
packageFilter.addPrefixToFilter(getExpectedArg(args, ++i));
} else if ("-t".equals(args[i])) {
reportTitle = getExpectedArg(args, ++i);
+ } else if ("-a".equals(args[i])) {
+ apiLevel = Integer.parseInt(getExpectedArg(args, ++i));
} else {
printUsage();
}
@@ -131,12 +154,16 @@
*/
ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath);
+ CddCoverage cddCoverage = getEmptyCddCoverage();
// Add superclass information into api coverage.
apiCoverage.resolveSuperClasses();
for (File testApk : testApks) {
addApiCoverage(apiCoverage, testApk, dexDeps);
+ addCddCoverage(cddCoverage, testApk, apiLevel);
}
- outputCoverageReport(apiCoverage, testApks, outputFile, format, packageFilter, reportTitle);
+
+ outputCoverageReport(apiCoverage, cddCoverage, testApks, outputFile,
+ format, packageFilter, reportTitle);
}
/** Get the argument or print out the usage and exit. */
@@ -202,8 +229,106 @@
}
}
- private static void outputCoverageReport(ApiCoverage apiCoverage, List<File> testApks,
- File outputFile, int format, PackageFilter packageFilter, String reportTitle)
+ private static void addCddCoverage(CddCoverage cddCoverage, File testSource, int api)
+ throws IOException {
+
+ if (testSource.getName().endsWith(".apk")) {
+ addCddApkCoverage(cddCoverage, testSource, api);
+ } else if (testSource.getName().endsWith(".jar")) {
+ addCddJarCoverage(cddCoverage, testSource);
+ } else {
+ System.err.println("Unsupported file type for CDD coverage: " + testSource.getPath());
+ }
+ }
+
+ private static void addCddJarCoverage(CddCoverage cddCoverage, File testSource)
+ throws IOException {
+
+ Collection<Class<?>> classes = JarTestFinder.getClasses(testSource);
+ for (Class<?> c : classes) {
+ for (java.lang.reflect.Method m : c.getMethods()) {
+ if (m.isAnnotationPresent(CddTest.class)) {
+ CddTest cddTest = m.getAnnotation(CddTest.class);
+ CddCoverage.TestMethod testMethod =
+ new CddCoverage.TestMethod(
+ testSource.getName(), c.getName(), m.getName());
+ cddCoverage.addCoverage(cddTest.requirement(), testMethod);
+ }
+ }
+ }
+ }
+
+ private static void addCddApkCoverage(
+ CddCoverage cddCoverage, File testSource, int api)
+ throws IOException {
+
+ DexFile dexFile = null;
+ try {
+ dexFile = DexFileFactory.loadDexFile(
+ testSource, null /*dexEntry*/, Opcodes.forApi(api));
+ } catch (IOException | DexFileFactory.DexFileNotFound e) {
+ System.err.println("Unable to load dex file: " + testSource.getPath());
+ return;
+ }
+
+ String moduleName = testSource.getName();
+ for (ClassDef classDef : dexFile.getClasses()) {
+ String className = classDef.getType();
+ handleAnnotations(
+ cddCoverage, moduleName, className, null /*methodName*/,
+ classDef.getAnnotations());
+
+ for (Method method : classDef.getMethods()) {
+ String methodName = method.getName();
+ handleAnnotations(
+ cddCoverage, moduleName, className, methodName, method.getAnnotations());
+ }
+ }
+ }
+
+ private static void handleAnnotations(
+ CddCoverage cddCoverage, String moduleName, String className,
+ String methodName, Set<? extends Annotation> annotations) {
+ for (Annotation annotation : annotations) {
+ if (annotation.getType().equals(CDD_REQUIREMENT_ANNOTATION)) {
+ for (AnnotationElement annotationElement : annotation.getElements()) {
+ if (annotationElement.getName().equals(CDD_REQUIREMENT_ELEMENT_NAME)) {
+ String cddRequirement =
+ ((StringEncodedValue) annotationElement.getValue()).getValue();
+ CddCoverage.TestMethod testMethod =
+ new CddCoverage.TestMethod(
+ moduleName, dexToJavaName(className), methodName);
+ cddCoverage.addCoverage(cddRequirement, testMethod);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a string like Landroid/app/cts/DownloadManagerTest;
+ * return android.app.cts.DownloadManagerTest.
+ */
+ private static String dexToJavaName(String dexName) {
+ if (!dexName.startsWith("L") || !dexName.endsWith(";")) {
+ return dexName;
+ }
+ dexName = dexName.replace('/', '.');
+ if (dexName.length() > 2) {
+ dexName = dexName.substring(1, dexName.length() - 1);
+ }
+ return dexName;
+ }
+
+ private static CddCoverage getEmptyCddCoverage() {
+ CddCoverage cddCoverage = new CddCoverage();
+ // TODO(nicksauer): Read in the valid list of requirements
+ return cddCoverage;
+ }
+
+ private static void outputCoverageReport(ApiCoverage apiCoverage, CddCoverage cddCoverage,
+ List<File> testApks, File outputFile, int format, PackageFilter packageFilter,
+ String reportTitle)
throws IOException, TransformerException, InterruptedException {
OutputStream out = outputFile != null
@@ -213,15 +338,17 @@
try {
switch (format) {
case FORMAT_TXT:
- TextReport.printTextReport(apiCoverage, packageFilter, out);
+ TextReport.printTextReport(apiCoverage, cddCoverage, packageFilter, out);
break;
case FORMAT_XML:
- XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+ XmlReport.printXmlReport(testApks, apiCoverage, cddCoverage,
+ packageFilter, reportTitle, out);
break;
case FORMAT_HTML:
- HtmlReport.printHtmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+ HtmlReport.printHtmlReport(testApks, apiCoverage, cddCoverage,
+ packageFilter, reportTitle, out);
break;
}
} finally {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
index 0e6b54a..f53a884 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
@@ -37,7 +37,8 @@
class HtmlReport {
public static void printHtmlReport(final List<File> testApks, final ApiCoverage apiCoverage,
- final PackageFilter packageFilter, final String reportTitle, final OutputStream out)
+ final CddCoverage cddCoverage, final PackageFilter packageFilter,
+ final String reportTitle, final OutputStream out)
throws IOException, TransformerException {
final PipedOutputStream xmlOut = new PipedOutputStream();
final PipedInputStream xmlIn = new PipedInputStream(xmlOut);
@@ -45,7 +46,8 @@
Thread t = new Thread(new Runnable() {
@Override
public void run() {
- XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, xmlOut);
+ XmlReport.printXmlReport(
+ testApks, apiCoverage, cddCoverage, packageFilter, reportTitle, xmlOut);
// Close the output stream to avoid "Write dead end" errors.
try {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java
new file mode 100644
index 0000000..2aa3e72
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 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.apicoverage;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/** Representation of the entire CDD. */
+class JarTestFinder {
+
+ public static Collection<Class<?>> getClasses(File jarTestFile)
+ throws IllegalArgumentException {
+ List<Class<?>> classes = new ArrayList<>();
+
+ try (JarFile jarFile = new JarFile(jarTestFile)) {
+ Enumeration<JarEntry> e = jarFile.entries();
+
+ URL[] urls = {
+ new URL(String.format("jar:file:%s!/", jarTestFile.getAbsolutePath()))
+ };
+ URLClassLoader cl = URLClassLoader.newInstance(urls, JarTestFinder.class.getClassLoader());
+
+ while (e.hasMoreElements()) {
+ JarEntry je = e.nextElement();
+ if (je.isDirectory() || !je.getName().endsWith(".class")
+ || je.getName().contains("$")) {
+ continue;
+ }
+ String className = getClassName(je.getName());
+ if (!className.endsWith("Test")) {
+ continue;
+ }
+ try {
+ Class<?> cls = cl.loadClass(className);
+ int modifiers = cls.getModifiers();
+ if (!Modifier.isStatic(modifiers)
+ && !Modifier.isPrivate(modifiers)
+ && !Modifier.isProtected(modifiers)
+ && !Modifier.isInterface(modifiers)
+ && !Modifier.isAbstract(modifiers)) {
+
+ classes.add(cls);
+ }
+ } catch (ClassNotFoundException | Error x) {
+ System.err.println(
+ String.format("Cannot find test class %s from %s",
+ className, jarTestFile.getName()));
+ x.printStackTrace();
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return classes;
+ }
+
+ private static String getClassName(String name) {
+ // -6 because of .class
+ return name.substring(0, name.length() - 6).replace('/', '.');
+ }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
index 3adc020..978a652 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -27,8 +27,8 @@
*/
class TextReport {
- public static void printTextReport(ApiCoverage api, PackageFilter packageFilter,
- OutputStream outputStream) {
+ public static void printTextReport(ApiCoverage api, CddCoverage CddCoverage,
+ PackageFilter packageFilter, OutputStream outputStream) {
PrintStream out = new PrintStream(outputStream);
CoverageComparator comparator = new CoverageComparator();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 2ba16cf..e6ac3af 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -33,7 +33,8 @@
class XmlReport {
public static void printXmlReport(List<File> testApks, ApiCoverage apiCoverage,
- PackageFilter packageFilter, String reportTitle, OutputStream outputStream) {
+ CddCoverage cddCoverage, PackageFilter packageFilter, String reportTitle,
+ OutputStream outputStream) {
PrintStream out = new PrintStream(outputStream);
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<?xml-stylesheet type=\"text/xsl\" href=\"api-coverage.xsl\"?>");
@@ -137,6 +138,20 @@
}
out.println("</api>");
+ out.println("<cdd>");
+ for (CddCoverage.CddRequirement requirement : cddCoverage.getCddRequirements()) {
+ out.println("<requirement id=\"" + requirement.getRequirementId() + "\">");
+ for (CddCoverage.TestMethod method : requirement.getTestMethods()) {
+ out.print("<test module=\"" + method.getTestModule()
+ + "\" class=\"" + method.getTestClass() + "\" ");
+ if (method.getTestMethod() != null) {
+ out.print("method=\"" + method.getTestMethod() + "\"");
+ }
+ out.println("/>" );
+ }
+ out.println("</requirement>");
+ }
+ out.println("</cdd>");
out.println("<total numCovered=\"" + totalCoveredMethods + "\" "
+ "numTotal=\"" + totalMethods + "\" "
+ "coveragePercentage=\""